From f9d100ce013c0a64a4b2bf1ad9e1fbf7dfc6affc Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 21 Nov 2025 06:25:17 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20Phase=2025.1=20=E5=AE=8C=E4=BA=86=20-?= =?UTF-8?q?=20LoopForm=20v2/Stage1=20CLI/=E7=92=B0=E5=A2=83=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E5=89=8A=E6=B8=9B=20+=20Phase=2026-D=20=E3=81=8B?= =?UTF-8?q?=E3=82=89=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 25.1 完了成果: - ✅ LoopForm v2 テスト・ドキュメント・コメント完備 - 4ケース(A/B/C/D)完全テストカバレッジ - 最小再現ケース作成(SSAバグ調査用) - SSOT文書作成(loopform_ssot.md) - 全ソースに [LoopForm] コメントタグ追加 - ✅ Stage-1 CLI デバッグ環境構築 - stage1_cli.hako 実装 - stage1_bridge.rs ブリッジ実装 - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh) - アーキテクチャ改善提案文書 - ✅ 環境変数削減計画策定 - 25変数の完全調査・分類 - 6段階削減ロードマップ(25→5、80%削減) - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG) Phase 26-D からの累積変更: - PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等) - MIRビルダーリファクタリング - 型伝播・最適化パス改善 - その他約300ファイルの累積変更 🎯 技術的成果: - SSAバグ根本原因特定(条件分岐内loop変数変更) - Region+next_iパターン適用完了(UsingCollectorBox等) - LoopFormパターン文書化・テスト化完了 - セルフホスティング基盤強化 Co-Authored-By: Claude Co-Authored-By: ChatGPT Co-Authored-By: Task Assistant --- AGENTS.md | 41 +- CURRENT_TASK.md | 177 ++-- .../roadmap/phases/phase-25.1/README.md | 11 +- .../stage1-usingresolver-loopform.md | 1 + .../phases/phase-25.3-funcscanner/README.md | 57 +- .../roadmap/phases/phase-25/README.md | 12 +- .../runtime/cli-hakorune-stage1.md | 8 +- docs/guides/testing-guide.md | 61 ++ lang/src/compiler/entry/compiler_stageb.hako | 106 ++- .../compiler/entry/using_resolver_box.hako | 172 ++-- lang/src/compiler/parser/parser_box.hako | 7 +- .../parser/scan/parser_string_utils_box.hako | 126 ++- .../parser/stmt/parser_control_box.hako | 44 +- .../compiler/parser/stmt/parser_stmt_box.hako | 105 +-- .../pipeline_v2/using_resolver_box.hako | 4 + lang/src/runner/stage1_cli.hako | 4 + lang/src/using/resolve_ssot_box.hako | 24 + nyash.toml | 1 + src/abi/nyrt_shim.rs | 18 +- src/ast.rs | 8 +- src/ast/nodes.rs | 113 ++- src/ast/utils.rs | 6 +- src/backend/abi_util.rs | 18 +- src/backend/mir_interpreter/exec.rs | 73 +- .../mir_interpreter/handlers/arithmetic.rs | 3 +- src/backend/mir_interpreter/handlers/boxes.rs | 44 +- .../mir_interpreter/handlers/boxes_array.rs | 102 +-- .../handlers/boxes_instance.rs | 99 ++- .../mir_interpreter/handlers/boxes_map.rs | 233 +++--- .../handlers/boxes_object_fields.rs | 177 +++- .../mir_interpreter/handlers/boxes_plugin.rs | 56 +- .../mir_interpreter/handlers/boxes_string.rs | 416 +++++----- .../mir_interpreter/handlers/calls/externs.rs | 18 +- .../mir_interpreter/handlers/calls/global.rs | 57 +- .../mir_interpreter/handlers/calls/method.rs | 87 +- .../mir_interpreter/handlers/calls/mod.rs | 64 +- .../handlers/extern_provider.rs | 367 ++++++--- .../mir_interpreter/handlers/externals.rs | 98 ++- src/backend/mir_interpreter/handlers/misc.rs | 22 +- src/backend/mir_interpreter/handlers/mod.rs | 15 +- src/backend/mir_interpreter/helpers.rs | 182 ++++- src/backend/mir_interpreter/method_router.rs | 74 +- src/backend/mir_interpreter/mod.rs | 52 +- .../utils/conversion_helpers.rs | 14 +- .../mir_interpreter/utils/error_helpers.rs | 10 +- src/backend/mir_interpreter/utils/mod.rs | 6 +- src/backend/mir_interpreter/utils/naming.rs | 1 - .../mir_interpreter/utils/receiver_helpers.rs | 4 +- src/box_arithmetic.rs | 2 +- src/box_factory/builtin_impls/array_box.rs | 4 +- src/box_factory/builtin_impls/bool_box.rs | 4 +- src/box_factory/builtin_impls/console_box.rs | 4 +- src/box_factory/builtin_impls/file_box.rs | 6 +- src/box_factory/builtin_impls/integer_box.rs | 4 +- src/box_factory/builtin_impls/map_box.rs | 4 +- src/box_factory/builtin_impls/mod.rs | 16 +- src/box_factory/builtin_impls/null_box.rs | 4 +- src/box_factory/builtin_impls/string_box.rs | 4 +- src/box_factory/mod.rs | 47 +- src/box_factory/plugin.rs | 17 +- src/box_factory/user_defined.rs | 2 +- src/box_operators.rs | 88 +- src/box_operators/helpers.rs | 2 +- src/box_operators/macros.rs | 10 +- src/box_operators/static_ops.rs | 2 +- src/box_trait.rs | 10 +- src/boxes/arithmetic/add_box.rs | 2 +- src/boxes/arithmetic/compare_box.rs | 2 +- src/boxes/arithmetic/divide_box.rs | 2 +- src/boxes/arithmetic/mod.rs | 14 +- src/boxes/arithmetic/multiply_box.rs | 2 +- src/boxes/arithmetic/subtract_box.rs | 2 +- src/boxes/array/mod.rs | 12 +- src/boxes/basic/bool_box.rs | 4 +- src/boxes/basic/error_box.rs | 4 +- src/boxes/basic/file_box.rs | 2 +- src/boxes/basic/integer_box.rs | 4 +- src/boxes/basic/mod.rs | 16 +- src/boxes/basic/string_box.rs | 2 +- src/boxes/basic/void_box.rs | 4 +- src/boxes/debug_box.rs | 2 +- src/boxes/egui_box.rs | 2 +- src/boxes/file/box_shim.rs | 19 +- src/boxes/file/builtin_factory.rs | 8 +- src/boxes/file/mod.rs | 50 +- src/boxes/file/provider.rs | 12 +- src/boxes/http_message_box.rs | 32 +- src/boxes/map_box.rs | 10 +- src/boxes/missing_box.rs | 45 +- src/boxes/mod.rs | 4 +- src/cli/args.rs | 141 +++- src/cli/mod.rs | 15 +- src/cli/utils.rs | 1 - src/config/env.rs | 89 +- src/config/provider_env.rs | 21 +- src/debug/hub.rs | 18 +- src/debug/mod.rs | 2 +- src/grammar/generated.rs | 34 +- src/host_providers/llvm_codegen.rs | 210 +++-- src/host_providers/mir_builder.rs | 35 +- src/host_providers/mod.rs | 3 +- src/instance_v2.rs | 13 +- src/lib.rs | 16 +- src/macro/ast_json.rs | 279 ++++++- src/macro/ctx.rs | 8 +- src/macro/engine.rs | 216 ++++- src/macro/macro_box.rs | 138 +++- src/macro/macro_box_ny.rs | 509 +++++++++--- src/macro/mod.rs | 618 +++++++++++--- src/macro/pattern.rs | 484 +++++++++-- src/main.rs | 6 +- src/mir/aot_plan_import.rs | 12 +- src/mir/basic_block.rs | 5 +- src/mir/builder.rs | 195 +++-- src/mir/builder/call_resolution.rs | 7 +- src/mir/builder/calls/annotation.rs | 67 +- src/mir/builder/calls/build.rs | 134 ++- src/mir/builder/calls/call_target.rs | 4 +- src/mir/builder/calls/call_unified.rs | 14 +- src/mir/builder/calls/effects_analyzer.rs | 31 +- src/mir/builder/calls/emit.rs | 48 +- src/mir/builder/calls/extern_calls.rs | 35 +- src/mir/builder/calls/function_lowering.rs | 49 +- src/mir/builder/calls/guard.rs | 34 +- src/mir/builder/calls/lowering.rs | 49 +- src/mir/builder/calls/materializer.rs | 18 +- src/mir/builder/calls/method_resolution.rs | 25 +- src/mir/builder/calls/mod.rs | 24 +- src/mir/builder/calls/resolver.rs | 154 ++-- src/mir/builder/calls/special_handlers.rs | 43 +- src/mir/builder/calls/unified_emitter.rs | 167 +++- src/mir/builder/context.rs | 3 +- src/mir/builder/control_flow.rs | 4 +- src/mir/builder/decls.rs | 88 +- src/mir/builder/emission/branch.rs | 15 +- src/mir/builder/emission/compare.rs | 24 +- src/mir/builder/emission/constant.rs | 32 +- src/mir/builder/emission/mod.rs | 4 +- src/mir/builder/emit_guard/mod.rs | 6 +- src/mir/builder/exprs.rs | 44 +- src/mir/builder/exprs_call.rs | 4 +- src/mir/builder/exprs_peek.rs | 36 +- src/mir/builder/exprs_qmark.rs | 4 +- src/mir/builder/fields.rs | 27 +- src/mir/builder/if_form.rs | 145 ++-- src/mir/builder/lifecycle.rs | 90 +- src/mir/builder/loops.rs | 5 +- src/mir/builder/metadata/mod.rs | 1 - src/mir/builder/metadata/propagate.rs | 10 +- src/mir/builder/method_call_handlers.rs | 16 +- src/mir/builder/name_const.rs | 2 +- src/mir/builder/observe/mod.rs | 3 +- src/mir/builder/observe/resolve.rs | 25 +- src/mir/builder/ops.rs | 156 +++- src/mir/builder/origin/infer.rs | 8 +- src/mir/builder/origin/mod.rs | 1 - src/mir/builder/origin/phi.rs | 34 +- src/mir/builder/phi.rs | 100 ++- src/mir/builder/receiver.rs | 18 +- src/mir/builder/rewrite/known.rs | 119 ++- src/mir/builder/rewrite/mod.rs | 1 - src/mir/builder/rewrite/special.rs | 112 ++- src/mir/builder/router/mod.rs | 1 - src/mir/builder/schedule/block.rs | 49 +- src/mir/builder/schedule/mod.rs | 1 - src/mir/builder/ssa/local.rs | 78 +- src/mir/builder/ssa/mod.rs | 1 - src/mir/builder/stmts.rs | 93 ++- src/mir/builder/type_registry.rs | 26 +- src/mir/builder/types/annotation.rs | 50 +- src/mir/builder/types/mod.rs | 1 - src/mir/builder/utils.rs | 93 ++- src/mir/definitions/call_unified.rs | 36 +- src/mir/definitions/mod.rs | 2 +- src/mir/function.rs | 8 +- src/mir/function_emission.rs | 21 +- src/mir/hints.rs | 190 ++++- src/mir/instruction.rs | 8 +- src/mir/instruction/methods.rs | 5 +- src/mir/instruction/tests.rs | 4 +- src/mir/instruction_kinds/mod.rs | 388 ++++++--- src/mir/mod.rs | 36 +- src/mir/optimizer.rs | 20 +- src/mir/optimizer_passes/diagnostics.rs | 2 +- src/mir/optimizer_passes/mod.rs | 2 +- src/mir/optimizer_passes/normalize.rs | 56 +- .../optimizer_passes/normalize_core13_pure.rs | 94 ++- src/mir/passes/cse.rs | 8 +- src/mir/phi_core/body_local_phi_builder.rs | 62 +- src/mir/phi_core/common.rs | 18 +- src/mir/phi_core/conservative.rs | 14 +- src/mir/phi_core/header_phi_builder.rs | 18 +- src/mir/phi_core/if_phi.rs | 52 +- src/mir/phi_core/local_scope_inspector.rs | 26 +- src/mir/phi_core/loop_phi.rs | 45 +- src/mir/phi_core/loop_snapshot_manager.rs | 6 +- src/mir/phi_core/loop_var_classifier.rs | 90 +- src/mir/phi_core/loopform_builder.rs | 209 +++-- src/mir/phi_core/mod.rs | 4 +- src/mir/phi_core/phi_input_collector.rs | 40 +- src/mir/printer.rs | 2 +- src/mir/printer_helpers.rs | 127 ++- src/mir/region/function_slot_registry.rs | 8 +- src/mir/region/mod.rs | 2 +- src/mir/region/observer.rs | 13 +- src/mir/ssot/cf_common.rs | 6 +- src/mir/ssot/loop_common.rs | 19 +- src/mir/ssot/mod.rs | 2 +- src/mir/types.rs | 1 - src/mir/utils/control_flow.rs | 42 +- src/mir/utils/mod.rs | 10 +- src/mir/utils/phi_helpers.rs | 22 +- src/mir/value_kind.rs | 2 +- src/mir/verification.rs | 59 +- src/mir/verification/barrier.rs | 2 +- src/mir/verification/cfg.rs | 43 +- src/mir/verification/dom.rs | 17 +- src/mir/verification/ssa.rs | 8 +- src/mir/verification_types.rs | 13 +- src/parser/common.rs | 14 +- src/parser/cursor.rs | 103 ++- src/parser/declarations/box_def/header.rs | 2 +- src/parser/declarations/box_def/interface.rs | 2 +- .../declarations/box_def/members/common.rs | 12 +- .../box_def/members/constructors.rs | 9 +- .../declarations/box_def/members/fields.rs | 42 +- .../declarations/box_def/members/methods.rs | 11 +- .../declarations/box_def/members/mod.rs | 5 +- .../declarations/box_def/members/postfix.rs | 10 +- .../box_def/members/properties.rs | 384 +++++++-- src/parser/declarations/box_def/mod.rs | 65 +- src/parser/declarations/box_def/validators.rs | 44 +- src/parser/declarations/static_def/header.rs | 33 +- src/parser/declarations/static_def/members.rs | 43 +- src/parser/declarations/static_def/mod.rs | 60 +- .../declarations/static_def/validators.rs | 5 +- src/parser/expr/match_expr.rs | 115 ++- src/parser/expr/mod.rs | 2 +- src/parser/expr/primary.rs | 4 +- src/parser/expr_cursor.rs | 22 +- src/parser/expressions.rs | 2 +- src/parser/mod.rs | 108 ++- src/parser/statements/control_flow.rs | 2 +- src/parser/statements/declarations.rs | 8 +- src/parser/statements/exceptions.rs | 8 +- src/parser/statements/helpers.rs | 3 +- src/parser/statements/io_async.rs | 4 +- src/parser/statements/mod.rs | 48 +- src/parser/statements/modules.rs | 7 +- src/parser/statements/variables.rs | 28 +- src/providers/mod.rs | 1 - src/providers/ring1/file/core_ro.rs | 11 +- src/providers/ring1/file/mod.rs | 1 - src/providers/ring1/mod.rs | 1 - src/runner/cli_directives.rs | 107 ++- src/runner/core_executor.rs | 19 +- src/runner/dispatch.rs | 93 ++- src/runner/hv1_inline.rs | 769 +++++++++++------- src/runner/json_v0_bridge/lowering.rs | 109 ++- src/runner/json_v0_bridge/lowering/expr.rs | 72 +- src/runner/json_v0_bridge/lowering/if_else.rs | 18 +- src/runner/json_v0_bridge/lowering/loop_.rs | 47 +- .../json_v0_bridge/lowering/match_expr.rs | 36 +- src/runner/json_v0_bridge/lowering/merge.rs | 4 +- src/runner/json_v0_bridge/lowering/ternary.rs | 10 +- .../json_v0_bridge/lowering/throw_ctx.rs | 11 +- .../json_v0_bridge/lowering/try_catch.rs | 143 +++- src/runner/json_v0_bridge/mod.rs | 16 +- src/runner/json_v1_bridge.rs | 316 ++++--- src/runner/mir_json/common.rs | 31 +- src/runner/mir_json_emit.rs | 149 +++- src/runner/mir_json_v0.rs | 166 +++- src/runner/mod.rs | 172 +++- src/runner/modes/common.rs | 6 +- src/runner/modes/common_util/core_bridge.rs | 47 +- src/runner/modes/common_util/diag.rs | 97 ++- src/runner/modes/common_util/exec.rs | 64 +- src/runner/modes/common_util/hako.rs | 13 +- src/runner/modes/common_util/io.rs | 9 +- src/runner/modes/common_util/mod.rs | 14 +- .../modes/common_util/provider_registry.rs | 45 +- src/runner/modes/common_util/pyvm.rs | 28 +- .../modes/common_util/resolve/context.rs | 33 + src/runner/modes/common_util/resolve/mod.rs | 44 +- .../modes/common_util/resolve/path_util.rs | 1 - .../common_util/resolve/prelude_manager.rs | 73 +- src/runner/modes/common_util/resolve/seam.rs | 18 +- .../common_util/resolve/selfhost_pipeline.rs | 35 +- src/runner/modes/common_util/resolve/strip.rs | 215 ++++- .../common_util/resolve/using_resolution.rs | 47 +- .../modes/common_util/selfhost/child.rs | 5 +- src/runner/modes/common_util/selfhost/json.rs | 10 +- src/runner/modes/common_util/selfhost/mod.rs | 1 - src/runner/modes/common_util/selfhost_exe.rs | 10 +- src/runner/modes/llvm.rs | 61 +- src/runner/modes/macro_child/entry.rs | 160 +++- src/runner/modes/macro_child/mod.rs | 3 +- .../modes/macro_child/transforms/array.rs | 116 ++- .../modes/macro_child/transforms/foreach.rs | 338 +++++++- .../macro_child/transforms/if_to_loopform.rs | 131 ++- .../modes/macro_child/transforms/lift.rs | 421 ++++++++-- .../modes/macro_child/transforms/loops.rs | 106 ++- .../modes/macro_child/transforms/map.rs | 97 ++- .../modes/macro_child/transforms/mod.rs | 40 +- .../modes/macro_child/transforms/peek.rs | 322 ++++++-- .../modes/macro_child/transforms/postfix.rs | 147 +++- .../modes/macro_child/transforms/scopebox.rs | 127 ++- src/runner/modes/mir.rs | 10 +- src/runner/modes/mir_interpreter.rs | 9 +- src/runner/modes/mod.rs | 4 +- src/runner/modes/pyvm.rs | 137 +++- src/runner/modes/vm.rs | 69 +- src/runner/modes/vm_fallback.rs | 103 ++- src/runner/modes/wasm.rs | 9 +- src/runner/pipe_io.rs | 36 +- src/runner/pipeline.rs | 97 ++- src/runner/plugins.rs | 44 +- src/runner/selfhost.rs | 193 +++-- src/runner_plugin_init.rs | 23 +- src/runtime/deprecations.rs | 1 - src/runtime/gc.rs | 8 +- src/runtime/gc_controller.rs | 17 +- src/runtime/gc_mode.rs | 1 - src/runtime/gc_trace.rs | 1 - src/runtime/global_hooks.rs | 4 +- src/runtime/mod.rs | 14 +- src/runtime/observe.rs | 1 - src/runtime/plugin_loader_unified.rs | 90 +- .../enabled/extern_functions.rs | 21 +- .../plugin_loader_v2/enabled/ffi_bridge.rs | 59 +- .../enabled/instance_manager.rs | 12 +- .../enabled/loader/singletons.rs | 22 +- .../enabled/method_resolver.rs | 42 +- src/runtime/plugin_loader_v2/enabled/mod.rs | 2 +- src/runtime/provider_lock.rs | 22 +- src/runtime/provider_verify.rs | 67 +- src/stdlib/mod.rs | 2 +- src/tests/identical_exec_instance.rs | 2 +- src/tests/mir_breakfinder_ssa.rs | 5 +- src/tests/mir_funcscanner_skip_ws.rs | 16 +- src/tests/mir_funcscanner_ssa.rs | 24 +- src/tests/mir_locals_ssa.rs | 24 +- src/tests/mir_stage1_using_resolver_verify.rs | 215 ++++- src/tests/mir_stageb_like_args_length.rs | 27 +- src/tests/mir_stageb_loop_break_continue.rs | 10 +- src/tests/mir_value_kind.rs | 50 +- src/tests/mod.rs | 14 +- src/tests/parser_static_box_members.rs | 44 +- src/tests/typebox_tlv_diff.rs | 88 +- src/tokenizer/cursor.rs | 1 - src/tokenizer/engine.rs | 22 +- src/tokenizer/kinds.rs | 12 +- src/tokenizer/lex_ident.rs | 12 +- src/tokenizer/lex_number.rs | 1 - src/tokenizer/lex_string.rs | 11 +- src/tokenizer/mod.rs | 11 +- src/tokenizer/whitespace.rs | 1 - src/using/mod.rs | 6 +- src/using/policy.rs | 1 - src/using/resolver.rs | 130 ++- src/using/simple_registry.rs | 34 +- src/using/spec.rs | 1 - src/using/ssot_bridge.rs | 27 +- tests/mir_instruction_unit.rs | 2 +- tests/mir_static_main_args_loop.rs | 8 +- .../stageb_fib_program_defs_canary_vm.sh | 1 + 366 files changed, 14322 insertions(+), 5236 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1d11dcac..9e810ef3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,38 +146,38 @@ PR テンプレ(追加項目) - [ ] 「スモークを通すためだけのハードコード」を入れていません(根治で解決) - [ ] 受け入れ基準に記載の検証を実施しました(dev OFF/ログ確認) -### 5.2 Rust Freeze Policy(Self‑Host First) +### 5.2 Rust Minimal Policy(Self‑Host First, but not Frozen) -目的: 脱Rustで開発効率を最大化する。Rust層は“最小シーム+バグ修正”のみに留め、分析/ルール/可視化は .hako 側で実装する。 +目的: 脱Rustを志向しつつも、Stage‑1 / Self‑Host ラインの整備やツールの使いやすさ向上のために、**Rust層で必要な構造的変更やブリッジ強化は積極的に許可する**。分析/ルール/可視化は引き続き .hako 側が主戦場。 - 原則 - - Rustは「SSOTランナー導線(resolve→parse→merge)」と「VM/Interpreterの安定化(バグ修正、挙動不変)」のみ。 - - 新規機能・ルール・可視化・チェックは .hako で実装(自己ホスト)。 - - 変更は既定OFFのトグルで可逆・小差分・戻し手順あり(AGENTS.md 5.1に準拠)。 + - Rustは「SSOTランナー導線(resolve→parse→merge)」と「VM/Interpreterの安定化」を軸にしつつ、Stage‑1 CLI / selfhost ブリッジ / エラーメッセージ改善など**開発導線の改善**も扱ってよい。 + - 新規の言語ルール・静的解析ロジック・ビジネスロジックは、基本的に .hako 側で実装(自己ホスト)。 + - 変更はできるだけ小さく・可逆に保ちつつ、必要に応じてトグルや dev 用フラグでガード(AGENTS.md 5.1 に準拠)。 -- 許可(最小のRust変更) - - ランナー導線の保守(using text‑merge for .hako の維持)。 - - バグ修正(既定挙動不変、必要ならトグル既定OFF)。 - - Analyzerシームの提供(抽出のみ): - - 例: `--hako-analyze ` → AST/Analysis JSON を stdout/--out に出力。 - - 判定ロジックは持たない(抽出専用)。 - - 薄いCLI糖衣:`--hako-check`(内部で `--hako-analyze`→.hakoアナライザをVM経由で実行)。 +- 許可(Rustでやってよいことの例) + - ランナー導線の保守・改善(Stage‑B/Stage‑1/Stage0 の配線、using text‑merge for .hako の維持・整理)。 + - Stage‑1 / Stage‑B / selfhost 向けのブリッジ強化(子プロセス起動、環境変数の集約、エラー表示の改善)。 + - バグ修正(既定挙動を壊さない範囲。必要なら既定OFFトグル)。 + - Analyzerシームの提供(AST/Analysis JSON の抽出専用。判定ロジックは持たない)。 + - 薄いCLI糖衣:`--hako-check` など、.hako 側のアナライザを呼び出すための CLI エントリ。 -- 禁止/抑制 - - ルール実装(Lint/命名/依存/特定Box名分岐)をRustに持ち込むこと。 - - 広域リファクタ・既定挙動変更(凍結)。 +- 禁止/抑制(依然として .hako 側でやる領域) + - Lint / 命名規則 / 依存関係チェック / 特定 Box 名への分岐など、**ルール実装の本体**。 + - LoopSSA / 数値カーネル / 高レベルの最適化ロジックを Rust に新規実装すること(Self‑Host で十分に表現できる範囲)。 + - 広域リファクタや既定挙動を大きく変える変更(必要なら Phase/Proposal に切り出してから)。 - .hako 側の責務(Self‑Host) - - Lint/解析/可視化/関係図(DOT)を .hako で実装(tools/hako_check/*)。 + - Lint / 解析 / 可視化 / 関係図(DOT)を .hako で実装(tools/hako_check/*)。 - 解析入力は Rust の Analysis JSON(または AST JSON)。 - 返り値は件数ベース(0=OK、>0=警告/エラー件数)。 - 受け入れ基準(Rust変更時) - - quickスモーク/Adapter reps 緑維持(既定挙動不変)。 - - 変更はトグルでガード(既定OFF)・小差分・戻し手順付き。 - - .hako からの利用例/READMEを更新。 + - quick スモーク / 代表テストが緑維持(既定挙動を意図せず変えていない)。 + - 変更は目的が明確で、小さく・可逆(トグルや限定スコープでガード)であること。 + - .hako からの利用例 / README / proposal を更新し、将来の開発者が迷わないようにしておく。 -補足:自己ホスト達成済みのため、Rust層は“何かあった時のための最低限の手入れ”に限定。日常の機能拡張は .hako 側で行い、構造/テスト/ドキュメントを伴って進める。 +補足:.hako 側の解析・Stage‑1 ラインが十分に動き始めたので、Rust層は「完全凍結」ではなく、**Self‑Host を支えるための最小+必要な整備を行う層**という扱いに緩和する。日常の機能拡張や言語仕様変更はこれまで通り .hako 側で行い、Rust 変更は「導線の改善」「ブリッジ」「可観測性の向上」にフォーカスする。 ### 6. Fail-Fast with Structure @@ -379,6 +379,7 @@ Notes - Phase‑17(LoopForm Self‑Hosting & Polish): `docs/development/roadmap/phases/phase-17-loopform-selfhost/` - MacroBox(ユーザー拡張): `docs/guides/macro-box.md` - MacroBox in Nyash(設計草案): `docs/guides/macro-box-nyash.md` +- MIR デバッグ総覧(dump/hints/__mir__): `docs/guides/testing-guide.md`(`MIR デバッグの入口まとめ` セクション) # Repository Guidelines diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 21947be6..4addc4a6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -17,11 +17,37 @@ - .hako 側: - Stage‑B コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。 - JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。 +- Stage‑B / FuncScanner ライン: + - Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs` に `TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。 + - Stage‑B は block パーサ優先 + defs を Block 包みで構造化。次手: Stage‑1 UsingResolver ループの Region+next_i 揃え / Stage‑1 CLI program-json selfhost 準備。 --- ## 1. 最近完了した重要タスク +### 1-0. Phase 25.3 — FuncScanner / Stage‑B defs 安定化(完了) + +**目的** +- Stage‑B / FuncScanner ラインで defs が欠落したり Loop が脱落する問題を塞ぎ、selfhost 側の canary を緑に戻す。 + +**やったこと** +- StageBDriverBox.main: + - main 本文を `{…}` で包んだ block パーサ優先で Program(JSON) に組み立て、defs には `{"type":"Block","body":[…]}` 形式で埋め込むよう整理。 + - Program パーサ fallback は `HAKO_STAGEB_PROGRAM_PARSE_FALLBACK=1` の opt-in に封じ込め(skip_ws 崩れを回避)。 +- StageBFuncScannerBox._scan_methods: + - block パーサ優先に統一し、Program パーサは `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1` でのみ有効化。 + - defs パラメータに必ず `me` を足す従来挙動は維持(TestBox/Main いずれも同型で出力)。 +- Rust 層の追加変更なし(LoopForm v2 / LoopSnapshotMergeBox をそのまま利用)。 + +**結果** +- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` が安定して PASS。 + - `Program.kind == "Program"` + - `defs` に `TestBox.fib` / `Main.main` を保持していること。 + - `TestBox.fib.body.body[*]` に `Loop` ノードが含まれること。 + を満たした状態で `rc=0` になることを確認。 +- FuncScanner / Stage‑B 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox 上で構造的に安定したとみなし、Phase 25.3 はクローズ。 +- 次フェーズの入口が整理できたので、Stage‑1 UsingResolver ループ(Region+next_i 形)と Stage‑1 CLI program-json/selfhost 導線に着手可能。 + ### 1-1. Phase 25.1m — Static Method / LoopForm v2 continue + PHI Fix(完了) **目的** @@ -138,6 +164,26 @@ --- +### 1-4. Phase 25.1A‑3 — Stage‑1 CLI bridge(stub 実装) + +- Rust 側: `src/runner/stage1_bridge.rs` を `run_refactored` 入口に組み込み、`NYASH_USE_STAGE1_CLI=1` かつ再入ガードなしのときに `lang/src/runner/stage1_cli.hako` を子プロセスで起動する。`STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `STAGE1_BACKEND` / `STAGE1_PROGRAM_JSON` で mode 選択。entry override は `STAGE1_CLI_ENTRY` / `HAKORUNE_STAGE1_ENTRY`。 +- .hako 側: `Stage1Cli` に最低限の本体を実装。 + - `emit_program_json`: Stage‑1 UsingResolver で prefix を結合し、BuildBox.emit_program_json_v0 で Program(JSON v0) を返す。 + - `emit_mir_json`: `MirBuilderBox.emit_from_program_json_v0` をそのまま呼ぶ(delegate 未設定なら null)。 + - `run_program_json`: backend==llvm の場合は `env.codegen.emit_object` まで通す。vm/pyvm は当面 MIR(JSON) を stdout に出すのみ(実行は Stage0 橋渡し未配線)。 + - CLI: `emit program-json|mir-json` / `run --backend ... ` を受理。`NYASH_SCRIPT_ARGS_JSON` を JSON で best-effort 伝播。 +- Docs: `docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md` に stub 状態を追記(run は暫定挙動)。 +- Known gaps: vm/pyvm 実行はまだ Stage0 への橋渡し未着手。llvm も emit object 止まり(link/exec は後続)。 + +### 1-5. Phase 25.1A‑4 — Using SSOT 薄設計+BuildBox include 除去 + +- SSOT: `lang/src/using/resolve_ssot_box.hako` に README 相当のコメントと I/F(resolve_modules/resolve_prefix)を追加。現状は no-op だが「using をここで扱う」窓口を固定。 +- Stage1UsingResolver: `resolve_for_program_json` を追加し、SSOT を呼ぶ導線だけ確保(現状は透過返し)。 +- BuildBox: 先頭の `include` を削除し、`using lang.compiler.entry.bundle_resolver as BundleResolver` に置換(Stage‑B パーサが include を解せない問題の足場づくり)。 +- 実行確認: `NYASH_ALLOW_NYASH=1 HAKO_ALLOW_NYASH=1 NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ... cargo run --bin hakorune -- basic_test.hako` が RC=0 で完走(ただし stdout は `RC: 0` のみ、program-json 出力は未配線)。`cargo run ... emit program-json` は Rust CLI 側の表面が未対応のためエラー(想定挙動)。 + +--- + ## 2. まだ残っている問題・課題(2025-11-18 時点) ### 2-1. Stage‑B 本体の型エラー(`String > Integer(13)`) @@ -230,27 +276,51 @@ ## 3. 次にやること(候補タスク) -ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。 +ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。 +Rust 側は LoopForm v2 / Stage‑B fib / Stage‑1 UsingResolver 構造テストまで一通り整ったので、当面は Stage‑1 CLI / selfhost ラインの小さな箱から進める。 -1. **Stage‑B / BreakFinder / FuncScanner ラインの SSA 根治(Phase 25.1m 続き)** - - JSON v0 bridge の Loop lowering で、`backedge_to_cond` が `Jump` だけでなく `Branch(cond→header/exit)` も認識するように修正(`loop_.rs`)。 - → BreakFinderBox._find_loops/2 のような break を含むループでも header PHI が正しく張られるようにした。 - - BreakFinderBox は解析用の static box として扱い、`me._find_loops` / `me._jumps_to` を `BreakFinderBox._find_loops` / `_jumps_to` に正規化。 - → `me` 未定義による Undefined ValueId を避ける。 - - Stage‑B → Stage‑1 パーサ経路は、`ParserBox.parse_program2` のトップレベルループではなく、`parse_block2` で `Main.main` の本文をブロックとしてパースし、 - Program(JSON v0) を自前で組み立てる構造に変更(Stage‑B 固まり問題を回避)。 +### A. Stage‑1 CLI / Stage0 ブリッジ(Phase 25.1 — いまここ) -2. **Stage‑B 再入ガード `env.set/2` の整理** - - 再入ガードを Box 内 state で持つ方向か、Rust 側に dev 専用 extern を追加するかを決める。 - - どちらにしても「本番経路(prod ランナー)には影響しない」ようフラグでガードする。 +- A‑1: Stage‑1 CLI stub を Stage0 Rust ランナーから呼び出すブリッジ(DONE) + - 実装: `src/runner/stage1_bridge.rs` + `src/runner/mod.rs` に `maybe_run_stage1_cli_stub` を追加。 + - `NYASH_USE_STAGE1_CLI=1`(既定 OFF)かつ `NYASH_STAGE1_CLI_CHILD!=1` のときにだけ有効。 + - entry `.hako` は `STAGE1_CLI_ENTRY` / `HAKORUNE_STAGE1_ENTRY` で上書き可能(既定: `lang/src/runner/stage1_cli.hako`)。 + - 入力ファイル / モード: + - `STAGE1_EMIT_PROGRAM_JSON=1`: `hakorune emit program-json ` を子プロセスで実行。 + - `STAGE1_EMIT_MIR_JSON=1`: `STAGE1_PROGRAM_JSON` があれば `--from-program-json`、なければ `` から `emit mir-json`。 + - 上記どちらも無い場合: `hakorune run --backend `(backend は `STAGE1_BACKEND` か CLI の backend 設定)。 + - `NYASH_SCRIPT_ARGS_JSON` を拾って `--` 以降の script 引数も Stage‑1 側に転送。 + - 再入防止として子プロセスには `NYASH_STAGE1_CLI_CHILD=1` を付与(子側からは Rust ブリッジを素通り)。 +- A‑2: Stage‑1 CLI skeleton の責務を doc に固定(DONE) + - `lang/src/runner/stage1_cli.hako` で `Stage1Cli.emit_program_json/emit_mir_json/run_program_json/stage1_main` のシグネチャとトグルトポロジーを固定。 + - `docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md` に Rust 側ブリッジの振る舞いとトグル名(`NYASH_USE_STAGE1_CLI` / `STAGE1_EMIT_*` / `STAGE1_BACKEND` / `NYASH_STAGE1_CLI_CHILD`)を追記。 +- A‑3: 次ステップ(未着手) + - Stage‑1 CLI skeleton に Stage‑B/BuildBox/MirBuilder 呼び出しを順に実装し、「Program(JSON)/MIR(JSON) を selfhost 経由で emit できる」状態まで持っていく。 + - `tools/selfhost/run_stage1_cli.sh` から呼び出す selfhost パスと、Rust CLl からのブリッジパスの両方で JSON I/O 契約が同じになるように揃える。 -3. **.hako LoopSSA v2 実装(LoopForm v2 への追従)** - - Rust LoopForm v2 の規約(Carrier/Pinned / preheader/header/latch/exit)を `.hako` の LoopSSA に輸入。 - - Stage‑B Test2 / selfhost CLI の JSON→MIR 経路で MirVerifier 緑を目標にする。 +### B. Stage‑1 UsingResolver / LoopForm v2 ライン(Phase 25.1e 系フォロー) -4. **Selfhost / CLI 周辺のテスト整理** - - 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースに対して、 - 「Phase 25.1m でどこまで緑になったか」「どこから先が 25.1k 以降の仕事か」を tests / tools 側で見える化する。 +- B‑1: entry UsingResolver の Region+next_i 化とコメント整備(おおむね DONE) + - `lang/src/compiler/entry/using_resolver_box.hako` の 3 ループ(entries / JSON スキャン / modules_list)は Region+next_i 形に揃え済み。 + - 役割コメント: `resolve_for_source`(Stage‑B body_src を受けて prefix を返す)、`_collect_using_entries`(JSON スキャンして entries を集める)、`_build_module_map`(modules_list を map 化)を明記済み。 + - `HAKO_STAGEB_APPLY_USINGS=0` の時は prefix を空 string にしつつ、depth ガードだけは走らせる仕様もコメントで固定。 +- B‑2: UsingResolver 構造テスト(ループ形/PHI)の拡充(DONE) + - `src/tests/mir_stage1_using_resolver_verify.rs` に Region+next_i ループと early-exit JSON スキャンパターンの軽量テストを追加。 + - これらは cargo test 経路で常時緑を維持し、v2 quick スモークへの昇格は「実行時間とノイズを見ながら後続フェーズで検討」という扱いにする(docs にメモ済み)。 +- B‑3: pipeline_v2 UsingResolver との責務境界(DONE) + - `lang/src/compiler/pipeline_v2/using_resolver_box.hako` 冒頭に、「entry 側=ファイル I/O + using 収集」「pipeline_v2 側=modules_json 上の alias/path 解決」と役割メモを追加。 + - RegexFlow ベースの単一路ループは Region 化不要(stateful helper)とし、LoopForm v2 の観点からも「観測対象外」とする。 + +### C. Stage‑B / LoopSSA / Selfhost まわり(中期タスク) + +- C‑1: Stage‑B 再入ガード `env.set/2` の整理(据え置き) + - dev 専用 extern を Rust 側に追加するか、Stage‑B 箱側で state に寄せるかを決める必要があるが、現在はループ/PHI ラインを優先し保留。 + - このタスクに着手するときは「prod/CI 経路から完全に切り離した dev ガード」として設計する。 +- C‑2: .hako LoopSSA v2 実装(Rust LoopForm v2 への追従) + - Rust LoopForm v2 の Carrier/Pinned/BodyLocalInOut モデルを `.hako` の LoopSSA に輸入し、Stage‑B Test2 / selfhost CLI でも MirVerifier 緑を目指す中〜長期タスク。 + - 具体的な対象: `lang/src/compiler/builder/ssa/loopssa.hako` と Stage‑B/BreakFinder 周辺の region 化済みループ。 +- C‑3: Selfhost / CLI 周辺のテスト整理 + - 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースを tests/tools 側でタグ付け(quick/integration/selfhost)、Phase 25.1 の「どこまでが Rust 側」「どこからが Stage1 側」を見える化する。 --- @@ -268,44 +338,51 @@ 次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。 -## 2-? Stage‑B FuncScanner / LoopSnapshotMergeBox / MIR ロガー(in progress) +## 3. これからやるタスクのラフ一覧(25.1 / Stage‑1 系) -- StageBFuncScannerBox を `compiler_stageb.hako` 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶ構成にしている。 -- VM 側の Undefined value(BreakFinderBox._find_loops/2)は LoopForm v2 / continue + PHI 修正で解消済み。 -- FuncScannerBox.scan_all_boxes/1 については、Phase 25.2 で `LoopSnapshotMergeBox` を導入し、continue/break/exit スナップショットのマージを一元化したことで - 13 個の continue を含む複雑ループでも Undefined Value が出ないところまで到達しており、 - さらに `lang/src/compiler/tests/funcscanner_fib_min.hako` を使った最小ハーネスでも `NYASH_VM_VERIFY_MIR=1` で Undefined Value が再現しないところまで確認済み。 - - LoopSnapshotMergeBox 実装: `src/mir/phi_core/loop_snapshot_merge.rs` - - `merge_continue_for_header` / `merge_exit` / `optimize_same_value` / `sanitize_inputs` など、ヘッダ/exit PHI 用の入力統合ロジックを提供。 - - LoopBuilder / LoopFormBuilder は、この箱を経由して continue_merge / exit PHI を構成するようにリファクタ済み。 -- 代表テスト: - - ループ系: `mir_stageb_loop_break_continue::*` / `mir_loopform_exit_phi::*` / `mir_stageb_like_args_length::*` はすべて PASS。 - - 手書きループ: - - 基本ループ: sum=10(0+1+2+3+4)✅ - - break/continue を含むループ: sum=19, i=7 ✅ - - body-local 変数を含むループ: result=6, i=3 ✅(exit PHI で body-local を正しく統合) -- StageBFuncScannerBox.scan_all_boxes(src) は依然として fib サンプルに対して defs=[](Program(JSON) に `TestBox.fib` が載っていない)という問題が残っているため、 - 次の担当者は StageBFuncScannerBox.scan_all_boxes/1 と FuncScannerBox.scan_all_boxes/1 の MIR を比較しつつ、 - Stage‑B body 抽出 → StageBFuncScannerBox.scan_all_boxes → Program(JSON v0).defs 注入の導線自体を見直してほしい(LoopForm/PHI/スナップショット側は LoopSnapshotMergeBox で安定化済み)。 -- Phase 25.3 では、FuncScanner / Stage‑B ラインの観測専用として `.hako` から直接 MIR ログを埋め込める `__mir__` 構文を導入した。 - - 構文: `__mir__.log("label", v1, v2, ...)` / `__mir__.mark("label")` - - lowering: `MirInstruction::DebugLog { message: "label", values: [v1, v2, ...] }` に変換される(LoopForm/PHI には影響しない)。 - - 実行: `NYASH_MIR_DEBUG_LOG=1` のときだけ `[MIR-LOG] label: %id=value ...` を VM 側で出力し、それ以外は no-op。 - - 実装ポイント: `src/mir/builder/calls/build.rs` の `build_method_call_impl` で `__mir__` receiver を検出し、`try_build_mir_debug_call` で `DebugLog` 命令に directly lowering している。 - - これにより、FuncScannerBox.scan_all_boxes や StageBFuncScannerBox.scan_all_boxes のループ頭や `box` 検出位置にラベルを打ち、 - VM 実行ログと合わせて「どの経路で未初期化の値が残っているか」を .hako 側から追いやすくなった。 - - 併せて `MirBuilder::handle_me_method_call` を `MeCallPolicyBox` で箱化し、me-call のフォールバックを静的メソッド降下に統一した。 - - `Box.method/Arity` lowered 関数が存在する場合は従来どおり `me` を先頭引数にした Global call。 - - 存在しない場合は `handle_static_method_call(cls, method, arguments)` に委譲し、static helper(FuncScannerBox._parse_params / _strip_comments など)呼び出しとして扱う。 - - これにより static box 文脈で「実体のない me を receiver にした Method call」が生成される経路が閉じられ、FuncScannerBox._scan_methods/4 系の UndefinedValue は Rust 側で構造的に防止されている。 +ここから先は「まず設計と箱分割を書いてから実装」という方針で進めるタスク群だよ。 +### A. Rust 層解析(LoopForm v2 / JSON v0 / Stage‑1 観測) -## 3. Static Box フィールド仕様ドキュメント(完了) +- A-1: LoopForm v2 / LoopSnapshotMerge の入口確認 + - `src/mir/loop_builder.rs` / `src/mir/phi_core/loopform_builder.rs` / `src/mir/phi_core/loop_snapshot_merge.rs` を「Stage‑1 から見た導線」として読み直し、どのレイヤで Carrier / Pinned / BodyLocalInOut が決まるかを short メモ化する。 +- A-2: JSON v0 → MIR ブリッジの導線整理 + - `src/runner/json_v0_bridge/lowering/`(特に `loop_.rs`)を通して、Program(JSON v0).body / defs.body(Block) が LoopForm v2 までどう運ばれるかを図として docs に落とす。 +- A-3: Stage‑1 UsingResolver の MIR 観測 + - 既存テスト `src/tests/mir_stage1_using_resolver_verify.rs` の MIR dump を元に、「どのループが Region+next_i 化候補か」「既に問題なく LoopForm に乗っている場所はどこか」を箇条書きで整理する。 -- docs/reference/language/LANGUAGE_REFERENCE_2025.md: 3.4 Static Boxパターンに「static box 内のフィールドはすべて static フィールドとして扱われる」旨を明記した。 -- 言語仕様としては、static box の中では `PI: FloatBox` のような宣言で十分であり、追加の `static` キーワードは不要であることをドキュメント上で固定。 -- 次タスク候補(Claude Code 向け): static box / box のフィールド挙動に関するサンプルとテスト(VM/JSON v0 両方)の整理、および static box 上の `me` セマンティクス(25.1p DebugLog フェーズと連動)の仕様化。 +### B. Stage‑1 UsingResolver 箱化・ループ整理(.hako 側) +- B-1: Stage1UsingResolverBox の責務分割設計 + - `lang/src/compiler/entry/using_resolver_box.hako` を、collect_entries / modules_map / file_read などの小さいロジック箱に概念的に分割し、「どの箱が何を責務とするか」を docs に書く(実際のファイル分割は後段)。 +- B-2: すべてのループを Region+next_i 形に揃える計画 + - entry の UsingResolver 内に残っている「pos++/continue 多発型」ループを洗い出し、Region+next_i 形にどう書き換えるかを phase-25.1 docs に追記する。 + - 目的: LoopForm v2 / PHI から見たときに「1 region / 1 backedge / 明確な次位置決定」という形に統一する。 + - 状況メモ: entry 側 3 ループ(entries/JSON scan/modules_list)は Region+next_i 化済み。残りなし。 +- B-3: pipeline_v2 UsingResolver との役割分担 + - `lang/src/compiler/pipeline_v2/using_resolver_box.hako` と entry/Stage1UsingResolverBox のどちらが「テキスト / JSON / modules_map」のどこまでを担当するかを整理し、責務境界をドキュメントに固定する。 + - 状況メモ: pipeline_v2 側は modules_json の alias 解決のみ(RegexFlow.find_from の単一路ループ)。Region 化不要として据え置き、境界だけ明記。 +- B-4: 構造テストの追加計画 + - Region+next_i パターンの軽量 SSA テスト(すでに 1 本追加済み)を基準に、もう 1〜2 本、UsingResolver ソースに近いパターン(JSON スキャン+early-exit)を追加する方針を決める。 + +### C. Stage‑1 CLI / program-json / mir-json Self‑Host 準備 + +- C-1: Stage‑1 CLI インターフェースの設計メモ + - `.hako → Program(JSON)` / `Program(JSON) → MIR(JSON)` / `MIR(JSON) → 実行` を Stage‑1 側からどう呼び出すか(関数名・引数レベル)を docs に先に書く。 +- C-2: Stage‑B → Stage‑1 データフロー図 + - `compiler_stageb.hako` → Program(JSON v0) → Stage‑1 UsingResolver → MirBuilder までのパイプラインを Phase‑25.1 ドキュメントに 1 枚の図としてまとめる。 +- C-3: Rust CLI 側ブリッジの最小ガード案 + - Stage0/Rust CLI は「Program(JSON/MIR(JSON) を受け取り VM/LLVM に流すだけ」に縮退させる方針と、既定 OFF トグル(selfhost 入口)をどう切るかを設計メモとして追加。 + +### D. 将来フェーズ向けメモ(variable_map 決定化ライン) + +- D-1: MirBuilder::variable_map / BoxCompilationContext::variable_map BTreeMap 化案 + - どの構造を HashMap→BTreeMap 化するか、どのテスト(`mir_funcscanner_skip_ws_vm_debug_flaky` など)で決定性を確認するかを Phase‑25.x の設計メモとして書いておく。 +- D-2: dev 専用 / flaky テストの扱い方針 + - どのテストが dev ignore(手動実行用)で、いつ/何を満たしたら常時有効に戻すかを、このファイルと関連 README に明確に残す。 + +このセクションは「すぐ実装する TODO ではなく、25.1〜25.x ラインで踏むべき設計タスク一覧」として使う予定だよ。 +(静的 me-call 修正の参考メモはここに残しつつ、別セクションは整理済み) ## 3. Phase 25.1q — LoopForm Front Unification(DONE / follow-up 別タスクへ) diff --git a/docs/development/roadmap/phases/phase-25.1/README.md b/docs/development/roadmap/phases/phase-25.1/README.md index f1fa4a11..06bed12e 100644 --- a/docs/development/roadmap/phases/phase-25.1/README.md +++ b/docs/development/roadmap/phases/phase-25.1/README.md @@ -43,9 +43,9 @@ Status: design+partial implementation(Stage1 ビルド導線の初期版まで - Rust VM/LLVM のコア(MIR インタプリタ/コード生成)の提供。 - Stage1 で AOT されたコア関数(後述)を呼び出すランチャ。 -**禁止/抑制:** -- パーサ高レイヤ/Stage‑B/MirBuilder/AotPrep/numeric core のロジックを Rust 側に新規追加しない。 -- 新しい Box 実装やランタイム機能を Rust に持ち込まない(Phase 25 Rust Freeze を継続)。 +**禁止/抑制(緩和版):** +- パーサ高レイヤ/Stage‑B/MirBuilder/AotPrep/numeric core の**意味論そのもの**を Rust 側に新規実装しない(Self‑Host の責務)。 +- 新しい Box 実装や高レベルランタイム機能を Rust に持ち込むのは原則避ける(ただし Stage‑1 ブリッジやエラーログ改善など、導線・可観測性に必要な最小限の変更は許可)。 ### Stage1 — Hakorune Selfhost Binary @@ -157,6 +157,11 @@ Status: design+partial implementation(Stage1 ビルド導線の初期版まで - [ ] Stage0→Stage1→Stage1' のビルドシーケンスを文章で定義(どの組み合わせで自己一致チェックを行うか)。 - [ ] 「普段使うのは Stage1」「問題発生時に Stage0 から再生成」という運用パターンを docs に記載。 +### E. Stage‑1 UsingResolver / LoopForm v2 対応(設計) + +- [x] 設計ドラフトを追加(`stage1-usingresolver-loopform.md`)。Region+next_i 形ループと Carrier/Pinned 対応の指針を明文化。 +- [x] Rust 側の観測結果を反映し、具体的なリライト手順とテスト項目を更新する(JSON→LoopForm v2 導線/Stage‑B→Stage‑1 データフローのテキスト図を追記)。 + ## 実装チェックリスト(25.1 実行順案) ### 1. バイナリ命名と役割の明確化 diff --git a/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md b/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md index af183a89..94e67bde 100644 --- a/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md +++ b/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md @@ -112,6 +112,7 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha ### ループ洗い出しメモ(entry / pipeline_v2) - entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako) - Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割 + - 追加テスト: modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。 - 残り: なし(現状の 3 ループは all Region 形) - pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako) - 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。 diff --git a/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md b/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md index 3470b1ef..d1d4c9b7 100644 --- a/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md +++ b/docs/development/roadmap/phases/phase-25.3-funcscanner/README.md @@ -1,5 +1,7 @@ # Phase 25.3 — FuncScanner / Stage‑B defs 安定化 +Status: 完了(Stage‑B fib defs canary 緑/2025-11 時点) + ## スコープ / ゴール - 対象レイヤ @@ -22,6 +24,30 @@ - Loop/PHI の意味論は **LoopFormBuilder + LoopSnapshotMergeBox** に完全委譲したまま、 - FuncScanner / Stage‑B は「テキストスキャン+JSON 組み立て」の箱として綺麗に分離された状態にする。 +## 完了ステータス(2024-11-20 時点) + +- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` が緑(Phase 25.3 の完了条件)。 + - `defs` に `TestBox.fib` / `Main.main` が入り、`TestBox.fib.body.body[*]` に `Loop` ノードが含まれる状態を固定。 +- Stage‑B 本線の整理: + - `StageBDriverBox.main`: main 本文は `{…}` で包んだ block パーサ優先で JSON 化し、defs 側は `{"type":"Block","body":[…]}` に構造化して注入。 + - `StageBFuncScannerBox._scan_methods`: block パーサ優先に揃え、Program パーサは `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1` の opt-in 時だけ使う安全側トグルに変更(skip_ws 崩れの再発防止)。 +- Rust 層は無改変(LoopForm v2 / LoopSnapshotMergeBox の SSOT をそのまま利用)。 + +## JSON v0 フロントと AOT ルートの契約(Phase 25.3 時点) + +- Program(JSON v0) の形: + - ルートは常に `{"version":0,"kind":"Program","body":[...]}` + - defs は `\"defs\":[{ \"name\", \"params\", \"box\", \"body\" }]` を追加する形で注入する。 + - Stage‑B / FuncScanner 経由の `defs[*].body` は必ず `{\"type\":\"Block\",\"body\":[Stmt...]}` でラップし、AOT/VM 側からは「通常の Block ノード」として読めるようにする。 +- フロント/バックエンドの責務分離: + - Stage‑B / FuncScanner: `.hako` テキスト → Program(JSON v0) まで(ループ/PHI の意味論は持たない)。 + - Rust 側 LoopForm v2 / JSON v0 Bridge: Program(JSON v0) → MIR/AOT まで(Loop/PHI/SSA の SSOT)。 +- Stage‑B トグル整理(抜粋): + - `HAKO_STAGEB_FUNC_SCAN=1`(既定): defs を常に埋める。`0` で Stage‑B からの defs 注入を無効化。 + - `HAKO_STAGEB_PROGRAM_PARSE_FALLBACK=1`: main 本文で Program パーサ fallback を有効化(既定は OFF、block パーサ優先)。 + - `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1`: FuncScanner 側で Program パーサ fallback を有効化(既定は OFF)。 + 通常の開発・CI ではいずれも OFF にしておき、VM/パーサ調査時だけ opt‑in で使う想定。 + ## すでに前提としている状態 - LoopForm v2 / PHI / snapshot: @@ -118,27 +144,28 @@ FuncScanner / Stage‑B のデバッグ時には、`scan_all_boxes` のループ - ここでの主なバグは「SSA ではなく、Stage‑B が fib ソースから defs を組み立てきれていないこと」なので、 ループ構造や LoopForm には手を入れず、Stage‑B 側のテキスト処理と defs 生成パスを中心に見る。 -### 4. fib defs canary & ドキュメント更新 +### 4. fib defs canary & ドキュメント更新(完了) - 対象: - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` - `docs/development/roadmap/phases/phase-25.1q/README.md` - `CURRENT_TASK.md` -- やること: - - canary スクリプトで: - - `Program.kind == "Program"` - - `defs` に `TestBox.fib` が存在すること。 - - `TestBox.fib.body` に `Loop` ノードが含まれること。 - を満たした状態で `rc=0` になるまで確認する。 - - Phase 25.1q / 25.2 の README には、 - - 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」 - - 「Phase 25.3 で FuncScanner / Stage‑B defs もこの土台の上に載せた」 - というつながりを 1〜2 行で追記する。 - - `CURRENT_TASK.md` には: - - Phase 25.3 のタスク完了状況(FuncScanner fib / Stage‑B fib canary 緑)と、 - - その後に繋がる Stage‑1 UsingResolver / Stage‑1 CLI self‑host ラインへのブリッジ - を短く整理しておく。 +今回の結果: +- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` は `rc=0` で安定緑。 +- canary 内部で確認している条件: + - `Program.kind == "Program"` + - `defs` に `TestBox.fib` / `Main.main` が含まれていること。 + - `TestBox.fib.body` の直下(`body.body[*]`)に `Loop` ノードが最低 1 つ含まれていること。 +- これにより、FuncScanner / Stage‑B 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox の上で構造的に安定したとみなせる。 + +ドキュメント側: +- Phase 25.1q / 25.2 の README には、 + - 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」 + - 「Phase 25.3 で FuncScanner / Stage‑B defs もこの土台の上に載せた」 + という関係を 1〜2 行で追記済み。 +- `CURRENT_TASK.md` では Phase 25.3 を「Stage‑B fib defs canary 緑」まで完了したフェーズとして整理し、 + 次フェーズを Stage‑1 UsingResolver ループ整理 / Stage‑1 CLI program-json selfhost 導線に設定した。 ### 5. mir_funcscanner_skip_ws 系テストの扱い(flaky → dev 専用) diff --git a/docs/development/roadmap/phases/phase-25/README.md b/docs/development/roadmap/phases/phase-25/README.md index ed0246da..1c9b1a54 100644 --- a/docs/development/roadmap/phases/phase-25/README.md +++ b/docs/development/roadmap/phases/phase-25/README.md @@ -69,7 +69,7 @@ Related docs: - `IntArrayCore`(数値一次元配列コア) - `MatI64`(行列箱・i64版) などを、「Rust プラグイン実装」ではなく **Hakorune 実装+ごく薄い intrinsic** に置き換えるための設計ロードマップを固める。 -- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Freeze Policy(Self‑Host First)」を Phase 25 で具体化する。 +- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Minimal Policy(Self‑Host First, but not Frozen)」として Phase 25 で具体化する。 ## レイヤー方針(Ring0 / Ring1) @@ -82,8 +82,8 @@ Related docs: - **汎用 intrinsic** のみ提供(例: メモリ確保・生ポインタload/store・基本的な memcpy 等) **禁止 / 抑制:** -- 新しい Box 種類(IntArrayCore / MatI64 / StringBuilder 等)を Rust 側に増やさない。 -- 新しい最適化ロジック・言語ルール・Box メソッド実装を Rust に追加しない(AGENTS.md 5.2 Rust Freeze Policy に準拠)。 +- 新しい Box 種類(IntArrayCore / MatI64 / StringBuilder 等)の**本体ロジック**を Rust 側に増やさない(型安全な intrinsic のみに留める)。 +- 新しい最適化ロジック・言語ルール・Box メソッド実装を Rust に追加しない(AGENTS.md 5.2 Rust Minimal Policy に準拠)。 ### Ring1(Hakorune / Nyash ― System サブセット) @@ -112,11 +112,11 @@ Related docs: Phase 25 は「設計とロードマップの確定」が主目的。実装・移行作業自体は後続フェーズ(22.x/26.x など)で分割実施する。 -### 1) Rust Freeze の明文化とチェックリスト +### 1) Rust Minimal Policy の明文化とチェックリスト -- 既存の「Rust Freeze Policy(Self‑Host First)」を、ランタイム/箱/数値系に特化して再整理: +- 既存の「Rust Freeze Policy(Self‑Host First)」を、「Self‑Host を支える最小+必要な整備は許可する」Rust Minimal Policy として再整理: - 新規 Box / ランタイム機能は Rust ではなく .hako で実装する。 - - Rust 変更は「最小の intrinsic 追加」か「バグ修正」に限定。 + - Rust 変更は「最小の intrinsic 追加」「Stage‑1/Stage‑B ブリッジの改善」「エラーログ・可観測性の向上」か「バグ修正」に限定。 - PR / フェーズ用チェックリスト案を作成: - [ ] この変更は Ring0 の責務か?(VM/allocator/LLVM/OS FFI のみ) - [ ] 新しい Box/アルゴリズムを Rust に追加していないか? diff --git a/docs/development/runtime/cli-hakorune-stage1.md b/docs/development/runtime/cli-hakorune-stage1.md index ba98da96..ee905147 100644 --- a/docs/development/runtime/cli-hakorune-stage1.md +++ b/docs/development/runtime/cli-hakorune-stage1.md @@ -1,6 +1,6 @@ # Stage1 Hakorune CLI Design(Proposal) -Status: design-only(Phase 25.1 時点では仕様策定と導線の整理まで) +Status: design-only + Stage0 stub 実装済み(Phase 25.1 時点では仕様策定と導線の整理まで。selfhost EXE 本体は未実装) ## ゴール @@ -15,6 +15,8 @@ Status: design-only(Phase 25.1 時点では仕様策定と導線の整理ま - 役割: プロセス起動・VM/LLVM コア・ny-llvmc 呼び出しなどの「Ring0」。 - Ny 側からは `env.mirbuilder.emit` / `env.codegen.emit_object` / `env.codegen.link_object` などの extern 名で見える。 - Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層(C-ABI/extern)として利用することを前提とする。 + - Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI(`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する + ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE(`target/selfhost/hakorune`)はまだ設計段階。 - Stage1(Hakorune selfhost CLI) - 実体: `target/selfhost/hakorune`(Phase 25.1 では Ny Executor プロトタイプ)。 - 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。 @@ -64,6 +66,10 @@ Phase 25.1a では、**`emit program-json` / `emit mir-json` / `build exe` の 3 - `--quiet` でログ抑制、`-o/--out` で出力 EXE パスを指定可能。C-API トグル(`NYASH_LLVM_USE_CAPI`, `HAKO_V1_EXTERN_PROVIDER_C_ABI`)が無効な場合は fail-fast。 - Stage1 バイナリ(`target/selfhost/hakorune`)を直接叩く際は `NYASH_NYRT_SILENT_RESULT=1` を付与し、stdout に JSON だけを流す運用を徹底する(`tools/selfhost/run_stage1_cli.sh` が環境セットとバイナリ検出を担当)。 +#### デバッグ Tips(Phase 25.1a) +- `STAGE1_CLI_DEBUG=1` を付けると `.hako` 側 `stage1_main` の ENTRY ログが出る。Rust ブリッジが正しく Stage1 stub を呼んでいるか確認する際に使う。 +- `NYASH_CLI_VERBOSE=1` か `2` を付けると Rust 側 bridge (`stage1_bridge.rs`) が子プロセス起動ログを出力する。 + ## `run` コマンド ```text diff --git a/docs/guides/testing-guide.md b/docs/guides/testing-guide.md index 29b51338..b0a0e8a9 100644 --- a/docs/guides/testing-guide.md +++ b/docs/guides/testing-guide.md @@ -98,6 +98,67 @@ python3 tools/phi_trace_check.py --file tmp/phi_trace.jsonl --summary - `tools/smokes/phi_trace_local.sh`(ビルド→サンプル実行→チェックを一括) - `tools/smokes/v2/run.sh --profile quick|integration` で代表スモークを実行 +## MIR デバッグの入口まとめ + +### 1. CLI レベルの MIR ダンプ + +- ソースから直接 MIR を確認: + - `./target/release/nyash --dump-mir path/to/program.hako` +- VM 実行経路で MIR を一緒に吐く: + - `NYASH_VM_DUMP_MIR=1 ./target/release/nyash path/to/program.hako` +- JSON で詳細解析したい場合: + - `./target/release/nyash --emit-mir-json mir.json path/to/program.hako` + - 例: `jq '.functions[0].blocks' mir.json` でブロック構造を確認。 + +### 2. Scope / Loop ヒント(NYASH_MIR_HINTS) + +- 環境変数でヒント出力を制御: + - `NYASH_MIR_HINTS="|..."` +- 例: + - `NYASH_MIR_HINTS="trace|all"`(stderr へ全ヒント) + - `NYASH_MIR_HINTS="jsonl=tmp/hints.jsonl|loop"`(ループ関連のみ JSONL 出力) +- 詳細: `docs/guides/scopebox.md`, `src/mir/hints.rs`。 + +### 3. __mir__ ロガー(.hako から仕込む MIR ログ) + +- 目的: + - `.hako` 側のループや SSA まわりを「MIR レベル」で観測するための dev 専用フック。 + - 実行意味論には影響しない(Effect::DebugLog のみ)。 +- 構文(.hako 内): + + ```hako + __mir__.log("label", v1, v2, ...) + __mir__.mark("label") + ``` + + - 第1引数は String リテラル必須(それ以外は通常の呼び出し扱い)。 + - `log` は第2引数以降の式を評価し、その ValueId 群を記録。 + - `mark` はラベルだけのマーカー。 + - 戻り値は Void 定数扱いのため、式コンテキストに書いても型崩れしない。 + +- 実行時の有効化: + - `NYASH_MIR_DEBUG_LOG=1 ./target/release/nyash path/to/program.hako` + - VM の MIR interpreter が次のようなログを stderr に出力: + + ```text + [MIR-LOG] label: %10=123 %11="foo" + ``` + +- 実装位置: + - lowering: `src/mir/builder/calls/build.rs` の `try_build_mir_debug_call`(receiver が `__mir__` のときに `MirInstruction::DebugLog` を挿入)。 + - 実行: `src/backend/mir_interpreter/handlers/mod.rs`(`NYASH_MIR_DEBUG_LOG=1` のときだけログを出す)。 + +- 利用例(ループ観測の定番パターン): + + ```hako + loop(i < n) { + __mir__.log("loop/head", i, n) + ... + } + __mir__.mark("loop/exit") + ``` + + - FuncScanner / Stage‑B では、skip_whitespace や scan_all_boxes のループ頭・出口に挿入して、ValueId と値の流れを追跡する用途で使用している。 ## 🔌 **プラグインテスター(BID-FFI診断ツール)** ```bash diff --git a/lang/src/compiler/entry/compiler_stageb.hako b/lang/src/compiler/entry/compiler_stageb.hako index 00c79e87..6e3eea99 100644 --- a/lang/src/compiler/entry/compiler_stageb.hako +++ b/lang/src/compiler/entry/compiler_stageb.hako @@ -795,8 +795,8 @@ static box StageBFuncScannerBox { } local body = s.substring(open_idx + 1, close_idx) + // Include Main.main as well so defs covers entry + helpers local skip_main = 0 - if box_name == "Main" { skip_main = 1 } local include_me = 0 if box_name != "Main" { include_me = 1 } local defs_box = StageBFuncScannerBox._scan_methods(body, box_name, skip_main, include_me) @@ -979,16 +979,29 @@ static box StageBFuncScannerBox { method_body = FuncScannerBox._strip_comments(method_body) method_body = FuncScannerBox._trim(method_body) - // Parse method body to JSON (statement list) using block parser + // Parse method body to JSON (statement list) + // Policy: block parser first (braced) to avoid VM dispatch misrouting, then Program parser as backup. local body_json = null if method_body.length() > 0 { - local p = new ParserBox() - p.stage3_enable(1) - local block_src = "{" + method_body + "}" - local block_res = p.parse_block2(block_src, 0) - local at = block_res.lastIndexOf("@") - if at >= 0 { - body_json = block_res.substring(0, at) + // 1st: block parser with explicit braces (LoopForm-friendly, avoids ctx misbinding) + { + local p = new ParserBox() + p.stage3_enable(1) + local block_src = "{" + method_body + "}" + local block_res = p.parse_block2(block_src, 0) + local at = block_res.lastIndexOf("@") + if at >= 0 { body_json = block_res.substring(0, at) } + } + // 2nd: program parser trims body JSON if block path produced nothing (opt-in) + if body_json == null || body_json == "" || body_json == "[]" { + local allow_prog = env.get("HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK") + if allow_prog != null && ("" + allow_prog) == "1" { + local p2 = new ParserBox() + p2.stage3_enable(1) + local prog_json = p2.parse_program2(method_body) + local extracted = me._extract_body_from_program(prog_json) + if extracted.length() > 0 { body_json = extracted } + } } } @@ -1017,6 +1030,49 @@ static box StageBFuncScannerBox { return 0 } + // Helper: extract Program.body array from Program(JSON) string + _extract_body_from_program(prog_json) { + if prog_json == null { return "" } + local s = "" + prog_json + local n = s.length() + local head = "\"body\":" + local hlen = head.length() + local start = -1 + { + local i = 0 + loop(i + hlen <= n) { + if s.substring(i, i + hlen) == head { start = i + hlen break } + i = i + 1 + } + } + if start < 0 { return "" } + + // Find '[' after "body": + local open = -1 + { + local j = start + loop(j < n) { + local ch = s.substring(j, j + 1) + if ch == "[" { open = j break } + j = j + 1 + } + } + if open < 0 { return "" } + + local depth = 0 + local k = open + loop(k < n) { + local ch2 = s.substring(k, k + 1) + if ch2 == "[" { depth = depth + 1 } + else if ch2 == "]" { + depth = depth - 1 + if depth == 0 { return s.substring(open, k + 1) } + } + k = k + 1 + } + return "" + } + // Helper: 空白文字スキップ(FuncScannerBox に委譲) _skip_whitespace(s, idx) { return FuncScannerBox.skip_whitespace(s, idx) @@ -1151,14 +1207,30 @@ static box StageBDriverBox { // 6) Parse and emit Stage‑1 JSON v0 (Program) // Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。 // 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。 - // Stage‑B/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ - // を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)。 - local block_res = p.parse_block2(body_src, 0) + // Stage‑B/selfhost: body_src は Main.main 内のブロック本文(外側の {} は含まない)。 + // 安定性向上のため Program パーサを優先し、空の場合のみ block パーサで再度包んで解釈する。 local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}" { - local at = block_res.lastIndexOf("@") - if at >= 0 { - local body_json = block_res.substring(0, at) + local body_json = null + + // Prefer block parser with explicit braces to avoid VM dispatch drift seen on program parser. + if body_src != null { + local block_src = "{" + body_src + "}" + local block_res = p.parse_block2(block_src, 0) + local at = block_res.lastIndexOf("@") + if at >= 0 { body_json = block_res.substring(0, at) } + } + + // Optional: enable program parser fallback only when explicitly requested + if (body_json == null || body_json == "" || body_json == "[]") { + local allow_prog = env.get("HAKO_STAGEB_PROGRAM_PARSE_FALLBACK") + if allow_prog != null && ("" + allow_prog) == "1" { + local prog_json = p.parse_program2(body_src) + body_json = StageBFuncScannerBox._extract_body_from_program(prog_json) + } + } + + if body_json != null && body_json != "" { ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}" } } @@ -1246,6 +1318,8 @@ static box StageBDriverBox { local mparams = def.get("params") local mbody = "" + def.get("body_json") local mbox = "" + def.get("box") + // Wrap body in Block node to align with Program.body shape expectations + local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}" // Build params array JSON local params_arr = "[" local pi = 0 @@ -1257,7 +1331,7 @@ static box StageBDriverBox { } params_arr = params_arr + "]" if mi > 0 { defs_json = defs_json + "," } - defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + mbody + ",\"box\":\"" + mbox + "\"}" + defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}" mi = mi + 1 } defs_json = defs_json + "]" diff --git a/lang/src/compiler/entry/using_resolver_box.hako b/lang/src/compiler/entry/using_resolver_box.hako index 003bdeee..ca13d336 100644 --- a/lang/src/compiler/entry/using_resolver_box.hako +++ b/lang/src/compiler/entry/using_resolver_box.hako @@ -3,6 +3,9 @@ // - Collect line-based using declarations via UsingCollectorBox. // - Resolve namespace targets against `nyash.toml` `[modules]` entries (fed via env). // - Read the referenced .hako files and return a concatenated prefix for Stage‑B. +// Structure tags: +// [Region] JSON/ArrayBox/MapBox ループはすべて Region+next_i 形に統一して SSA を安定化。 +// [LoopForm] carrier=pinned を明示し、continue/break を next_i で合流させる。 // // Env requirements: // - HAKO_STAGEB_MODULES_LIST: `name=path` entries joined by "|||". @@ -13,6 +16,10 @@ using lang.compiler.parser.scan.parser_common_utils_box as ParserCommonUtilsBox using selfhost.shared.json.utils.json_frag as JsonFragBox static box Stage1UsingResolverBox { + // Entrypoint: collect `using` entries and stitch a prefix. + // - HAKO_STAGEB_APPLY_USINGS=0 → prefix ""(defs/body には何も足さない) + // - Depth guard prevents recursive self-calls from cascading. + // [Region] entries 走査は pos/next_pos で一意に前進。pinned: entries/seen, carrier: i。 resolve_for_source(src) { // Shallow recursion guard to prevent accidental self-recursion in Stage‑1 using resolver. { @@ -24,50 +31,88 @@ static box Stage1UsingResolverBox { env.set("HAKO_STAGEB_USING_DEPTH", "1") } - if src == null { return "" } - local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS") - if apply_flag != null && ("" + apply_flag) == "0" { return "" } - - local entries = me._collect_using_entries(src) - if entries == null || entries.length() == 0 { return "" } - - local modules = me._build_module_map() - local seen = new MapBox() local prefix = "" + if src != null { + local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS") + // apply_flag==0 なら prefix は空のまま返す(Stage‑B defs/body に変更なし) + if apply_flag == null || ("" + apply_flag) != "0" { + local entries = me._collect_using_entries(src) + if entries != null && entries.length() > 0 { + local modules = me._build_module_map() + local seen = new MapBox() - local i = 0 - local n = entries.length() - loop(i < n) { - local entry = entries.get(i) - local name = "" + entry.get("name") - if name == "" { i = i + 1 continue } + local i = 0 + local n = entries.length() + loop(i < n) { + // Region+next_i 形で多経路を末尾合流させる(SSA/PHI 安定化) + local next_i = i + 1 + local entry = entries.get(i) + local name = "" + entry.get("name") - local path = entry.get("path") - if path == null || ("" + path) == "" { - path = me._lookup_module_path(modules, name) - } else { - path = "" + path + // 早期スキップ条件をまとめて evaluate + local should_emit = 1 + if name == "" { should_emit = 0 } + + // path 解決(明示 path 優先、無ければ modules マップ) + local path = null + if should_emit == 1 { + path = entry.get("path") + if path == null || ("" + path) == "" { + path = me._lookup_module_path(modules, name) + } else { + path = "" + path + } + if path == null || path == "" { should_emit = 0 } + } + + // abs path 化 + local abs_path = null + if should_emit == 1 { + abs_path = me._abs_path(path) + if abs_path == null || abs_path == "" { should_emit = 0 } + } + + // 重複 guard + if should_emit == 1 { + if seen.get(abs_path) == "1" { should_emit = 0 } + } + + // ファイル読み込み + if should_emit == 1 { + local code = me._read_file(abs_path) + if code == null { should_emit = 0 } + if should_emit == 1 { + seen.set(abs_path, "1") + prefix = prefix + "\n" + code + "\n" + } + } + + i = next_i + } + } } - if path == null || path == "" { i = i + 1 continue } - - local abs_path = me._abs_path(path) - if abs_path == null || abs_path == "" { i = i + 1 continue } - if seen.get(abs_path) == "1" { i = i + 1 continue } - - local code = me._read_file(abs_path) - if code == null { i = i + 1 continue } - - seen.set(abs_path, "1") - prefix = prefix + "\n" + code + "\n" - i = i + 1 } - // Clear depth guard before returning + // Clear depth guard before returning(適用なしでも guard を戻す) env.set("HAKO_STAGEB_USING_DEPTH", "0") return prefix } + // Program(JSON v0) に対する using 解決(IOなし、SSOT委譲)。 + // - modules_json: nyash.toml [modules] 相当の JSON + // - using_entries_json: UsingCollectorBox.collect の生出力 + // - ctx: resolve_ssot_box 向けのヒント(modules/cwd/using_paths など) + resolve_for_program_json(program_json, modules_json, using_entries_json, ctx) { + using hako.using.resolve.ssot as UsingResolveSSOTBox + // MVP: IO せずに SSOT が shape を整えるだけ。現状は透過返し。 + local resolved = UsingResolveSSOTBox.resolve_modules(modules_json, using_entries_json, ctx) + // 返すものは modules_json をそのまま(今は純粋パス)。将来 prefix/string を付与する。 + return resolved + } + // Collect entries from UsingCollectorBox JSON output. + // Responsibility: JSON を配列にパースするだけ(path 解決やファイル読み込みは呼び元)。 + // [Region] JSON スキャンは pos/next_pos を明示し、early-exit も next_pos=n で一本化。 _collect_using_entries(src) { local json = UsingCollectorBox.collect(src) if json == null || json == "" || json == "[]" { return null } @@ -75,27 +120,36 @@ static box Stage1UsingResolverBox { local pos = 0 local n = json.length() loop(pos < n) { + // Region+next_pos 形(break を next_pos=n で表現) + local next_pos = n local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos) - if name_idx < 0 { break } - local name = JsonFragBox.read_string_after(json, name_idx + 7) - local obj_end = JsonFragBox.index_of_from(json, "}", name_idx) - if obj_end < 0 { obj_end = n } + if name_idx < 0 { + next_pos = n + } else { + local name = JsonFragBox.read_string_after(json, name_idx + 7) + local obj_end = JsonFragBox.index_of_from(json, "}", name_idx) + if obj_end < 0 { obj_end = n } - local path = null - local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx) - if path_idx >= 0 && path_idx < obj_end { - path = JsonFragBox.read_string_after(json, path_idx + 7) + local path = null + local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx) + if path_idx >= 0 && path_idx < obj_end { + path = JsonFragBox.read_string_after(json, path_idx + 7) + } + + local entry = new MapBox() + entry.set("name", name) + if path != null { entry.set("path", path) } + out.push(entry) + next_pos = obj_end + 1 } - - local entry = new MapBox() - entry.set("name", name) - if path != null { entry.set("path", path) } - out.push(entry) - pos = obj_end + 1 + pos = next_pos } return out } + // Build modules map from env `HAKO_STAGEB_MODULES_LIST` ("name=path" joined by "|||"). + // Responsibility: env を MapBox に積むだけ。存在しない/空なら空 Map を返す。 + // [Region] delim 走査も start/next_start で一本化し、continue/break を排除した形に固定。 _build_module_map() { local map = new MapBox() local raw = env.get("HAKO_STAGEB_MODULES_LIST") @@ -105,13 +159,20 @@ static box Stage1UsingResolverBox { local delim = "|||" local start = 0 - loop(true) { + local cont = 1 + loop(cont == 1) { + local next_start = str.length() local next = ParserCommonUtilsBox.index_of(str, start, delim) local seg = "" - if next >= 0 { seg = str.substring(start, next) } else { seg = str.substring(start, str.length()) } + if next >= 0 { + seg = str.substring(start, next) + next_start = next + delim.length() + } else { + seg = str.substring(start, str.length()) + cont = 0 + } me._push_module_entry(map, seg) - if next < 0 { break } - start = next + delim.length() + start = next_start } return map } @@ -152,7 +213,14 @@ static box Stage1UsingResolverBox { if path == null { return null } @fb = new FileBox() @ok = fb.open(path, "r") - if ok != 1 { return null } + if ok != 1 { + // Dev note: keep quiet by default; enable verbose by setting HAKO_STAGEB_USING_DEBUG=1. + local dbg = env.get("HAKO_STAGEB_USING_DEBUG") + if dbg != null && ("" + dbg) == "1" { + print("[stageb/using_resolver] failed to open file: " + path) + } + return null + } @content = fb.read() fb.close() return content diff --git a/lang/src/compiler/parser/parser_box.hako b/lang/src/compiler/parser/parser_box.hako index f4bbeeed..984483b9 100644 --- a/lang/src/compiler/parser/parser_box.hako +++ b/lang/src/compiler/parser/parser_box.hako @@ -51,11 +51,14 @@ box ParserBox { // === JSON utilities === esc_json(s) { + // Defensive: accept null/void and normalize to string once + if s == null { return "" } + local str = "" + s local out = "" local i = 0 - local n = s.length() + local n = str.length() loop(i < n) { - local ch = s.substring(i, i+1) + local ch = str.substring(i, i+1) if ch == "\\" { out = out + "\\\\" } else { if ch == "\"" { out = out + "\\\"" } else { out = out + ch } } diff --git a/lang/src/compiler/parser/scan/parser_string_utils_box.hako b/lang/src/compiler/parser/scan/parser_string_utils_box.hako index b82f01fe..74bc5b8c 100644 --- a/lang/src/compiler/parser/scan/parser_string_utils_box.hako +++ b/lang/src/compiler/parser/scan/parser_string_utils_box.hako @@ -1,20 +1,116 @@ // Moved from apps/selfhost-compiler/boxes/parser/scan/parser_string_utils_box.hako -// ParserStringUtilsBox — Delegation to StringHelpers (unified string utilities) +// ParserStringUtilsBox — Minimal self-contained string helpers // Responsibility: Backward compatibility wrapper for parser code -// Notes: All functionality now provided by apps/selfhost/common/string_helpers.hako - -using sh_core as StringHelpers +// Notes: sh_core への依存で VM 差分が出たため、ここに最小実装を内包する。 static box ParserStringUtilsBox { - // Delegate all methods to StringHelpers (centralized implementation) - i2s(v) { return StringHelpers.int_to_str(v) } - is_digit(ch) { return StringHelpers.is_digit(ch) } - is_space(ch) { return StringHelpers.is_space(ch) } - is_alpha(ch) { return StringHelpers.is_alpha(ch) } - starts_with(src, i, pat) { return StringHelpers.starts_with(src, i, pat) } - starts_with_kw(src, i, kw) { return StringHelpers.starts_with_kw(src, i, kw) } - index_of(src, i, pat) { return StringHelpers.index_of(src, i, pat) } - trim(s) { return StringHelpers.trim(s) } - to_int(s) { return StringHelpers.to_i64(s) } - skip_ws(src, i) { return StringHelpers.skip_ws(src, i) } + // Numeric to string (minimal) + i2s(v) { + local n = ParserStringUtilsBox.to_int(v) + if n == 0 { return "0" } + if n < 0 { return "-" + ParserStringUtilsBox.i2s(0 - n) } + local out = "" + loop(n > 0) { + local d = n % 10 + out = "0123456789".substring(d, d + 1) + out + n = n / 10 + } + return out + } + + is_digit(ch) { return ch >= "0" && ch <= "9" } + is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" } + is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" } + + starts_with(src, i, pat) { + if src == null || pat == null { return 0 } + local n = src.length() + local m = pat.length() + if i + m > n { return 0 } + local k = 0 + loop(k < m) { + if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 } + k = k + 1 + } + return 1 + } + + // Keyword match with boundary(Stage‑B 安定性のためここで実装) + starts_with_kw(src, i, kw) { + if src == null || kw == null { return 0 } + local n = src.length() + local m = kw.length() + if i + m > n { return 0 } + local k = 0 + loop(k < m) { + if src.substring(i + k, i + k + 1) != kw.substring(k, k + 1) { return 0 } + k = k + 1 + } + local j = i + m + if j >= n { return 1 } + local ch = src.substring(j, j + 1) + if ParserStringUtilsBox.is_alpha(ch) == 1 || ParserStringUtilsBox.is_digit(ch) == 1 { return 0 } + return 1 + } + + index_of(src, i, pat) { + if src == null || pat == null { return -1 } + local n = src.length() + local m = pat.length() + if m == 0 { return i } + local j = i + loop(j + m <= n) { + if ParserStringUtilsBox.starts_with(src, j, pat) == 1 { return j } + j = j + 1 + } + return -1 + } + + trim(s) { + if s == null { return "" } + local str = "" + s + local n = str.length() + local b = 0 + loop(b < n) { + local ch = str.substring(b, b + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break } + } + local e = n + loop(e > b) { + local ch = str.substring(e - 1, e) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break } + } + if e > b { return str.substring(b, e) } + return "" + } + + to_int(s) { + if s == null { return 0 } + local str = "" + s + local n = str.length() + if n == 0 { return 0 } + local neg = 0 + local i = 0 + if str.substring(0, 1) == "-" { neg = 1 i = 1 } + local acc = 0 + loop(i < n) { + local ch = str.substring(i, i + 1) + if ch < "0" || ch > "9" { break } + acc = acc * 10 + ("0123456789".indexOf(ch)) + i = i + 1 + } + if neg == 1 { return 0 - acc } + return acc + } + + skip_ws(src, i) { + if src == null { return i } + local n = src.length() + local j = i + loop(j < n) { + local ch = src.substring(j, j + 1) + if ParserStringUtilsBox.is_space(ch) == 1 { j = j + 1 } else { break } + } + return j + } } diff --git a/lang/src/compiler/parser/stmt/parser_control_box.hako b/lang/src/compiler/parser/stmt/parser_control_box.hako index 54ce11f1..720a877c 100644 --- a/lang/src/compiler/parser/stmt/parser_control_box.hako +++ b/lang/src/compiler/parser/stmt/parser_control_box.hako @@ -7,15 +7,16 @@ using sh_core as StringHelpers // Required: using chain resolution not implemen static box ParserControlBox { // Parse: if (cond) { ... } else { ... } parse_if(src, i, stmt_start, ctx) { - // Shallow recursion guard for Stage‑B / selfhost: protect parse_if from - // accidental self-recursion via malformed input or control flow bugs. + // Depth guard (allow nesting, cap runaway recursion) + local if_depth_before = 0 { - local depth = env.get("HAKO_STAGEB_IF_DEPTH") - if depth != null && ("" + depth) != "0" { + local depth_raw = env.get("HAKO_STAGEB_IF_DEPTH") + if depth_raw != null { if_depth_before = StringHelpers.to_i64("" + depth_raw) } + if if_depth_before >= 64 { print("[stageb/recursion] ParserControlBox.parse_if recursion detected") return "{\"type\":\"If\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"then\":[]}" } - env.set("HAKO_STAGEB_IF_DEPTH", "1") + env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before + 1)) } local j = i + 2 // skip "if" j = ctx.skip_ws(src, j) @@ -52,7 +53,7 @@ static box ParserControlBox { } ctx.gpos_set(j) - env.set("HAKO_STAGEB_IF_DEPTH", "0") + env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before)) if else_json == null { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}" } else { @@ -63,14 +64,15 @@ static box ParserControlBox { // Parse: loop(cond) { ... } // Dev tracing: set HAKO_PARSER_TRACE_LOOP=1 to enable debug prints parse_loop(src, i, stmt_start, ctx) { - // Shallow recursion guard for parse_loop + local loop_depth_before = 0 { - local depth = env.get("HAKO_STAGEB_LOOP_DEPTH") - if depth != null && ("" + depth) != "0" { + local depth_raw = env.get("HAKO_STAGEB_LOOP_DEPTH") + if depth_raw != null { loop_depth_before = StringHelpers.to_i64("" + depth_raw) } + if loop_depth_before >= 64 { print("[stageb/recursion] ParserControlBox.parse_loop recursion detected") return "{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"body\":[]}" } - env.set("HAKO_STAGEB_LOOP_DEPTH", "1") + env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before + 1)) } local trace = 0 // dev-only (disabled in Stage-B runtime: no env API here) @@ -154,7 +156,7 @@ static box ParserControlBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) - env.set("HAKO_STAGEB_LOOP_DEPTH", "0") + env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before)) return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}" } @@ -222,15 +224,27 @@ static box ParserControlBox { // Parse: { stmt1; stmt2; ... } parse_block(src, i, ctx) { + local depth_before = 0 { - local depth = env.get("HAKO_STAGEB_BLOCK_DEPTH") - if depth != null && ("" + depth) != "0" { + local depth_raw = env.get("HAKO_STAGEB_BLOCK_DEPTH") + if depth_raw != null { depth_before = StringHelpers.to_i64("" + depth_raw) } + if depth_before >= 64 { print("[stageb/recursion] ParserControlBox.parse_block recursion detected") return "[]@" + ctx.i2s(i) } - env.set("HAKO_STAGEB_BLOCK_DEPTH", "1") + env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before + 1)) } local j = ctx.skip_ws(src, i) + // Stage‑B safety: inline whitespace skip in case ctx.skip_ws fails to advance on VM + { + local n = src.length() + local jj = j + loop(jj < n) { + local ch = src.substring(jj, jj+1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break } + } + j = jj + } if src.substring(j, j+1) != "{" { return "[]@" + ctx.i2s(j) } j = j + 1 @@ -283,7 +297,7 @@ static box ParserControlBox { } body = body + "]" - env.set("HAKO_STAGEB_BLOCK_DEPTH", "0") + env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before)) return body + "@" + ctx.i2s(j) } } diff --git a/lang/src/compiler/parser/stmt/parser_stmt_box.hako b/lang/src/compiler/parser/stmt/parser_stmt_box.hako index 9fe39b48..b4de6bf7 100644 --- a/lang/src/compiler/parser/stmt/parser_stmt_box.hako +++ b/lang/src/compiler/parser/stmt/parser_stmt_box.hako @@ -15,12 +15,23 @@ static box ParserStmtBox { local depth = env.get("HAKO_STAGEB_STMT_DEPTH") if depth != null && ("" + depth) != "0" { print("[stageb/recursion] ParserStmtBox.parse recursion detected") - // Fail-fast: return empty stmt and keep position unchanged - return "" + // Allow re-entry: reset depth to 1 and continue(ドロップしない) + env.set("HAKO_STAGEB_STMT_DEPTH", "1") + } else { + env.set("HAKO_STAGEB_STMT_DEPTH", "1") } - env.set("HAKO_STAGEB_STMT_DEPTH", "1") } local j = ctx.skip_ws(src, i) + // VM 差分で skip_ws が前進しないパスがあったため、手動で最小限の空白スキップをフォローする。 + { + local n = src.length() + local jj = j + loop(jj < n) { + local ch = src.substring(jj, jj+1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break } + } + j = jj + } { local tr = env.get("NYASH_PARSER_TRACE") if tr != null && ("" + tr) == "1" { @@ -86,49 +97,6 @@ static box ParserStmtBox { return out_using } - // assignment: IDENT '=' expr - if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) { - { - local tr = env.get("NYASH_PARSER_TRACE") - if tr != null && ("" + tr) == "1" { - print("[parser/stmt] kind=assign-or-local j=" + ("" + j)) - } - } - local idp0 = ctx.read_ident2(src, j) - local at0 = idp0.lastIndexOf("@") - if at0 > 0 { - local name0 = idp0.substring(0, at0) - local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length())) - k0 = ctx.skip_ws(src, k0) - if k0 < src.length() && src.substring(k0, k0+1) == "=" { - local eq_two = "=" - if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) } - if eq_two != "==" { - k0 = k0 + 1 - k0 = ctx.skip_ws(src, k0) - local default_local = "{\"type\":\"Int\",\"value\":0}" - local expr_json0 = default_local - local end_pos0 = k0 - if k0 < src.length() { - local ahead = src.substring(k0, k0+1) - if ahead != "}" && ahead != ";" { - expr_json0 = ctx.parse_expr2(src, k0) - end_pos0 = ctx.gpos_get() - } - } - k0 = end_pos0 - if k0 <= stmt_start { - if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } - } - ctx.gpos_set(k0) - // Reset recursion guard before returning - env.set("HAKO_STAGEB_STMT_DEPTH", "0") - return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}" - } - } - } - } - // return statement if ctx.starts_with_kw(src, j, "return") == 1 { { @@ -288,6 +256,49 @@ static box ParserStmtBox { return out_try } + // assignment: IDENT '=' expr(キーワードより後段に配置して、'loop' などを誤認しないようにする) + if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=assign-or-local j=" + ("" + j)) + } + } + local idp0 = ctx.read_ident2(src, j) + local at0 = idp0.lastIndexOf("@") + if at0 > 0 { + local name0 = idp0.substring(0, at0) + local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length())) + k0 = ctx.skip_ws(src, k0) + if k0 < src.length() && src.substring(k0, k0+1) == "=" { + local eq_two = "=" + if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) } + if eq_two != "==" { + k0 = k0 + 1 + k0 = ctx.skip_ws(src, k0) + local default_local = "{\"type\":\"Int\",\"value\":0}" + local expr_json0 = default_local + local end_pos0 = k0 + if k0 < src.length() { + local ahead = src.substring(k0, k0+1) + if ahead != "}" && ahead != ";" { + expr_json0 = ctx.parse_expr2(src, k0) + end_pos0 = ctx.gpos_get() + } + } + k0 = end_pos0 + if k0 <= stmt_start { + if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } + } + ctx.gpos_set(k0) + // Reset recursion guard before returning + env.set("HAKO_STAGEB_STMT_DEPTH", "0") + return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}" + } + } + } + } + // Fallback: expression or unknown token { local tr = env.get("NYASH_PARSER_TRACE") @@ -365,6 +376,8 @@ static box ParserStmtBox { if j < src.length() { j = j + 1 } else { j = src.length() } } ctx.gpos_set(j) + // Reset recursion guard before returning (using statements participate in stmt depth tracking) + env.set("HAKO_STAGEB_STMT_DEPTH", "0") return "" } } diff --git a/lang/src/compiler/pipeline_v2/using_resolver_box.hako b/lang/src/compiler/pipeline_v2/using_resolver_box.hako index 8c073616..01ae1cd3 100644 --- a/lang/src/compiler/pipeline_v2/using_resolver_box.hako +++ b/lang/src/compiler/pipeline_v2/using_resolver_box.hako @@ -1,4 +1,8 @@ // UsingResolverBox — static, stateful resolver helpers(インスタンス禁止・VM互換) +// Boundary memo: +// - entry/using_resolver_box: file I/O + using 収集、modules_list を MapBox へ積む役。 +// - pipeline_v2/using_resolver_box: modules_json 上で alias/path をテキスト検索で解決する役。 +// RegexFlow の単一路ループのみで continue/backedge 分岐を持たないため、Region+next_i 化は不要と判断。 // State layout (Map): { // alias_paths: Map, alias_names: Map, alias_keys: Array, // modules_map: Map, modules_keys: Array diff --git a/lang/src/runner/stage1_cli.hako b/lang/src/runner/stage1_cli.hako index 02a105ee..d3922722 100644 --- a/lang/src/runner/stage1_cli.hako +++ b/lang/src/runner/stage1_cli.hako @@ -107,6 +107,10 @@ static box Stage1Cli { // CLI dispatcher (stub) method stage1_main(args) { + if env.get("STAGE1_CLI_DEBUG") == "1" { + local argc = 0; if args != null { argc = args.size() } + print("[stage1-cli/debug] stage1_main ENTRY: argc=" + ("" + argc) + " env_emits={prog=" + ("" + env.get("STAGE1_EMIT_PROGRAM_JSON")) + ",mir=" + ("" + env.get("STAGE1_EMIT_MIR_JSON")) + "} backend=" + ("" + env.get("STAGE1_BACKEND"))) + } { local use_cli = env.get("NYASH_USE_STAGE1_CLI") if use_cli == null || ("" + use_cli) != "1" { diff --git a/lang/src/using/resolve_ssot_box.hako b/lang/src/using/resolve_ssot_box.hako index f0ce1ad1..d7999d26 100644 --- a/lang/src/using/resolve_ssot_box.hako +++ b/lang/src/using/resolve_ssot_box.hako @@ -9,6 +9,18 @@ // - ctx.using_paths: Array(将来のヒント/本箱では純粋合成のみ) // - ctx.cwd: String(相対の基準) +// Quick README (Phase 25.1 using設計) +// - 役割: using 解決の唯一の窓口(SSOT)。parser/Stage‑B/Stage‑1 は IO や名前探索をここに委譲する。 +// - I/F: +// - resolve(name, ctx) -> path|null : modules/cwd/using_paths から純粋に合成して返す(IOなし)。 +// - resolve_modules(modules_json, using_entries_json, ctx) -> modules_json_resolved|null : +// modules_json(nyash.toml 相当)と using_entries_json(UsingCollector 出力)を突き合わせ、 +// 「解決済み modules_json」(同名重複などの調停結果)を返す。IOなし。 +// - resolve_prefix(using_entries_json, modules_json, ctx) -> prefix_string : +// using_entries_json を modules_json を使ってパス解決し、ファイル読まずに +// 「prefix 文字列(空でよい)を組み立てるための情報」を返すスコープ(MVPは空文字返しでOK)。 +// - ポリシー: IO/ファイル読み込みは絶対にしない。より重い処理は entry/pipeline 側の責務。 + static box UsingResolveSSOTBox { /// Resolve a module name to a file path string (or null when not found). /// name: requested module name (e.g., "hako.mir.builder.internal.lower_return_int") @@ -55,4 +67,16 @@ static box UsingResolveSSOTBox { if base.endsWith("/") { return base + leaf } return base + "/" + leaf } + + // Merge modules_json + using_entries_json into resolved modules table (pure, no IO). + resolve_modules(modules_json, using_entries_json, ctx) { + // MVP: just echo back modules_json (keep behavior unchanged) to establish interface. + return modules_json + } + + // Build prefix string from using_entries_json with modules_json (pure, no IO). + resolve_prefix(using_entries_json, modules_json, ctx) { + // MVP: no file read, so prefix is empty. Interface is reserved for Stage‑1 entry. + return "" + } } diff --git a/nyash.toml b/nyash.toml index e873fa7b..b74ec398 100644 --- a/nyash.toml +++ b/nyash.toml @@ -136,6 +136,7 @@ path = "lang/src/shared/common/string_helpers.hako" "lang.compiler.entry.compiler" = "lang/src/compiler/entry/compiler.hako" "lang.compiler.entry.compiler_stageb" = "lang/src/compiler/entry/compiler_stageb.hako" "lang.compiler.entry.bundle_resolver" = "lang/src/compiler/entry/bundle_resolver.hako" +"lang.compiler.entry.using_resolver_box" = "lang/src/compiler/entry/using_resolver_box.hako" "lang.compiler.entry.using_resolver" = "lang/src/compiler/entry/using_resolver_box.hako" "lang.compiler.emit.mir_emitter_box" = "lang/src/compiler/emit/mir_emitter_box.hako" "lang.compiler.emit.common.json_emit_box" = "lang/src/compiler/emit/common/json_emit_box.hako" diff --git a/src/abi/nyrt_shim.rs b/src/abi/nyrt_shim.rs index 2005b6ca..91ee5af1 100644 --- a/src/abi/nyrt_shim.rs +++ b/src/abi/nyrt_shim.rs @@ -2,16 +2,22 @@ // Kept tiny and isolated. Linkage names match include/nyrt.h. #[no_mangle] -pub extern "C" fn nyrt_init() -> i32 { 0 } +pub extern "C" fn nyrt_init() -> i32 { + 0 +} #[no_mangle] -pub extern "C" fn nyrt_teardown() { } +pub extern "C" fn nyrt_teardown() {} #[no_mangle] -pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { 1 } +pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { + 1 +} #[no_mangle] -pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { 0 } +pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { + 0 +} #[no_mangle] pub extern "C" fn nyrt_hostcall( @@ -20,7 +26,9 @@ pub extern "C" fn nyrt_hostcall( _payload_json: *const ::std::os::raw::c_char, _out_buf: *mut ::std::os::raw::c_char, _out_buf_len: u32, -) -> i32 { 0 } +) -> i32 { + 0 +} #[cfg(test)] mod tests { diff --git a/src/ast.rs b/src/ast.rs index 9d36100f..e1b7c8e5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -10,8 +10,8 @@ use std::collections::HashMap; use std::fmt; mod span; pub use span::Span; -mod utils; mod nodes; +mod utils; pub use nodes::*; // Span は src/ast/span.rs へ分離(re-export で後方互換維持) @@ -573,7 +573,6 @@ pub enum ASTNode { /// meフィールドアクセス: me.field MeField { field: String, span: Span }, - /// ローカル変数宣言: local x, y, z Local { variables: Vec, @@ -584,10 +583,7 @@ pub enum ASTNode { /// ScopeBox(オプション): 診断/マクロ可視性のためのno-opスコープ。 /// 正規化で注入され、MIRビルダがブロックとして処理(意味不変)。 - ScopeBox { - body: Vec, - span: Span, - }, + ScopeBox { body: Vec, span: Span }, /// Outbox変数宣言: outbox x, y, z (static関数内専用) Outbox { diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 6afd70c6..891554a9 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -22,7 +22,15 @@ impl TryFrom for AssignStmt { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::Assignment { target, value, span } => Ok(AssignStmt { target, value, span }), + ASTNode::Assignment { + target, + value, + span, + } => Ok(AssignStmt { + target, + value, + span, + }), other => Err(other), } } @@ -30,7 +38,11 @@ impl TryFrom for AssignStmt { impl From for ASTNode { fn from(s: AssignStmt) -> Self { - ASTNode::Assignment { target: s.target, value: s.value, span: s.span } + ASTNode::Assignment { + target: s.target, + value: s.value, + span: s.span, + } } } @@ -52,7 +64,10 @@ impl TryFrom for ReturnStmt { impl From for ASTNode { fn from(s: ReturnStmt) -> Self { - ASTNode::Return { value: s.value, span: s.span } + ASTNode::Return { + value: s.value, + span: s.span, + } } } @@ -68,7 +83,17 @@ impl TryFrom for IfStmt { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::If { condition, then_body, else_body, span } => Ok(IfStmt { condition, then_body, else_body, span }), + ASTNode::If { + condition, + then_body, + else_body, + span, + } => Ok(IfStmt { + condition, + then_body, + else_body, + span, + }), other => Err(other), } } @@ -76,7 +101,12 @@ impl TryFrom for IfStmt { impl From for ASTNode { fn from(s: IfStmt) -> Self { - ASTNode::If { condition: s.condition, then_body: s.then_body, else_body: s.else_body, span: s.span } + ASTNode::If { + condition: s.condition, + then_body: s.then_body, + else_body: s.else_body, + span: s.span, + } } } @@ -96,8 +126,17 @@ impl TryFrom for BinaryExpr { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::BinaryOp { operator, left, right, span } => - Ok(BinaryExpr { operator, left, right, span }), + ASTNode::BinaryOp { + operator, + left, + right, + span, + } => Ok(BinaryExpr { + operator, + left, + right, + span, + }), other => Err(other), } } @@ -105,7 +144,12 @@ impl TryFrom for BinaryExpr { impl From for ASTNode { fn from(e: BinaryExpr) -> Self { - ASTNode::BinaryOp { operator: e.operator, left: e.left, right: e.right, span: e.span } + ASTNode::BinaryOp { + operator: e.operator, + left: e.left, + right: e.right, + span: e.span, + } } } @@ -120,7 +164,15 @@ impl TryFrom for CallExpr { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::FunctionCall { name, arguments, span } => Ok(CallExpr { name, arguments, span }), + ASTNode::FunctionCall { + name, + arguments, + span, + } => Ok(CallExpr { + name, + arguments, + span, + }), other => Err(other), } } @@ -128,7 +180,11 @@ impl TryFrom for CallExpr { impl From for ASTNode { fn from(c: CallExpr) -> Self { - ASTNode::FunctionCall { name: c.name, arguments: c.arguments, span: c.span } + ASTNode::FunctionCall { + name: c.name, + arguments: c.arguments, + span: c.span, + } } } @@ -144,8 +200,17 @@ impl TryFrom for MethodCallExpr { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::MethodCall { object, method, arguments, span } => - Ok(MethodCallExpr { object, method, arguments, span }), + ASTNode::MethodCall { + object, + method, + arguments, + span, + } => Ok(MethodCallExpr { + object, + method, + arguments, + span, + }), other => Err(other), } } @@ -153,7 +218,12 @@ impl TryFrom for MethodCallExpr { impl From for ASTNode { fn from(m: MethodCallExpr) -> Self { - ASTNode::MethodCall { object: m.object, method: m.method, arguments: m.arguments, span: m.span } + ASTNode::MethodCall { + object: m.object, + method: m.method, + arguments: m.arguments, + span: m.span, + } } } @@ -168,8 +238,15 @@ impl TryFrom for FieldAccessExpr { type Error = ASTNode; fn try_from(node: ASTNode) -> Result { match node { - ASTNode::FieldAccess { object, field, span } => - Ok(FieldAccessExpr { object, field, span }), + ASTNode::FieldAccess { + object, + field, + span, + } => Ok(FieldAccessExpr { + object, + field, + span, + }), other => Err(other), } } @@ -177,6 +254,10 @@ impl TryFrom for FieldAccessExpr { impl From for ASTNode { fn from(f: FieldAccessExpr) -> Self { - ASTNode::FieldAccess { object: f.object, field: f.field, span: f.span } + ASTNode::FieldAccess { + object: f.object, + field: f.field, + span: f.span, + } } } diff --git a/src/ast/utils.rs b/src/ast/utils.rs index 281ca1b7..109afbe9 100644 --- a/src/ast/utils.rs +++ b/src/ast/utils.rs @@ -35,7 +35,7 @@ impl ASTNode { ASTNode::FromCall { .. } => "FromCall", ASTNode::ThisField { .. } => "ThisField", ASTNode::MeField { .. } => "MeField", - + ASTNode::Local { .. } => "Local", ASTNode::Outbox { .. } => "Outbox", ASTNode::FunctionCall { .. } => "FunctionCall", @@ -277,7 +277,7 @@ impl ASTNode { ASTNode::MeField { field, .. } => { format!("MeField({})", field) } - + ASTNode::Local { variables, .. } => { format!("Local({})", variables.join(", ")) } @@ -368,7 +368,7 @@ impl ASTNode { ASTNode::FromCall { span, .. } => *span, ASTNode::ThisField { span, .. } => *span, ASTNode::MeField { span, .. } => *span, - + ASTNode::Local { span, .. } => *span, ASTNode::Outbox { span, .. } => *span, ASTNode::FunctionCall { span, .. } => *span, diff --git a/src/backend/abi_util.rs b/src/backend/abi_util.rs index 255409ce..2b9613e8 100644 --- a/src/backend/abi_util.rs +++ b/src/backend/abi_util.rs @@ -54,10 +54,20 @@ pub fn eq_vm(a: &VMValue, b: &VMValue) -> bool { (Float(x), Integer(y)) => *x == (*y as f64), (BoxRef(ax), BoxRef(by)) => Arc::ptr_eq(ax, by), // Treat BoxRef(VoidBox/MissingBox) as equal to Void (null) for backward compatibility - (BoxRef(bx), Void) => bx.as_any().downcast_ref::().is_some() - || bx.as_any().downcast_ref::().is_some(), - (Void, BoxRef(bx)) => bx.as_any().downcast_ref::().is_some() - || bx.as_any().downcast_ref::().is_some(), + (BoxRef(bx), Void) => { + bx.as_any().downcast_ref::().is_some() + || bx + .as_any() + .downcast_ref::() + .is_some() + } + (Void, BoxRef(bx)) => { + bx.as_any().downcast_ref::().is_some() + || bx + .as_any() + .downcast_ref::() + .is_some() + } _ => false, } } diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index f5b7be48..dc00c1a9 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -20,9 +20,7 @@ impl MirInterpreter { if self.call_depth > MAX_CALL_DEPTH { eprintln!( "[vm-call-depth] exceeded {} in fn={} (depth={})", - MAX_CALL_DEPTH, - func.signature.name, - self.call_depth + MAX_CALL_DEPTH, func.signature.name, self.call_depth ); self.call_depth = self.call_depth.saturating_sub(1); return Err(VMError::InvalidInstruction(format!( @@ -31,7 +29,9 @@ impl MirInterpreter { ))); } // Phase 1: delegate cross-class reroute / narrow fallbacks to method_router - if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; } + if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { + return r; + } let saved_regs = mem::take(&mut self.regs); let saved_fn = self.cur_fn.clone(); self.cur_fn = Some(func.signature.name.clone()); @@ -59,7 +59,11 @@ impl MirInterpreter { let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS") .ok() .and_then(|v| v.parse::().ok()) - .or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::().ok())) + .or_else(|| { + std::env::var("NYASH_VM_MAX_STEPS") + .ok() + .and_then(|v| v.parse::().ok()) + }) .unwrap_or(1_000_000); let mut steps: u64 = 0; @@ -133,9 +137,7 @@ impl MirInterpreter { if trace_phi { eprintln!( "[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}", - block.id, - last_pred, - block.predecessors + block.id, last_pred, block.predecessors ); } for inst in block.phi_instructions() { @@ -154,7 +156,11 @@ impl MirInterpreter { Ok(v) => v, Err(e) => { // Dev safety valve: tolerate undefined phi inputs by substituting Void - if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { + if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED") + .ok() + .as_deref() + == Some("1") + { if Self::trace_enabled() { eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e); } @@ -179,17 +185,41 @@ impl MirInterpreter { // Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0). let strict = { let on = |s: &str| { - matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable") + matches!( + s, + "1" | "true" | "on" | "yes" | "y" | "t" | "enabled" | "enable" + ) }; let off = |s: &str| { - matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable") + matches!( + s, + "0" | "false" + | "off" + | "no" + | "n" + | "f" + | "disabled" + | "disable" + ) }; if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") { let v = v.to_ascii_lowercase(); - if off(&v) { false } else if on(&v) { true } else { true } + if off(&v) { + false + } else if on(&v) { + true + } else { + true + } } else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") { let v = v.to_ascii_lowercase(); - if off(&v) { false } else if on(&v) { true } else { true } + if off(&v) { + false + } else if on(&v) { + true + } else { + true + } } else { true } @@ -213,7 +243,11 @@ impl MirInterpreter { let v = match self.reg_load(*val0) { Ok(v) => v, Err(e) => { - if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { + if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED") + .ok() + .as_deref() + == Some("1") + { if Self::trace_enabled() { eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e); } @@ -243,7 +277,11 @@ impl MirInterpreter { let v = match self.reg_load(*val) { Ok(v) => v, Err(e) => { - if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { + if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED") + .ok() + .as_deref() + == Some("1") + { if Self::trace_enabled() { eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e); } @@ -255,10 +293,7 @@ impl MirInterpreter { }; self.regs.insert(dst_id, v); if Self::trace_enabled() { - eprintln!( - "[vm-trace] phi dst={:?} take default val={:?}", - dst_id, val - ); + eprintln!("[vm-trace] phi dst={:?} take default val={:?}", dst_id, val); } } } diff --git a/src/backend/mir_interpreter/handlers/arithmetic.rs b/src/backend/mir_interpreter/handlers/arithmetic.rs index 4ada4eab..beab509e 100644 --- a/src/backend/mir_interpreter/handlers/arithmetic.rs +++ b/src/backend/mir_interpreter/handlers/arithmetic.rs @@ -32,7 +32,8 @@ impl MirInterpreter { if let Some(op_fn) = self.functions.get("AddOperator.apply/2").cloned() { if !in_guard { if crate::config::env::operator_box_add_adopt() { - let out = self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?; + let out = + self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?; self.regs.insert(dst, out); return Ok(()); } else { diff --git a/src/backend/mir_interpreter/handlers/boxes.rs b/src/backend/mir_interpreter/handlers/boxes.rs index cb20db73..a6901634 100644 --- a/src/backend/mir_interpreter/handlers/boxes.rs +++ b/src/backend/mir_interpreter/handlers/boxes.rs @@ -20,7 +20,9 @@ impl MirInterpreter { let s_opt: Option = match v0.clone() { VMValue::String(s) => Some(s), VMValue::BoxRef(b) => { - if let Some(sb) = b.as_any().downcast_ref::() { + if let Some(sb) = + b.as_any().downcast_ref::() + { Some(sb.value.clone()) } else { None @@ -29,10 +31,13 @@ impl MirInterpreter { _ => None, }; if let Some(s) = s_opt { - let boxed: Box = Box::new(crate::boxes::basic::StringBox::new(s)); + let boxed: Box = + Box::new(crate::boxes::basic::StringBox::new(s)); let created_vm = VMValue::from_nyash_box(boxed); self.regs.insert(dst, created_vm); - if Self::box_trace_enabled() { self.box_trace_emit_new(box_type, args.len()); } + if Self::box_trace_enabled() { + self.box_trace_emit_new(box_type, args.len()); + } return Ok(()); } } @@ -88,7 +93,7 @@ impl MirInterpreter { Err(e) => { return Err(self.err_with_context( &format!("PluginInvoke {}.{}", p.box_type, method), - &format!("{:?}", e) + &format!("{:?}", e), )) } } @@ -115,7 +120,10 @@ impl MirInterpreter { return Ok(()); } if let VMValue::BoxRef(b) = self.reg_load(box_val)? { - if b.as_any().downcast_ref::().is_some() { + if b.as_any() + .downcast_ref::() + .is_some() + { self.write_string(dst, "null".to_string()); return Ok(()); } @@ -125,7 +133,8 @@ impl MirInterpreter { if Self::box_trace_enabled() { let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) { VMValue::BoxRef(b) => { - if let Some(inst) = b.as_any().downcast_ref::() { + if let Some(inst) = b.as_any().downcast_ref::() + { inst.class_name.clone() } else { b.type_name().to_string() @@ -167,7 +176,10 @@ impl MirInterpreter { } } VMValue::BoxRef(ref b) => { - if b.as_any().downcast_ref::().is_some() { + if b.as_any() + .downcast_ref::() + .is_some() + { if let Some(val) = super::boxes_void_guards::handle_void_method(method) { self.write_result(dst, val); return Ok(()); @@ -252,9 +264,12 @@ impl MirInterpreter { // Determine receiver class name when possible let recv_cls: Option = match self.reg_load(box_val).ok() { Some(VMValue::BoxRef(b)) => { - if let Some(inst) = b.as_any().downcast_ref::() { + if let Some(inst) = b.as_any().downcast_ref::() + { Some(inst.class_name.clone()) - } else { None } + } else { + None + } } _ => None, }; @@ -262,13 +277,19 @@ impl MirInterpreter { let prefix = format!("{}.", want); cands.retain(|k| k.starts_with(&prefix)); } - if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None } + if cands.len() == 1 { + self.functions.get(&cands[0]).cloned() + } else { + None + } } { // Build argv: pass receiver as first arg (me) let recv_vm = self.reg_load(box_val)?; let mut argv: Vec = Vec::with_capacity(1 + args.len()); argv.push(recv_vm); - for a in args { argv.push(self.reg_load(*a)?); } + for a in args { + argv.push(self.reg_load(*a)?); + } let ret = self.exec_function_inner(&func, Some(&argv))?; self.write_result(dst, ret); return Ok(()); @@ -312,5 +333,4 @@ impl MirInterpreter { ) -> Result { super::boxes_array::try_handle_array_box(self, dst, box_val, method, args) } - } diff --git a/src/backend/mir_interpreter/handlers/boxes_array.rs b/src/backend/mir_interpreter/handlers/boxes_array.rs index 4b90aa95..771682c3 100644 --- a/src/backend/mir_interpreter/handlers/boxes_array.rs +++ b/src/backend/mir_interpreter/handlers/boxes_array.rs @@ -8,56 +8,58 @@ pub(super) fn try_handle_array_box( method: &str, args: &[ValueId], ) -> Result { - let recv = this.reg_load(box_val)?; - let recv_box_any: Box = match recv.clone() { - VMValue::BoxRef(b) => b.share_box(), - other => other.to_nyash_box(), - }; - if let Some(ab) = recv_box_any - .as_any() - .downcast_ref::() - { - match method { - "birth" => { - // No-op constructor init - this.write_void(dst); - return Ok(true); - } - "push" => { - this.validate_args_exact("push", args, 1)?; - let val = this.load_as_box(args[0])?; - let _ = ab.push(val); - this.write_void(dst); - return Ok(true); - } - "pop" => { - if !args.is_empty() { return Err(this.err_invalid("pop expects 0 args")); } - let ret = ab.pop(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "len" | "length" | "size" => { - let ret = ab.length(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "get" => { - this.validate_args_exact("get", args, 1)?; - let idx = this.load_as_box(args[0])?; - let ret = ab.get(idx); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "set" => { - this.validate_args_exact("set", args, 2)?; - let idx = this.load_as_box(args[0])?; - let val = this.load_as_box(args[1])?; - let _ = ab.set(idx, val); - this.write_void(dst); - return Ok(true); - } - _ => {} + let recv = this.reg_load(box_val)?; + let recv_box_any: Box = match recv.clone() { + VMValue::BoxRef(b) => b.share_box(), + other => other.to_nyash_box(), + }; + if let Some(ab) = recv_box_any + .as_any() + .downcast_ref::() + { + match method { + "birth" => { + // No-op constructor init + this.write_void(dst); + return Ok(true); } + "push" => { + this.validate_args_exact("push", args, 1)?; + let val = this.load_as_box(args[0])?; + let _ = ab.push(val); + this.write_void(dst); + return Ok(true); + } + "pop" => { + if !args.is_empty() { + return Err(this.err_invalid("pop expects 0 args")); + } + let ret = ab.pop(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "len" | "length" | "size" => { + let ret = ab.length(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "get" => { + this.validate_args_exact("get", args, 1)?; + let idx = this.load_as_box(args[0])?; + let ret = ab.get(idx); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "set" => { + this.validate_args_exact("set", args, 2)?; + let idx = this.load_as_box(args[0])?; + let val = this.load_as_box(args[1])?; + let _ = ab.set(idx, val); + this.write_void(dst); + return Ok(true); + } + _ => {} } - Ok(false) + } + Ok(false) } diff --git a/src/backend/mir_interpreter/handlers/boxes_instance.rs b/src/backend/mir_interpreter/handlers/boxes_instance.rs index 54bed76e..b70d5128 100644 --- a/src/backend/mir_interpreter/handlers/boxes_instance.rs +++ b/src/backend/mir_interpreter/handlers/boxes_instance.rs @@ -14,26 +14,39 @@ pub(super) fn try_handle_instance_box( other => other.to_nyash_box(), }; if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" { - eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len()); + eprintln!( + "[vm-trace] instance-check recv_box_any.type={} args_len={}", + recv_box_any.type_name(), + args.len() + ); } - if let Some(inst) = recv_box_any.as_any().downcast_ref::() { + if let Some(inst) = recv_box_any + .as_any() + .downcast_ref::() + { // Development guard: ensure JsonScanner core fields have sensible defaults if inst.class_name == "JsonScanner" { // populate missing fields to avoid Void in comparisons inside is_eof/advance if inst.get_field_ng("position").is_none() { - let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0)); + let _ = + inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0)); } if inst.get_field_ng("length").is_none() { - let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0)); + let _ = + inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0)); } if inst.get_field_ng("line").is_none() { let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1)); } if inst.get_field_ng("column").is_none() { - let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1)); + let _ = + inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1)); } if inst.get_field_ng("text").is_none() { - let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new())); + let _ = inst.set_field_ng( + "text".to_string(), + crate::value::NyashValue::String(String::new()), + ); } } // JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch @@ -47,11 +60,26 @@ pub(super) fn try_handle_instance_box( ); } // Resolve lowered method function: "Class.method/arity" - let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len())); + let primary = format!( + "{}.{}{}", + inst.class_name, + method, + format!("/{}", args.len()) + ); // Alternate naming: "ClassInstance.method/arity" - let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len())); + let alt = format!( + "{}Instance.{}{}", + inst.class_name, + method, + format!("/{}", args.len()) + ); // Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)" - let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1)); + let static_variant = format!( + "{}.{}{}", + inst.class_name, + method, + format!("/{}", args.len() + 1) + ); // Special-case: toString() → stringify/0 if present // Prefer base class (strip trailing "Instance") stringify when available. let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() { @@ -64,27 +92,43 @@ pub(super) fn try_handle_instance_box( Some(format!("{}.stringify/0", base_name)), Some(format!("{}.stringify/0", inst.class_name)), ) - } else { (None, None) }; + } else { + (None, None) + }; if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { eprintln!( "[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]", - inst.class_name, method, args.len(), primary, alt, static_variant + inst.class_name, + method, + args.len(), + primary, + alt, + static_variant ); } // Prefer stringify for toString() if present (semantic alias). Try instance first, then base. let func_opt = if let Some(ref sname) = stringify_inst { this.functions.get(sname).cloned() - } else { None } - .or_else(|| stringify_base.as_ref().and_then(|n| this.functions.get(n).cloned())) + } else { + None + } + .or_else(|| { + stringify_base + .as_ref() + .and_then(|n| this.functions.get(n).cloned()) + }) .or_else(|| this.functions.get(&primary).cloned()) .or_else(|| this.functions.get(&alt).cloned()) .or_else(|| this.functions.get(&static_variant).cloned()); if let Some(func) = func_opt { if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name); + eprintln!( + "[vm-trace] instance-dispatch hit -> {}", + func.signature.name + ); } // Build argv: me + args (works for both instance and static(me, ...)) let mut argv: Vec = Vec::with_capacity(1 + args.len()); @@ -95,7 +139,9 @@ pub(super) fn try_handle_instance_box( } } argv.push(recv_vm.clone()); - for a in args { argv.push(this.reg_load(*a)?); } + for a in args { + argv.push(this.reg_load(*a)?); + } let ret = this.exec_function_inner(&func, Some(&argv))?; this.write_result(dst, ret); return Ok(true); @@ -120,19 +166,28 @@ pub(super) fn try_handle_instance_box( if filtered.len() == 1 { let fname = &filtered[0]; if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname); + eprintln!( + "[vm-trace] instance-dispatch fallback (scoped) -> {}", + fname + ); } if let Some(func) = this.functions.get(fname).cloned() { let mut argv: Vec = Vec::with_capacity(1 + args.len()); if method == "birth" && crate::config::env::using_is_dev() { if matches!(recv_vm, VMValue::Void) { - return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden")); + return Err( + this.err_invalid("Dev assert: birth(me==Void) is forbidden") + ); } } argv.push(recv_vm.clone()); - for a in args { argv.push(this.reg_load(*a)?); } + for a in args { + argv.push(this.reg_load(*a)?); + } let ret = this.exec_function_inner(&func, Some(&argv))?; - if let Some(d) = dst { this.regs.insert(d, ret); } + if let Some(d) = dst { + this.regs.insert(d, ret); + } return Ok(true); } } else if filtered.len() > 1 { @@ -143,7 +198,11 @@ pub(super) fn try_handle_instance_box( } else { // No same-class candidate: do not dispatch cross-class if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len())); + eprintln!( + "[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", + method, + format!("/{}", args.len()) + ); } } } diff --git a/src/backend/mir_interpreter/handlers/boxes_map.rs b/src/backend/mir_interpreter/handlers/boxes_map.rs index ea3894aa..b32b4a01 100644 --- a/src/backend/mir_interpreter/handlers/boxes_map.rs +++ b/src/backend/mir_interpreter/handlers/boxes_map.rs @@ -8,115 +8,130 @@ pub(super) fn try_handle_map_box( method: &str, args: &[ValueId], ) -> Result { - let recv = this.reg_load(box_val)?; - let recv_box_any: Box = match recv.clone() { - VMValue::BoxRef(b) => b.share_box(), - other => other.to_nyash_box(), - }; - if let Some(mb) = recv_box_any - .as_any() - .downcast_ref::() - { - match method { - "birth" => { - // No-op constructor init for MapBox - this.write_void(dst); - return Ok(true); - } - // Field bridge: treat getField/setField as get/set with string key - "getField" => { - this.validate_args_exact("MapBox.getField", args, 1)?; - let k_vm = this.reg_load(args[0])?; - // Field access expects a String key; otherwise return a stable tag. - if !matches!(k_vm, VMValue::String(_)) { - this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string())); - return Ok(true); - } - let k = this.load_as_box(args[0])?; - let ret = mb.get(k); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "setField" => { - this.validate_args_exact("MapBox.setField", args, 2)?; - let k_vm = this.reg_load(args[0])?; - if !matches!(k_vm, VMValue::String(_)) { - this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string())); - return Ok(true); - } - let k = this.load_as_box(args[0])?; - let v = this.load_as_box(args[1])?; - let ret = mb.set(k, v); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "set" => { - this.validate_args_exact("MapBox.set", args, 2)?; - let k_vm = this.reg_load(args[0])?; - if !matches!(k_vm, VMValue::String(_)) { - this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string())); - return Ok(true); - } - let k = this.load_as_box(args[0])?; - let v = this.load_as_box(args[1])?; - let ret = mb.set(k, v); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "get" => { - this.validate_args_exact("MapBox.get", args, 1)?; - let k_vm = this.reg_load(args[0])?; - if !matches!(k_vm, VMValue::String(_)) { - this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string())); - return Ok(true); - } - let k = this.load_as_box(args[0])?; - let ret = mb.get(k); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "has" => { - this.validate_args_exact("MapBox.has", args, 1)?; - let k = this.load_as_box(args[0])?; - let ret = mb.has(k); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "delete" => { - this.validate_args_exact("MapBox.delete", args, 1)?; - let k_vm = this.reg_load(args[0])?; - if !matches!(k_vm, VMValue::String(_)) { - this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string())); - return Ok(true); - } - let k = this.load_as_box(args[0])?; - let ret = mb.delete(k); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "len" | "length" | "size" => { - let ret = mb.size(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "keys" => { - let ret = mb.keys(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "values" => { - let ret = mb.values(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "clear" => { - // Reset map to empty; return a neutral value - let ret = mb.clear(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - _ => {} + let recv = this.reg_load(box_val)?; + let recv_box_any: Box = match recv.clone() { + VMValue::BoxRef(b) => b.share_box(), + other => other.to_nyash_box(), + }; + if let Some(mb) = recv_box_any + .as_any() + .downcast_ref::() + { + match method { + "birth" => { + // No-op constructor init for MapBox + this.write_void(dst); + return Ok(true); } + // Field bridge: treat getField/setField as get/set with string key + "getField" => { + this.validate_args_exact("MapBox.getField", args, 1)?; + let k_vm = this.reg_load(args[0])?; + // Field access expects a String key; otherwise return a stable tag. + if !matches!(k_vm, VMValue::String(_)) { + this.write_result( + dst, + VMValue::String("[map/bad-key] field name must be string".to_string()), + ); + return Ok(true); + } + let k = this.load_as_box(args[0])?; + let ret = mb.get(k); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "setField" => { + this.validate_args_exact("MapBox.setField", args, 2)?; + let k_vm = this.reg_load(args[0])?; + if !matches!(k_vm, VMValue::String(_)) { + this.write_result( + dst, + VMValue::String("[map/bad-key] field name must be string".to_string()), + ); + return Ok(true); + } + let k = this.load_as_box(args[0])?; + let v = this.load_as_box(args[1])?; + let ret = mb.set(k, v); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "set" => { + this.validate_args_exact("MapBox.set", args, 2)?; + let k_vm = this.reg_load(args[0])?; + if !matches!(k_vm, VMValue::String(_)) { + this.write_result( + dst, + VMValue::String("[map/bad-key] key must be string".to_string()), + ); + return Ok(true); + } + let k = this.load_as_box(args[0])?; + let v = this.load_as_box(args[1])?; + let ret = mb.set(k, v); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "get" => { + this.validate_args_exact("MapBox.get", args, 1)?; + let k_vm = this.reg_load(args[0])?; + if !matches!(k_vm, VMValue::String(_)) { + this.write_result( + dst, + VMValue::String("[map/bad-key] key must be string".to_string()), + ); + return Ok(true); + } + let k = this.load_as_box(args[0])?; + let ret = mb.get(k); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "has" => { + this.validate_args_exact("MapBox.has", args, 1)?; + let k = this.load_as_box(args[0])?; + let ret = mb.has(k); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "delete" => { + this.validate_args_exact("MapBox.delete", args, 1)?; + let k_vm = this.reg_load(args[0])?; + if !matches!(k_vm, VMValue::String(_)) { + this.write_result( + dst, + VMValue::String("[map/bad-key] key must be string".to_string()), + ); + return Ok(true); + } + let k = this.load_as_box(args[0])?; + let ret = mb.delete(k); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "len" | "length" | "size" => { + let ret = mb.size(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "keys" => { + let ret = mb.keys(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "values" => { + let ret = mb.values(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "clear" => { + // Reset map to empty; return a neutral value + let ret = mb.clear(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + _ => {} } - Ok(false) + } + Ok(false) } diff --git a/src/backend/mir_interpreter/handlers/boxes_object_fields.rs b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs index 043039f8..ca50fbf1 100644 --- a/src/backend/mir_interpreter/handlers/boxes_object_fields.rs +++ b/src/backend/mir_interpreter/handlers/boxes_object_fields.rs @@ -9,8 +9,8 @@ pub(super) fn try_handle_object_fields( ) -> Result { // Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue { - use crate::value::NyashValue as NV; use super::VMValue as VV; + use crate::value::NyashValue as NV; match v { VV::Integer(i) => NV::Integer(*i), VV::Float(f) => NV::Float(*f), @@ -18,12 +18,12 @@ pub(super) fn try_handle_object_fields( VV::String(s) => NV::String(s.clone()), VV::Void => NV::Void, VV::Future(_) => NV::Void, // not expected in fields - VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here + VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here } } fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue { - use crate::value::NyashValue as NV; use super::VMValue as VV; + use crate::value::NyashValue as NV; match v { NV::Integer(i) => VV::Integer(*i), NV::Float(f) => VV::Float(*f), @@ -48,7 +48,8 @@ pub(super) fn try_handle_object_fields( // Create a temporary value to hold the singleton let temp_id = ValueId(999999999); // Temporary ID for singleton - this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone))); + this.regs + .insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone))); temp_id } else { box_val @@ -59,18 +60,27 @@ pub(super) fn try_handle_object_fields( // MapBox special-case: bridge to MapBox.get, with string-only key if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) { - if bref.as_any().downcast_ref::().is_some() { + if bref + .as_any() + .downcast_ref::() + .is_some() + { let key_vm = this.reg_load(args[0])?; if let VMValue::String(_) = key_vm { let k = key_vm.to_nyash_box(); let map = bref.share_box(); - if let Some(mb) = map.as_any().downcast_ref::() { + if let Some(mb) = + map.as_any().downcast_ref::() + { let ret = mb.get(k); this.write_result(dst, VMValue::from_nyash_box(ret)); return Ok(true); } } else { - this.write_string(dst, "[map/bad-key] field name must be string".to_string()); + this.write_string( + dst, + "[map/bad-key] field name must be string".to_string(), + ); return Ok(true); } } @@ -94,15 +104,22 @@ pub(super) fn try_handle_object_fields( }; // Prefer InstanceBox internal storage (structural correctness) if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { + if let Some(inst) = bref + .as_any() + .downcast_ref::() + { if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { eprintln!("[vm-trace] getField instance class={}", inst.class_name); } // Special-case bridge: JsonParser.length -> tokens.length() if inst.class_name == "JsonParser" && fname == "length" { if let Some(tokens_shared) = inst.get_field("tokens") { - let tokens_box: Box = tokens_shared.share_box(); - if let Some(arr) = tokens_box.as_any().downcast_ref::() { + let tokens_box: Box = + tokens_shared.share_box(); + if let Some(arr) = tokens_box + .as_any() + .downcast_ref::() + { let len_box = arr.length(); this.write_result(dst, VMValue::from_nyash_box(len_box)); return Ok(true); @@ -112,7 +129,9 @@ pub(super) fn try_handle_object_fields( // First: prefer fields_ng (NyashValue) when present if let Some(nv) = inst.get_field_ng(&fname) { // Dev trace: JsonToken field get - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" { + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") + && inst.class_name == "JsonToken" + { eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv); } // Treat complex Box-like values as "missing" for internal storage so that @@ -129,13 +148,18 @@ pub(super) fn try_handle_object_fields( ); if !is_missing { if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv); + eprintln!( + "[vm-trace] getField internal {}.{} -> {:?}", + inst.class_name, fname, nv + ); } // Special-case: NV::Box should surface as VMValue::BoxRef if let crate::value::NyashValue::Box(ref arc_m) = nv { if let Ok(guard) = arc_m.lock() { - let cloned: Box = guard.clone_box(); - let arc: std::sync::Arc = std::sync::Arc::from(cloned); + let cloned: Box = + guard.clone_box(); + let arc: std::sync::Arc = + std::sync::Arc::from(cloned); this.write_result(dst, VMValue::BoxRef(arc)); } else { this.write_void(dst); @@ -170,8 +194,12 @@ pub(super) fn try_handle_object_fields( _ => None, }; if let Some(v) = def { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v); + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") + { + eprintln!( + "[vm-trace] getField default JsonScanner.{} -> {:?}", + fname, v + ); } this.write_result(dst, v); return Ok(true); @@ -229,10 +257,14 @@ pub(super) fn try_handle_object_fields( // JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide // pragmatic defaults to avoid Void comparisons during bring-up. if let VMValue::Void = v { - let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1"); + let guard_on = + std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1"); let fn_ctx = this.cur_fn.as_deref().unwrap_or(""); if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname); + eprintln!( + "[vm-trace] getField guard_check ctx={} guard_on={} name={}", + fn_ctx, guard_on, fname + ); } if guard_on { let fn_ctx = this.cur_fn.as_deref().unwrap_or(""); @@ -243,7 +275,10 @@ pub(super) fn try_handle_object_fields( if is_scanner_ctx { // Try class-aware default first if let Ok(VMValue::BoxRef(bref2)) = this.reg_load(actual_box_val) { - if let Some(inst2) = bref2.as_any().downcast_ref::() { + if let Some(inst2) = bref2 + .as_any() + .downcast_ref::() + { if inst2.class_name == "JsonScanner" { let fallback = match fname.as_str() { "position" | "length" => Some(VMValue::Integer(0)), @@ -252,8 +287,13 @@ pub(super) fn try_handle_object_fields( _ => None, }; if let Some(val) = fallback { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val); + if std::env::var("NYASH_VM_TRACE").ok().as_deref() + == Some("1") + { + eprintln!( + "[vm-trace] getField final_default {} -> {:?}", + fname, val + ); } v = val; } @@ -280,7 +320,11 @@ pub(super) fn try_handle_object_fields( } if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { if let VMValue::BoxRef(b) = &v { - eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name()); + eprintln!( + "[vm-trace] getField legacy {} -> BoxRef({})", + fname, + b.type_name() + ); } else { eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v); } @@ -299,9 +343,13 @@ pub(super) fn try_handle_object_fields( // class name unknown here; use receiver type name if possible let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) { VMValue::BoxRef(b) => { - if let Some(inst) = b.as_any().downcast_ref::() { + if let Some(inst) = + b.as_any().downcast_ref::() + { inst.class_name.clone() - } else { b.type_name().to_string() } + } else { + b.type_name().to_string() + } } _ => "".to_string(), }; @@ -322,7 +370,8 @@ pub(super) fn try_handle_object_fields( // Create a temporary value to hold the singleton let temp_id = ValueId(999999998); // Temporary ID for singleton (different from getField) - this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone))); + this.regs + .insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone))); temp_id } else { box_val @@ -333,19 +382,28 @@ pub(super) fn try_handle_object_fields( // MapBox special-case: bridge to MapBox.set, with string-only key if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) { - if bref.as_any().downcast_ref::().is_some() { + if bref + .as_any() + .downcast_ref::() + .is_some() + { let key_vm = this.reg_load(args[0])?; if let VMValue::String(_) = key_vm { let k = key_vm.to_nyash_box(); let v = this.reg_load(args[1])?.to_nyash_box(); let map = bref.share_box(); - if let Some(mb) = map.as_any().downcast_ref::() { + if let Some(mb) = + map.as_any().downcast_ref::() + { let _ = mb.set(k, v); this.write_void(dst); return Ok(true); } } else { - this.write_string(dst, "[map/bad-key] field name must be string".to_string()); + this.write_string( + dst, + "[map/bad-key] field name must be string".to_string(), + ); return Ok(true); } } @@ -358,9 +416,15 @@ pub(super) fn try_handle_object_fields( // Dev trace: JsonToken field set if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { + if let Some(inst) = bref + .as_any() + .downcast_ref::() + { if inst.class_name == "JsonToken" { - eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv); + eprintln!( + "[vm-trace] JsonToken.setField name={} vmval={:?}", + fname, valv + ); } } } @@ -377,9 +441,13 @@ pub(super) fn try_handle_object_fields( }; let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) { VMValue::BoxRef(b) => { - if let Some(inst) = b.as_any().downcast_ref::() { + if let Some(inst) = + b.as_any().downcast_ref::() + { inst.class_name.clone() - } else { b.type_name().to_string() } + } else { + b.type_name().to_string() + } } _ => "".to_string(), }; @@ -387,28 +455,52 @@ pub(super) fn try_handle_object_fields( } // Prefer InstanceBox internal storage if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? { - if let Some(inst) = bref.as_any().downcast_ref::() { + if let Some(inst) = bref + .as_any() + .downcast_ref::() + { // Primitives → 内部保存 - if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) { + if matches!( + valv, + VMValue::Integer(_) + | VMValue::Float(_) + | VMValue::Bool(_) + | VMValue::String(_) + | VMValue::Void + ) { let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv)); return Ok(true); } // BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存 if let VMValue::BoxRef(bx) = &valv { - if let Some(ib) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value)); + if let Some(ib) = bx.as_any().downcast_ref::() + { + let _ = inst.set_field_ng( + fname.clone(), + crate::value::NyashValue::Integer(ib.value), + ); return Ok(true); } if let Some(fb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value)); + let _ = inst.set_field_ng( + fname.clone(), + crate::value::NyashValue::Float(fb.value), + ); return Ok(true); } if let Some(bb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value)); + let _ = inst.set_field_ng( + fname.clone(), + crate::value::NyashValue::Bool(bb.value), + ); return Ok(true); } - if let Some(sb) = bx.as_any().downcast_ref::() { - let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone())); + if let Some(sb) = bx.as_any().downcast_ref::() + { + let _ = inst.set_field_ng( + fname.clone(), + crate::value::NyashValue::String(sb.value.clone()), + ); return Ok(true); } // For complex Box values (InstanceBox/MapBox/ArrayBox...), store into @@ -419,10 +511,7 @@ pub(super) fn try_handle_object_fields( } } let key = this.object_key_for(actual_box_val); - this.obj_fields - .entry(key) - .or_default() - .insert(fname, valv); + this.obj_fields.entry(key).or_default().insert(fname, valv); Ok(true) } _ => Ok(false), diff --git a/src/backend/mir_interpreter/handlers/boxes_plugin.rs b/src/backend/mir_interpreter/handlers/boxes_plugin.rs index af23c3a5..11279786 100644 --- a/src/backend/mir_interpreter/handlers/boxes_plugin.rs +++ b/src/backend/mir_interpreter/handlers/boxes_plugin.rs @@ -52,7 +52,7 @@ pub(super) fn invoke_plugin_box( } Err(e) => Err(this.err_with_context( &format!("BoxCall {}.{}", p.box_type, method), - &format!("{:?}", e) + &format!("{:?}", e), )), } } else if recv_box.type_name() == "StringBox" { @@ -99,10 +99,7 @@ pub(super) fn invoke_plugin_box( if let Some(arg_id) = args.get(0) { let ch = this.reg_load(*arg_id)?.to_string(); let c = ch.chars().next().unwrap_or('\0'); - let is_alpha = - ('A'..='Z').contains(&c) || - ('a'..='z').contains(&c) || - c == '_'; + let is_alpha = ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) || c == '_'; this.write_result(dst, VMValue::Bool(is_alpha)); Ok(()) } else { @@ -115,14 +112,21 @@ pub(super) fn invoke_plugin_box( // Special-case: minimal runtime fallback for common InstanceBox methods when // lowered functions are not available (dev robustness). Keeps behavior stable // without changing semantics in the normal path. - if let Some(inst) = recv_box.as_any().downcast_ref::() { + if let Some(inst) = recv_box + .as_any() + .downcast_ref::() + { // Generic current() fallback: if object has integer 'position' and string 'text', // return one character at that position (or empty at EOF). This covers JsonScanner // and compatible scanners without relying on class name. if method == "current" && args.is_empty() { - if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position") { - if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text") { - let s = if pos < 0 || (pos as usize) >= text.len() { String::new() } else { + if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position") + { + if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text") + { + let s = if pos < 0 || (pos as usize) >= text.len() { + String::new() + } else { let bytes = text.as_bytes(); let i = pos as usize; let j = (i + 1).min(bytes.len()); @@ -137,7 +141,11 @@ pub(super) fn invoke_plugin_box( // Generic toString fallback for any non-plugin box if method == "toString" { // Map VoidBox.toString → "null" for JSON-friendly semantics - let s = if recv_box.as_any().downcast_ref::().is_some() { + let s = if recv_box + .as_any() + .downcast_ref::() + .is_some() + { "null".to_string() } else { recv_box.to_string_box().value @@ -148,7 +156,10 @@ pub(super) fn invoke_plugin_box( // Minimal runtime fallback for common InstanceBox.is_eof when lowered function is not present. // This avoids cross-class leaks and hard errors in union-like flows. if method == "is_eof" && args.is_empty() { - if let Some(inst) = recv_box.as_any().downcast_ref::() { + if let Some(inst) = recv_box + .as_any() + .downcast_ref::() + { if inst.class_name == "JsonToken" { let is = match inst.get_field_ng("type") { Some(crate::value::NyashValue::String(ref s)) => s == "EOF", @@ -158,8 +169,14 @@ pub(super) fn invoke_plugin_box( return Ok(()); } if inst.class_name == "JsonScanner" { - let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 }; - let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 }; + let pos = match inst.get_field_ng("position") { + Some(crate::value::NyashValue::Integer(i)) => i, + _ => 0, + }; + let len = match inst.get_field_ng("length") { + Some(crate::value::NyashValue::Integer(i)) => i, + _ => 0, + }; let is = pos >= len; this.write_result(dst, VMValue::Bool(is)); return Ok(()); @@ -167,7 +184,10 @@ pub(super) fn invoke_plugin_box( } } // Dynamic fallback for user-defined InstanceBox: dispatch to lowered function "Class.method/Arity" - if let Some(inst) = recv_box.as_any().downcast_ref::() { + if let Some(inst) = recv_box + .as_any() + .downcast_ref::() + { let class_name = inst.class_name.clone(); let arity = args.len(); // function name arity excludes 'me' let fname = format!("{}.{}{}", class_name, method, format!("/{}", arity)); @@ -190,10 +210,10 @@ pub(super) fn invoke_plugin_box( this.write_string(dst, String::new()); return Ok(()); } - // VoidBox graceful handling for common container-like methods - // Treat null.receiver.* as safe no-ops that return null/0 where appropriate - if recv_box.type_name() == "VoidBox" { - match method { + // VoidBox graceful handling for common container-like methods + // Treat null.receiver.* as safe no-ops that return null/0 where appropriate + if recv_box.type_name() == "VoidBox" { + match method { "object_get" | "array_get" | "toString" => { this.write_void(dst); return Ok(()); diff --git a/src/backend/mir_interpreter/handlers/boxes_string.rs b/src/backend/mir_interpreter/handlers/boxes_string.rs index f2d7faed..1775008a 100644 --- a/src/backend/mir_interpreter/handlers/boxes_string.rs +++ b/src/backend/mir_interpreter/handlers/boxes_string.rs @@ -7,207 +7,231 @@ pub(super) fn try_handle_string_box( method: &str, args: &[ValueId], ) -> Result { - if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-trace] try_handle_string_box(method={})", method); + if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { + eprintln!("[vm-trace] try_handle_string_box(method={})", method); + } + let recv = this.reg_load(box_val)?; + // Ultra-fast path: raw VM string receiver for length/size (no boxing at all) + if (method == "length" || method == "size") + && std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") + { + if let VMValue::String(ref raw) = recv { + let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1"); + let n = if use_cp { + raw.chars().count() as i64 + } else { + raw.len() as i64 + }; + this.write_result(dst, VMValue::Integer(n)); + return Ok(true); } - let recv = this.reg_load(box_val)?; - // Ultra-fast path: raw VM string receiver for length/size (no boxing at all) - if (method == "length" || method == "size") - && std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") - { - if let VMValue::String(ref raw) = recv { + } + // Handle ONLY when the receiver is actually a string. + // Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()). + let sb_norm_opt: Option = match recv.clone() { + VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)), + VMValue::BoxRef(b) => { + if b.as_any() + .downcast_ref::() + .is_some() + { + Some(b.to_string_box()) + } else { + None + } + } + _ => None, + }; + let Some(sb_norm) = sb_norm_opt else { + return Ok(false); + }; + // Only handle known string methods here (receiver is confirmed string) + match method { + "length" | "size" => { + // Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead) + if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") { let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1"); - let n = if use_cp { raw.chars().count() as i64 } else { raw.len() as i64 }; + let n = if use_cp { + sb_norm.value.chars().count() as i64 + } else { + sb_norm.value.len() as i64 + }; this.write_result(dst, VMValue::Integer(n)); return Ok(true); } + let ret = sb_norm.length(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); } - // Handle ONLY when the receiver is actually a string. - // Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()). - let sb_norm_opt: Option = match recv.clone() { - VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)), - VMValue::BoxRef(b) => { - if b.as_any().downcast_ref::().is_some() { - Some(b.to_string_box()) - } else { - None - } - } - _ => None, - }; - let Some(sb_norm) = sb_norm_opt else { return Ok(false) }; - // Only handle known string methods here (receiver is confirmed string) - match method { - "length" | "size" => { - // Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead) - if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") { - let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1"); - let n = if use_cp { sb_norm.value.chars().count() as i64 } else { sb_norm.value.len() as i64 }; - this.write_result(dst, VMValue::Integer(n)); - return Ok(true); - } - let ret = sb_norm.length(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "replace" => { - // replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal - this.validate_args_exact("replace", args, 2)?; - let old_s = this.reg_load(args[0])?.to_string(); - let new_s = this.reg_load(args[1])?.to_string(); - // Core policy: replace only the first occurrence - let out = if let Some(pos) = sb_norm.value.find(&old_s) { - let mut s = String::with_capacity(sb_norm.value.len() + new_s.len()); - s.push_str(&sb_norm.value[..pos]); - s.push_str(&new_s); - s.push_str(&sb_norm.value[pos + old_s.len()..]); - s - } else { - sb_norm.value.clone() - }; - this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out)))); - return Ok(true); - } - "trim" => { - let ret = sb_norm.trim(); - this.write_result(dst, VMValue::from_nyash_box(ret)); - return Ok(true); - } - "indexOf" => { - // Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex) - let (needle, from_index) = match args.len() { - 1 => { - // indexOf(search) - search from beginning - let n = this.reg_load(args[0])?.to_string(); - (n, 0) - } - 2 => { - // indexOf(search, fromIndex) - search from specified position - let n = this.reg_load(args[0])?.to_string(); - let from = this.reg_load(args[1])?.as_integer().unwrap_or(0); - (n, from.max(0) as usize) - } - _ => { - return Err(this.err_invalid( - "indexOf expects 1 or 2 args (search [, fromIndex])" - )); - } - }; - - // Search for needle starting from from_index - let search_str = if from_index >= sb_norm.value.len() { - "" - } else { - &sb_norm.value[from_index..] - }; - - let idx = search_str.find(&needle) - .map(|i| (from_index + i) as i64) - .unwrap_or(-1); - - this.write_result(dst, VMValue::Integer(idx)); - return Ok(true); - } - "contains" => { - // contains(search) -> boolean (true if found, false otherwise) - // Implemented as indexOf(search) >= 0 - this.validate_args_exact("contains", args, 1)?; - let needle = this.reg_load(args[0])?.to_string(); - let found = sb_norm.value.contains(&needle); - this.write_result(dst, VMValue::Bool(found)); - return Ok(true); - } - "lastIndexOf" => { - // lastIndexOf(substr) -> last index or -1 - this.validate_args_exact("lastIndexOf", args, 1)?; - let needle = this.reg_load(args[0])?.to_string(); - let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1); - this.write_result(dst, VMValue::Integer(idx)); - return Ok(true); - } - "stringify" => { - // JSON-style stringify for strings: quote and escape common characters - let mut quoted = String::with_capacity(sb_norm.value.len() + 2); - quoted.push('"'); - for ch in sb_norm.value.chars() { - match ch { - '"' => quoted.push_str("\\\""), - '\\' => quoted.push_str("\\\\"), - '\n' => quoted.push_str("\\n"), - '\r' => quoted.push_str("\\r"), - '\t' => quoted.push_str("\\t"), - c if c.is_control() => quoted.push(' '), - c => quoted.push(c), - } - } - quoted.push('"'); - this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted)))); - return Ok(true); - } - "substring" => { - // Support both 1-arg (start to end) and 2-arg (start, end) forms - let (s_idx, e_idx) = match args.len() { - 1 => { - // substring(start) - from start to end of string - let s = this.reg_load(args[0])?.as_integer().unwrap_or(0); - let len = sb_norm.value.chars().count() as i64; - (s, len) - } - 2 => { - // substring(start, end) - half-open interval [start, end) - let s = this.reg_load(args[0])?.as_integer().unwrap_or(0); - let e = this.reg_load(args[1])?.as_integer().unwrap_or(0); - (s, e) - } - _ => { - return Err(this.err_invalid( - "substring expects 1 or 2 args (start [, end])" - )); - } - }; - let len = sb_norm.value.chars().count() as i64; - let start = s_idx.max(0).min(len) as usize; - let end = e_idx.max(start as i64).min(len) as usize; - let chars: Vec = sb_norm.value.chars().collect(); - let sub: String = chars[start..end].iter().collect(); - this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub)))); - return Ok(true); - } - "concat" => { - this.validate_args_exact("concat", args, 1)?; - let rhs = this.reg_load(args[0])?; - let new_s = format!("{}{}", sb_norm.value, rhs.to_string()); - this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s)))); - return Ok(true); - } - "is_digit_char" => { - // Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test) - let ch_opt = if args.is_empty() { - sb_norm.value.chars().next() - } else if args.len() == 1 { - let s = this.reg_load(args[0])?.to_string(); - s.chars().next() - } else { - return Err(this.err_invalid("is_digit_char expects 0 or 1 arg")); - }; - let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false); - this.write_result(dst, VMValue::Bool(is_digit)); - return Ok(true); - } - "is_hex_digit_char" => { - let ch_opt = if args.is_empty() { - sb_norm.value.chars().next() - } else if args.len() == 1 { - let s = this.reg_load(args[0])?.to_string(); - s.chars().next() - } else { - return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg")); - }; - let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false); - this.write_result(dst, VMValue::Bool(is_hex)); - return Ok(true); - } - _ => {} + "replace" => { + // replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal + this.validate_args_exact("replace", args, 2)?; + let old_s = this.reg_load(args[0])?.to_string(); + let new_s = this.reg_load(args[1])?.to_string(); + // Core policy: replace only the first occurrence + let out = if let Some(pos) = sb_norm.value.find(&old_s) { + let mut s = String::with_capacity(sb_norm.value.len() + new_s.len()); + s.push_str(&sb_norm.value[..pos]); + s.push_str(&new_s); + s.push_str(&sb_norm.value[pos + old_s.len()..]); + s + } else { + sb_norm.value.clone() + }; + this.write_result( + dst, + VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))), + ); + return Ok(true); } - Ok(false) + "trim" => { + let ret = sb_norm.trim(); + this.write_result(dst, VMValue::from_nyash_box(ret)); + return Ok(true); + } + "indexOf" => { + // Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex) + let (needle, from_index) = match args.len() { + 1 => { + // indexOf(search) - search from beginning + let n = this.reg_load(args[0])?.to_string(); + (n, 0) + } + 2 => { + // indexOf(search, fromIndex) - search from specified position + let n = this.reg_load(args[0])?.to_string(); + let from = this.reg_load(args[1])?.as_integer().unwrap_or(0); + (n, from.max(0) as usize) + } + _ => { + return Err( + this.err_invalid("indexOf expects 1 or 2 args (search [, fromIndex])") + ); + } + }; + + // Search for needle starting from from_index + let search_str = if from_index >= sb_norm.value.len() { + "" + } else { + &sb_norm.value[from_index..] + }; + + let idx = search_str + .find(&needle) + .map(|i| (from_index + i) as i64) + .unwrap_or(-1); + + this.write_result(dst, VMValue::Integer(idx)); + return Ok(true); + } + "contains" => { + // contains(search) -> boolean (true if found, false otherwise) + // Implemented as indexOf(search) >= 0 + this.validate_args_exact("contains", args, 1)?; + let needle = this.reg_load(args[0])?.to_string(); + let found = sb_norm.value.contains(&needle); + this.write_result(dst, VMValue::Bool(found)); + return Ok(true); + } + "lastIndexOf" => { + // lastIndexOf(substr) -> last index or -1 + this.validate_args_exact("lastIndexOf", args, 1)?; + let needle = this.reg_load(args[0])?.to_string(); + let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1); + this.write_result(dst, VMValue::Integer(idx)); + return Ok(true); + } + "stringify" => { + // JSON-style stringify for strings: quote and escape common characters + let mut quoted = String::with_capacity(sb_norm.value.len() + 2); + quoted.push('"'); + for ch in sb_norm.value.chars() { + match ch { + '"' => quoted.push_str("\\\""), + '\\' => quoted.push_str("\\\\"), + '\n' => quoted.push_str("\\n"), + '\r' => quoted.push_str("\\r"), + '\t' => quoted.push_str("\\t"), + c if c.is_control() => quoted.push(' '), + c => quoted.push(c), + } + } + quoted.push('"'); + this.write_result( + dst, + VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))), + ); + return Ok(true); + } + "substring" => { + // Support both 1-arg (start to end) and 2-arg (start, end) forms + let (s_idx, e_idx) = match args.len() { + 1 => { + // substring(start) - from start to end of string + let s = this.reg_load(args[0])?.as_integer().unwrap_or(0); + let len = sb_norm.value.chars().count() as i64; + (s, len) + } + 2 => { + // substring(start, end) - half-open interval [start, end) + let s = this.reg_load(args[0])?.as_integer().unwrap_or(0); + let e = this.reg_load(args[1])?.as_integer().unwrap_or(0); + (s, e) + } + _ => { + return Err(this.err_invalid("substring expects 1 or 2 args (start [, end])")); + } + }; + let len = sb_norm.value.chars().count() as i64; + let start = s_idx.max(0).min(len) as usize; + let end = e_idx.max(start as i64).min(len) as usize; + let chars: Vec = sb_norm.value.chars().collect(); + let sub: String = chars[start..end].iter().collect(); + this.write_result( + dst, + VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))), + ); + return Ok(true); + } + "concat" => { + this.validate_args_exact("concat", args, 1)?; + let rhs = this.reg_load(args[0])?; + let new_s = format!("{}{}", sb_norm.value, rhs.to_string()); + this.write_result( + dst, + VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))), + ); + return Ok(true); + } + "is_digit_char" => { + // Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test) + let ch_opt = if args.is_empty() { + sb_norm.value.chars().next() + } else if args.len() == 1 { + let s = this.reg_load(args[0])?.to_string(); + s.chars().next() + } else { + return Err(this.err_invalid("is_digit_char expects 0 or 1 arg")); + }; + let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false); + this.write_result(dst, VMValue::Bool(is_digit)); + return Ok(true); + } + "is_hex_digit_char" => { + let ch_opt = if args.is_empty() { + sb_norm.value.chars().next() + } else if args.len() == 1 { + let s = this.reg_load(args[0])?.to_string(); + s.chars().next() + } else { + return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg")); + }; + let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false); + this.write_result(dst, VMValue::Bool(is_hex)); + return Ok(true); + } + _ => {} + } + Ok(false) } diff --git a/src/backend/mir_interpreter/handlers/calls/externs.rs b/src/backend/mir_interpreter/handlers/calls/externs.rs index 5f7de538..2a60da53 100644 --- a/src/backend/mir_interpreter/handlers/calls/externs.rs +++ b/src/backend/mir_interpreter/handlers/calls/externs.rs @@ -22,9 +22,15 @@ impl MirInterpreter { match &v { VMValue::Void => println!("null"), VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { + if bx + .as_any() + .downcast_ref::() + .is_some() + { println!("null"); - } else if let Some(sb) = bx.as_any().downcast_ref::() { + } else if let Some(sb) = + bx.as_any().downcast_ref::() + { println!("{}", sb.value); } else { println!("{}", v.to_string()); @@ -56,8 +62,12 @@ impl MirInterpreter { }; panic!("{}", msg); } - "hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")), - _ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))), + "hostbridge.extern_invoke" => Err(self.err_invalid( + "hostbridge.extern_invoke should be routed via extern_provider_dispatch", + )), + _ => { + Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))) + } } } } diff --git a/src/backend/mir_interpreter/handlers/calls/global.rs b/src/backend/mir_interpreter/handlers/calls/global.rs index 2574fc14..429e1c89 100644 --- a/src/backend/mir_interpreter/handlers/calls/global.rs +++ b/src/backend/mir_interpreter/handlers/calls/global.rs @@ -12,7 +12,9 @@ impl MirInterpreter { // Module-local/global function: execute by function table if present (use original name) if let Some(func) = self.functions.get(func_name).cloned() { let mut argv: Vec = Vec::with_capacity(args.len()); - for a in args { argv.push(self.reg_load(*a)?); } + for a in args { + argv.push(self.reg_load(*a)?); + } return self.exec_function_inner(&func, Some(&argv)); } @@ -60,13 +62,19 @@ impl MirInterpreter { let s = self.reg_load(*a0)?.to_string(); let opts = crate::host_providers::llvm_codegen::Opts { out: None, - nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from), - opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())), + nyrt: std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .map(std::path::PathBuf::from), + opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL") + .ok() + .or(Some("0".to_string())), timeout_ms: None, }; match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) { Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), - Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())), + Err(e) => { + Err(self.err_with_context("env.codegen.emit_object", &e.to_string())) + } } } else { Err(self.err_invalid("env.codegen.emit_object expects 1 arg")) @@ -74,32 +82,51 @@ impl MirInterpreter { } "env.codegen.link_object" | "env.codegen.link_object/3" => { // C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?] - if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || - std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { + if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") + || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + != Some("1") + { return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } - if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); } + if args.len() < 3 { + return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); + } let v = self.reg_load(args[2])?; let (obj_path, exe_out) = match v { VMValue::BoxRef(b) => { - if let Some(ab) = b.as_any().downcast_ref::() { - let idx0: Box = Box::new(crate::box_trait::IntegerBox::new(0)); + if let Some(ab) = b.as_any().downcast_ref::() + { + let idx0: Box = + Box::new(crate::box_trait::IntegerBox::new(0)); let elem0 = ab.get(idx0).to_string_box().value; let mut exe: Option = None; - let idx1: Box = Box::new(crate::box_trait::IntegerBox::new(1)); + let idx1: Box = + Box::new(crate::box_trait::IntegerBox::new(1)); let e1 = ab.get(idx1).to_string_box().value; - if !e1.is_empty() { exe = Some(e1); } + if !e1.is_empty() { + exe = Some(e1); + } (elem0, exe) - } else { (b.to_string_box().value, None) } + } else { + (b.to_string_box().value, None) + } } _ => (v.to_string(), None), }; let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(obj_path); - let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); - match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { + let exe = exe_out + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); + match crate::host_providers::llvm_codegen::link_object_capi( + &obj, + &exe, + extra.as_deref(), + ) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())) + Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())), } } "nyash.builtin.error" => { diff --git a/src/backend/mir_interpreter/handlers/calls/method.rs b/src/backend/mir_interpreter/handlers/calls/method.rs index d5b4d263..6feaef8d 100644 --- a/src/backend/mir_interpreter/handlers/calls/method.rs +++ b/src/backend/mir_interpreter/handlers/calls/method.rs @@ -22,18 +22,40 @@ impl MirInterpreter { if let Some(block) = func.blocks.get(&bb) { let mut last_recv: Option = None; for inst in &block.instructions { - if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = inst { - if box_type == box_name { last_recv = Some(*dst); } + if let crate::mir::MirInstruction::NewBox { + dst, + box_type, + .. + } = inst + { + if box_type == box_name { + last_recv = Some(*dst); + } } } if let Some(rid) = last_recv { - if let Ok(v) = self.reg_load(rid) { v } else { return Err(e); } + if let Ok(v) = self.reg_load(rid) { + v + } else { + return Err(e); + } } else { // Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed - let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1") - || std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1"); - if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } } - else { return Err(e); } + let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK") + .ok() + .as_deref() + == Some("1") + || std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() + == Some("1"); + if tolerate { + if let Some(a0) = args.get(0) { + self.reg_load(*a0)? + } else { + return Err(e); + } + } else { + return Err(e); + } } } else { return Err(e); @@ -43,10 +65,18 @@ impl MirInterpreter { } } else { // Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed - let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1") + let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() + == Some("1") || std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1"); - if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } } - else { return Err(e); } + if tolerate { + if let Some(a0) = args.get(0) { + self.reg_load(*a0)? + } else { + return Err(e); + } + } else { + return Err(e); + } } } }; @@ -57,7 +87,9 @@ impl MirInterpreter { // ArrayBox bridge if let Some(arr) = bx.as_any().downcast_ref::() { match method { - "birth" => { return Ok(VMValue::Void); } + "birth" => { + return Ok(VMValue::Void); + } "push" => { if let Some(a0) = args.get(0) { let v = self.load_as_box(*a0)?; @@ -162,14 +194,20 @@ impl MirInterpreter { "substring" => { let start = if let Some(a0) = args.get(0) { self.reg_load(*a0)?.as_integer().unwrap_or(0) - } else { 0 }; + } else { + 0 + }; let end = if let Some(a1) = args.get(1) { self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64) - } else { s.len() as i64 }; + } else { + s.len() as i64 + }; let len = s.len() as i64; let i0 = start.max(0).min(len) as usize; let i1 = end.max(0).min(len) as usize; - if i0 > i1 { return Ok(VMValue::String(String::new())); } + if i0 > i1 { + return Ok(VMValue::String(String::new())); + } // Note: operating on bytes; Nyash strings are UTF‑8, but tests are ASCII only here let bytes = s.as_bytes(); let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default(); @@ -209,8 +247,7 @@ impl MirInterpreter { "is_space" => { if let Some(arg_id) = args.get(0) { let ch = self.reg_load(*arg_id)?.to_string(); - let is_ws = - ch == " " || ch == "\t" || ch == "\n" || ch == "\r"; + let is_ws = ch == " " || ch == "\t" || ch == "\n" || ch == "\r"; Ok(VMValue::Bool(is_ws)) } else { Err(self.err_invalid("is_space requires 1 argument")) @@ -221,10 +258,9 @@ impl MirInterpreter { if let Some(arg_id) = args.get(0) { let ch = self.reg_load(*arg_id)?.to_string(); let c = ch.chars().next().unwrap_or('\0'); - let is_alpha = - ('A'..='Z').contains(&c) || - ('a'..='z').contains(&c) || - c == '_'; + let is_alpha = ('A'..='Z').contains(&c) + || ('a'..='z').contains(&c) + || c == '_'; Ok(VMValue::Bool(is_alpha)) } else { Err(self.err_invalid("is_alpha requires 1 argument")) @@ -234,8 +270,8 @@ impl MirInterpreter { } } else if let Some(p) = box_ref .as_any() - .downcast_ref::() - { + .downcast_ref::( + ) { let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); let host = host.read().unwrap(); let argv = self.load_args_as_boxes(args)?; @@ -249,14 +285,17 @@ impl MirInterpreter { Ok(None) => Ok(VMValue::Void), Err(e) => Err(self.err_with_context( &format!("Plugin method {}.{}", p.box_type, method), - &format!("{:?}", e) + &format!("{:?}", e), )), } } else { Err(self.err_method_not_found(&box_ref.type_name(), method)) } } - _ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))), + _ => Err(self.err_with_context( + "method call", + &format!("{} not supported on {:?}", method, receiver), + )), } } } diff --git a/src/backend/mir_interpreter/handlers/calls/mod.rs b/src/backend/mir_interpreter/handlers/calls/mod.rs index 96aac92c..b5863630 100644 --- a/src/backend/mir_interpreter/handlers/calls/mod.rs +++ b/src/backend/mir_interpreter/handlers/calls/mod.rs @@ -4,9 +4,9 @@ use super::*; +mod externs; mod global; mod method; -mod externs; // legacy by-name resolver has been removed (Phase 2 complete) impl MirInterpreter { @@ -19,18 +19,43 @@ impl MirInterpreter { ) -> Result<(), VMError> { if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") { match callee { - Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()), - Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()), - Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()), - Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()), - Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()), - Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()), - None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()), + Some(Callee::Global(n)) => { + eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()) + } + Some(Callee::Method { + box_name, method, .. + }) => eprintln!( + "[hb:path] call Callee::Method {}.{} argc={}", + box_name, + method, + args.len() + ), + Some(Callee::Constructor { box_type }) => eprintln!( + "[hb:path] call Callee::Constructor {} argc={}", + box_type, + args.len() + ), + Some(Callee::Closure { .. }) => { + eprintln!("[hb:path] call Callee::Closure argc={}", args.len()) + } + Some(Callee::Value(_)) => { + eprintln!("[hb:path] call Callee::Value argc={}", args.len()) + } + Some(Callee::Extern(n)) => { + eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()) + } + None => eprintln!( + "[hb:path] call Legacy func_id={:?} argc={}", + func, + args.len() + ), } } // SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form if let Some(Callee::Global(func_name)) = callee { - if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") { + if func_name == "hostbridge.extern_invoke" + || func_name.starts_with("hostbridge.extern_invoke/") + { let v = self.execute_extern_function("hostbridge.extern_invoke", args)?; self.write_result(dst, v); return Ok(()); @@ -44,7 +69,9 @@ impl MirInterpreter { if let VMValue::String(ref s) = name_val { if let Some(f) = self.functions.get(s).cloned() { let mut argv: Vec = Vec::with_capacity(args.len()); - for a in args { argv.push(self.reg_load(*a)?); } + for a in args { + argv.push(self.reg_load(*a)?); + } self.exec_function_inner(&f, Some(&argv))? } else { return Err(self.err_with_context("call", &format!( @@ -53,7 +80,10 @@ impl MirInterpreter { ))); } } else { - return Err(self.err_with_context("call", "by-name calls unsupported without Callee attachment")); + return Err(self.err_with_context( + "call", + "by-name calls unsupported without Callee attachment", + )); } }; self.write_result(dst, call_result); @@ -67,10 +97,15 @@ impl MirInterpreter { ) -> Result { match callee { Callee::Global(func_name) => self.execute_global_function(func_name, args), - Callee::Method { box_name, method, receiver, .. } => { - self.execute_method_callee(box_name, method, receiver, args) + Callee::Method { + box_name, + method, + receiver, + .. + } => self.execute_method_callee(box_name, method, receiver, args), + Callee::Constructor { box_type } => { + Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))) } - Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))), Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")), Callee::Value(func_val_id) => { let _ = self.reg_load(*func_val_id)?; @@ -79,5 +114,4 @@ impl MirInterpreter { Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args), } } - } diff --git a/src/backend/mir_interpreter/handlers/extern_provider.rs b/src/backend/mir_interpreter/handlers/extern_provider.rs index 91d22bd3..b68a7cc2 100644 --- a/src/backend/mir_interpreter/handlers/extern_provider.rs +++ b/src/backend/mir_interpreter/handlers/extern_provider.rs @@ -10,8 +10,12 @@ impl MirInterpreter { let key = format!("{}.{}", target, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -23,7 +27,9 @@ impl MirInterpreter { if let JsonValue::Object(ref mut m) = v { if !m.contains_key("version") { m.insert("version".to_string(), JsonValue::from(0)); - if let Ok(out) = serde_json::to_string(&v) { return out; } + if let Ok(out) = serde_json::to_string(&v) { + return out; + } } } s.to_string() @@ -64,9 +70,15 @@ impl MirInterpreter { VMValue::Void => println!("null"), VMValue::String(s) => println!("{}", s), VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { + if bx + .as_any() + .downcast_ref::() + .is_some() + { println!("null"); - } else if let Some(sb) = bx.as_any().downcast_ref::() { + } else if let Some(sb) = + bx.as_any().downcast_ref::() + { println!("{}", sb.value); } else { println!("{}", v.to_string()); @@ -90,9 +102,15 @@ impl MirInterpreter { VMValue::Void => eprintln!("[warn] null"), VMValue::String(s) => eprintln!("[warn] {}", s), VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { + if bx + .as_any() + .downcast_ref::() + .is_some() + { eprintln!("[warn] null"); - } else if let Some(sb) = bx.as_any().downcast_ref::() { + } else if let Some(sb) = + bx.as_any().downcast_ref::() + { eprintln!("[warn] {}", sb.value); } else { eprintln!("[warn] {}", v.to_string()); @@ -116,9 +134,15 @@ impl MirInterpreter { VMValue::Void => eprintln!("[error] null"), VMValue::String(s) => eprintln!("[error] {}", s), VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { + if bx + .as_any() + .downcast_ref::() + .is_some() + { eprintln!("[error] null"); - } else if let Some(sb) = bx.as_any().downcast_ref::() { + } else if let Some(sb) = + bx.as_any().downcast_ref::() + { eprintln!("[error] {}", sb.value); } else { eprintln!("[error] {}", v.to_string()); @@ -140,15 +164,29 @@ impl MirInterpreter { if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") { return Some(Ok(VMValue::String(String::new()))); } - if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.mirbuilder.emit", 1, args.len()))); } - let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + if args.is_empty() { + return Some(Err(ErrorBuilder::arg_count_mismatch( + "env.mirbuilder.emit", + 1, + args.len(), + ))); + } + let program_json = match self.reg_load(args[0]) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }; // Phase 21.8: Read imports from environment variable if present let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") { - match serde_json::from_str::>(&imports_json) { + match serde_json::from_str::>( + &imports_json, + ) { Ok(map) => map, Err(e) => { - eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e); + eprintln!( + "[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", + e + ); std::collections::HashMap::new() } } @@ -156,10 +194,17 @@ impl MirInterpreter { std::collections::HashMap::new() }; - let res = match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(&program_json, imports) { - Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))), - Err(e) => Err(ErrorBuilder::with_context("env.mirbuilder.emit", &e.to_string())), - }; + let res = + match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports( + &program_json, + imports, + ) { + Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))), + Err(e) => Err(ErrorBuilder::with_context( + "env.mirbuilder.emit", + &e.to_string(), + )), + }; Some(res) } "env.codegen.emit_object" => { @@ -167,55 +212,114 @@ impl MirInterpreter { if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") { return Some(Ok(VMValue::String(String::new()))); } - if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.codegen.emit_object", 1, args.len()))); } - let mir_json_raw = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + if args.is_empty() { + return Some(Err(ErrorBuilder::arg_count_mismatch( + "env.codegen.emit_object", + 1, + args.len(), + ))); + } + let mir_json_raw = match self.reg_load(args[0]) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }; // Normalize to v1 shape if missing/legacy (prevents harness NoneType errors) let mir_json = Self::patch_mir_json_version(&mir_json_raw); let opts = crate::host_providers::llvm_codegen::Opts { out: None, - nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from), - opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())), + nyrt: std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .map(std::path::PathBuf::from), + opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL") + .ok() + .or(Some("0".to_string())), timeout_ms: None, }; - let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { + let res = match crate::host_providers::llvm_codegen::mir_json_to_object( + &mir_json, opts, + ) { Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), - Err(e) => Err(ErrorBuilder::with_context("env.codegen.emit_object", &e.to_string())), + Err(e) => Err(ErrorBuilder::with_context( + "env.codegen.emit_object", + &e.to_string(), + )), }; Some(res) } "env.codegen.link_object" => { // Only supported on C-API route; expect 1 or 2 args: obj_path [, exe_out] let obj_path = match args.get(0) { - Some(v) => match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }, - None => return Some(Err(self.err_invalid("env.codegen.link_object expects 1+ args"))), + Some(v) => match self.reg_load(*v) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }, + None => { + return Some(Err( + self.err_invalid("env.codegen.link_object expects 1+ args") + )) + } }; let exe_out = match args.get(1) { - Some(v) => Some(match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }), + Some(v) => Some(match self.reg_load(*v) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }), None => None, }; let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); // Require C-API toggles - if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || - std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); + if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") + || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + != Some("1") + { + return Some(Err(ErrorBuilder::invalid_instruction( + "env.codegen.link_object: C-API route disabled", + ))); } let obj = std::path::PathBuf::from(obj_path); - let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); - match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { + let exe = exe_out + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); + match crate::host_providers::llvm_codegen::link_object_capi( + &obj, + &exe, + extra.as_deref(), + ) { Ok(()) => Some(Ok(VMValue::String(exe.to_string_lossy().into_owned()))), - Err(e) => Some(Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))), + Err(e) => Some(Err(ErrorBuilder::with_context( + "env.codegen.link_object", + &e.to_string(), + ))), } } // Environment "env.get" => { - if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.get", 1, args.len()))); } - let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + if args.is_empty() { + return Some(Err(ErrorBuilder::arg_count_mismatch( + "env.get", + 1, + args.len(), + ))); + } + let key = match self.reg_load(args[0]) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }; let val = std::env::var(&key).ok(); - Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void })) + Some(Ok(match val { + Some(s) => VMValue::String(s), + None => VMValue::Void, + })) } "env.set" => { if args.len() < 2 { - return Some(Err(ErrorBuilder::arg_count_mismatch("env.set", 2, args.len()))); + return Some(Err(ErrorBuilder::arg_count_mismatch( + "env.set", + 2, + args.len(), + ))); } let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), @@ -247,24 +351,24 @@ impl MirInterpreter { } } } else { - return Some(Err(self.err_invalid( - "env.box_introspect.kind expects 1 arg", - ))); + return Some(Err( + self.err_invalid("env.box_introspect.kind expects 1 arg") + )); } #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] let result = plugin_loader_v2::handle_box_introspect("kind", &collected); #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] - let result: crate::bid::BidResult>> = - Err(crate::bid::BidError::PluginError); + let result: crate::bid::BidResult< + Option>, + > = Err(crate::bid::BidError::PluginError); match result { Ok(Some(b)) => Some(Ok(VMValue::BoxRef(Arc::from(b)))), Ok(None) => Some(Ok(VMValue::Void)), - Err(e) => Some(Err(self.err_with_context( - "env.box_introspect.kind", - &format!("{:?}", e), - ))), + Err(e) => Some(Err( + self.err_with_context("env.box_introspect.kind", &format!("{:?}", e)) + )), } } "hostbridge.extern_invoke" => { @@ -272,17 +376,30 @@ impl MirInterpreter { eprintln!("[hb:entry:provider] hostbridge.extern_invoke"); } if args.len() < 2 { - return Some(Err(self.err_invalid("extern_invoke expects at least 2 args"))); + return Some(Err( + self.err_invalid("extern_invoke expects at least 2 args") + )); } - let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; - let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + let name = match self.reg_load(args[0]) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }; + let method = match self.reg_load(args[1]) { + Ok(v) => v.to_string(), + Err(e) => return Some(Err(e)), + }; // Extract first payload arg (optional) let mut first_arg_str: Option = None; if let Some(a2) = args.get(2) { - let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) }; + let v = match self.reg_load(*a2) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; match v { VMValue::BoxRef(b) => { - if let Some(ab) = b.as_any().downcast_ref::() { + if let Some(ab) = + b.as_any().downcast_ref::() + { let idx: Box = Box::new(crate::box_trait::IntegerBox::new(0)); let elem = ab.get(idx); @@ -299,16 +416,27 @@ impl MirInterpreter { eprintln!("[hb:dispatch:provider] {} {}", name, method); } let out = match (name.as_str(), method.as_str()) { - ("env.codegen", "link_object") if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") => { + ("env.codegen", "link_object") + if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") => + { // Trace payload shape before actual handling if let Some(a2) = args.get(2) { - let v = match self.reg_load(*a2) { Ok(v) => v, Err(_) => VMValue::Void }; + let v = match self.reg_load(*a2) { + Ok(v) => v, + Err(_) => VMValue::Void, + }; match &v { VMValue::BoxRef(b) => { - if b.as_any().downcast_ref::().is_some() { + if b.as_any() + .downcast_ref::() + .is_some() + { eprintln!("[hb:provider:args] link_object third=ArrayBox"); } else { - eprintln!("[hb:provider:args] link_object third=BoxRef({})", b.type_name()); + eprintln!( + "[hb:provider:args] link_object third=BoxRef({})", + b.type_name() + ); } } other => { @@ -327,13 +455,19 @@ impl MirInterpreter { }; match v { VMValue::BoxRef(b) => { - if let Some(ab) = b.as_any().downcast_ref::() { - let idx0: Box = Box::new(crate::box_trait::IntegerBox::new(0)); + if let Some(ab) = + b.as_any().downcast_ref::() + { + let idx0: Box = + Box::new(crate::box_trait::IntegerBox::new(0)); let elem0 = ab.get(idx0).to_string_box().value; let mut exe: Option = None; - let idx1: Box = Box::new(crate::box_trait::IntegerBox::new(1)); + let idx1: Box = + Box::new(crate::box_trait::IntegerBox::new(1)); let e1 = ab.get(idx1).to_string_box().value; - if !e1.is_empty() { exe = Some(e1); } + if !e1.is_empty() { + exe = Some(e1); + } (elem0, exe) } else { (b.to_string_box().value, None) @@ -342,25 +476,47 @@ impl MirInterpreter { _ => (v.to_string(), None), } } else { - return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array"))); + return Some(Err(self.err_invalid( + "extern_invoke env.codegen.link_object expects args array", + ))); }; - if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || - std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); + if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") + || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + != Some("1") + { + return Some(Err(ErrorBuilder::invalid_instruction( + "env.codegen.link_object: C-API route disabled", + ))); } let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(objs); - let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); - match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { + let exe = exe_out + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); + match crate::host_providers::llvm_codegen::link_object_capi( + &obj, + &exe, + extra.as_deref(), + ) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string())) + Err(e) => Err(ErrorBuilder::with_context( + "env.codegen.link_object", + &e.to_string(), + )), } } ("env.mirbuilder", "emit") => { if let Some(s) = first_arg_str { // Phase 21.8: Read imports from environment variable if present - let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") { - match serde_json::from_str::>(&imports_json) { + let imports = if let Ok(imports_json) = + std::env::var("HAKO_MIRBUILDER_IMPORTS") + { + match serde_json::from_str::< + std::collections::HashMap, + >(&imports_json) + { Ok(map) => map, Err(e) => { eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e); @@ -383,16 +539,21 @@ impl MirInterpreter { if let Some(s) = first_arg_str { let opts = crate::host_providers::llvm_codegen::Opts { out: None, - nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from), + nyrt: std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .map(std::path::PathBuf::from), opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(), timeout_ms: None, }; - match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) { + match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) + { Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), - Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())), + Err(e) => Err(self + .err_with_context("env.codegen.emit_object", &e.to_string())), } } else { - Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg")) + Err(self + .err_invalid("extern_invoke env.codegen.emit_object expects 1 arg")) } } ("env.codegen", "link_object") => { @@ -408,12 +569,18 @@ impl MirInterpreter { }; match v { VMValue::BoxRef(b) => { - if let Some(ab) = b.as_any().downcast_ref::() { - let idx0: Box = Box::new(crate::box_trait::IntegerBox::new(0)); + if let Some(ab) = + b.as_any().downcast_ref::() + { + let idx0: Box = + Box::new(crate::box_trait::IntegerBox::new(0)); obj_s = Some(ab.get(idx0).to_string_box().value); - let idx1: Box = Box::new(crate::box_trait::IntegerBox::new(1)); + let idx1: Box = + Box::new(crate::box_trait::IntegerBox::new(1)); let s1 = ab.get(idx1).to_string_box().value; - if !s1.is_empty() { exe_s = Some(s1); } + if !s1.is_empty() { + exe_s = Some(s1); + } } else { obj_s = Some(b.to_string_box().value); } @@ -426,18 +593,37 @@ impl MirInterpreter { } let objs = match obj_s { Some(s) => s, - None => return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args"))), + None => { + return Some(Err(self.err_invalid( + "extern_invoke env.codegen.link_object expects args", + ))) + } }; - if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || - std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); + if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") + || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + != Some("1") + { + return Some(Err(ErrorBuilder::invalid_instruction( + "env.codegen.link_object: C-API route disabled", + ))); } let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(objs); - let exe = exe_s.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); - match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { + let exe = exe_s + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); + match crate::host_providers::llvm_codegen::link_object_capi( + &obj, + &exe, + extra.as_deref(), + ) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string())) + Err(e) => Err(ErrorBuilder::with_context( + "env.codegen.link_object", + &e.to_string(), + )), } } ("env.box_introspect", "kind") => { @@ -487,9 +673,7 @@ impl MirInterpreter { } } other => { - if std::env::var("NYASH_BOX_INTROSPECT_TRACE") - .ok() - .as_deref() + if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref() == Some("1") { eprintln!( @@ -509,16 +693,15 @@ impl MirInterpreter { #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] let result = plugin_loader_v2::handle_box_introspect("kind", &collected); #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] - let result: crate::bid::BidResult>> = - Err(crate::bid::BidError::PluginError); + let result: crate::bid::BidResult< + Option>, + > = Err(crate::bid::BidError::PluginError); match result { Ok(Some(b)) => Ok(VMValue::BoxRef(Arc::from(b))), Ok(None) => Ok(VMValue::Void), - Err(e) => Err(self.err_with_context( - "env.box_introspect.kind", - &format!("{:?}", e), - )), + Err(e) => Err(self + .err_with_context("env.box_introspect.kind", &format!("{:?}", e))), } } _ => { @@ -529,7 +712,7 @@ impl MirInterpreter { "hostbridge.extern_invoke unsupported for {}.{} [provider] (check extern_provider_dispatch / env.* handlers)", name, method ))) - }, + } }; Some(out) } diff --git a/src/backend/mir_interpreter/handlers/externals.rs b/src/backend/mir_interpreter/handlers/externals.rs index 2f5b3a19..3ceba8a4 100644 --- a/src/backend/mir_interpreter/handlers/externals.rs +++ b/src/backend/mir_interpreter/handlers/externals.rs @@ -10,7 +10,9 @@ impl MirInterpreter { if let JsonValue::Object(ref mut m) = v { if !m.contains_key("version") { m.insert("version".to_string(), JsonValue::from(0)); - if let Ok(out) = serde_json::to_string(&v) { return out; } + if let Ok(out) = serde_json::to_string(&v) { + return out; + } } } s.to_string() @@ -52,23 +54,47 @@ impl MirInterpreter { if let Some(a0) = args.get(0) { let v = self.reg_load(*a0)?; // Dev-only: mirror print-trace for extern console.log - if Self::print_trace_enabled() { self.print_trace_emit(&v); } + if Self::print_trace_enabled() { + self.print_trace_emit(&v); + } // Treat VM Void and BoxRef(VoidBox) as JSON null for dev ergonomics match &v { - VMValue::Void => { println!("null"); self.write_void(dst); return Ok(()); } + VMValue::Void => { + println!("null"); + self.write_void(dst); + return Ok(()); + } VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { - println!("null"); self.write_void(dst); return Ok(()); + if bx + .as_any() + .downcast_ref::() + .is_some() + { + println!("null"); + self.write_void(dst); + return Ok(()); } - if let Some(sb) = bx.as_any().downcast_ref::() { - println!("{}", sb.value); self.write_void(dst); return Ok(()); + if let Some(sb) = + bx.as_any().downcast_ref::() + { + println!("{}", sb.value); + self.write_void(dst); + return Ok(()); } } - VMValue::String(s) => { println!("{}", s); self.write_void(dst); return Ok(()); } + VMValue::String(s) => { + println!("{}", s); + self.write_void(dst); + return Ok(()); + } _ => {} } // Operator Box (Stringify) – dev flag gated - if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY").ok().as_deref() == Some("1") { + if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY") + .ok() + .as_deref() + == Some("1") + { if let Some(op) = self.functions.get("StringifyOperator.apply/1").cloned() { let out = self.exec_function_inner(&op, Some(&[v.clone()]))?; println!("{}", out.to_string()); @@ -151,20 +177,28 @@ impl MirInterpreter { Ok(()) } ("env.mirbuilder", "emit") => { - let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?; + let ret = self + .extern_provider_dispatch("env.mirbuilder.emit", args) + .unwrap_or(Ok(VMValue::Void))?; self.write_result(dst, ret); Ok(()) } ("env.codegen", "emit_object") => { - let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?; + let ret = self + .extern_provider_dispatch("env.codegen.emit_object", args) + .unwrap_or(Ok(VMValue::Void))?; self.write_result(dst, ret); Ok(()) } ("env.codegen", "link_object") => { // Args in third param (ArrayBox): [obj_path, exe_out?] // Note: This branch is used for ExternCall form; provider toggles must be ON. - if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || - std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { + if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") + || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + != Some("1") + { return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } // Extract array payload @@ -172,13 +206,19 @@ impl MirInterpreter { let v = self.reg_load(*a2)?; match v { VMValue::BoxRef(b) => { - if let Some(ab) = b.as_any().downcast_ref::() { - let idx0: Box = Box::new(crate::box_trait::IntegerBox::new(0)); + if let Some(ab) = + b.as_any().downcast_ref::() + { + let idx0: Box = + Box::new(crate::box_trait::IntegerBox::new(0)); let elem0 = ab.get(idx0).to_string_box().value; let mut exe: Option = None; - let idx1: Box = Box::new(crate::box_trait::IntegerBox::new(1)); + let idx1: Box = + Box::new(crate::box_trait::IntegerBox::new(1)); let e1 = ab.get(idx1).to_string_box().value; - if !e1.is_empty() { exe = Some(e1); } + if !e1.is_empty() { + exe = Some(e1); + } (elem0, exe) } else { (b.to_string_box().value, None) @@ -187,13 +227,18 @@ impl MirInterpreter { _ => (v.to_string(), None), } } else { - return Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array")); + return Err(self + .err_invalid("extern_invoke env.codegen.link_object expects args array")); }; let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(obj_path); - let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); + let exe = exe_out + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) - .map_err(|e| self.err_with_context("env.codegen.link_object", &e.to_string()))?; + .map_err(|e| { + self.err_with_context("env.codegen.link_object", &e.to_string()) + })?; self.write_result(dst, VMValue::String(exe.to_string_lossy().into_owned())); Ok(()) } @@ -208,17 +253,18 @@ impl MirInterpreter { ("hostbridge", "extern_invoke") => { if let Some(res) = self.extern_provider_dispatch("hostbridge.extern_invoke", args) { match res { - Ok(v) => { self.write_result(dst, v); } - Err(e) => { return Err(e); } + Ok(v) => { + self.write_result(dst, v); + } + Err(e) => { + return Err(e); + } } return Ok(()); } return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]")); } - _ => Err(self.err_invalid(format!( - "ExternCall {}.{} not supported", - iface, method - ))), + _ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))), } } } diff --git a/src/backend/mir_interpreter/handlers/misc.rs b/src/backend/mir_interpreter/handlers/misc.rs index ef707c64..0d66bd5d 100644 --- a/src/backend/mir_interpreter/handlers/misc.rs +++ b/src/backend/mir_interpreter/handlers/misc.rs @@ -13,16 +13,28 @@ impl MirInterpreter { let v = self.reg_load(value)?; // Align with calls.rs behavior: Void/BoxRef(VoidBox) prints as null; raw String/StringBox unquoted match &v { - VMValue::Void => { println!("null"); return Ok(()); } + VMValue::Void => { + println!("null"); + return Ok(()); + } VMValue::BoxRef(bx) => { - if bx.as_any().downcast_ref::().is_some() { - println!("null"); return Ok(()); + if bx + .as_any() + .downcast_ref::() + .is_some() + { + println!("null"); + return Ok(()); } if let Some(sb) = bx.as_any().downcast_ref::() { - println!("{}", sb.value); return Ok(()); + println!("{}", sb.value); + return Ok(()); } } - VMValue::String(s) => { println!("{}", s); return Ok(()); } + VMValue::String(s) => { + println!("{}", s); + return Ok(()); + } _ => {} } println!("{}", v.to_string()); diff --git a/src/backend/mir_interpreter/handlers/mod.rs b/src/backend/mir_interpreter/handlers/mod.rs index 1472ee53..f7a370d9 100644 --- a/src/backend/mir_interpreter/handlers/mod.rs +++ b/src/backend/mir_interpreter/handlers/mod.rs @@ -3,9 +3,7 @@ use super::*; // VM dispatch trace macro (used across handlers) macro_rules! trace_dispatch { ($method:expr, $handler:expr) => { - if $method == "length" - && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") - { + if $method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { eprintln!("[vm-trace] length dispatch handler={}", $handler); } }; @@ -14,15 +12,15 @@ macro_rules! trace_dispatch { mod arithmetic; mod boxes; mod boxes_array; -mod boxes_string; +mod boxes_instance; mod boxes_map; mod boxes_object_fields; -mod boxes_instance; mod boxes_plugin; +mod boxes_string; mod boxes_void_guards; mod calls; -mod externals; mod extern_provider; +mod externals; mod memory; mod misc; @@ -90,10 +88,7 @@ impl MirInterpreter { } MirInstruction::DebugLog { message, values } => { // Dev-only: MIR-level debug logging (no new values defined). - if std::env::var("NYASH_MIR_DEBUG_LOG") - .ok() - .as_deref() == Some("1") - { + if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() == Some("1") { eprint!("[MIR-LOG] {}:", message); for vid in values { let v = self.reg_load(*vid).unwrap_or(VMValue::Void); diff --git a/src/backend/mir_interpreter/helpers.rs b/src/backend/mir_interpreter/helpers.rs index 7c4f508f..f0ec4ac8 100644 --- a/src/backend/mir_interpreter/helpers.rs +++ b/src/backend/mir_interpreter/helpers.rs @@ -8,10 +8,26 @@ impl MirInterpreter { match v { VMValue::Void => "void", VMValue::BoxRef(b) => { - if b.as_any().downcast_ref::().is_some() { "null" } - else if b.as_any().downcast_ref::().is_some() { "missing" } - else if b.as_any().downcast_ref::().is_some() { "void" } - else { "" } + if b.as_any() + .downcast_ref::() + .is_some() + { + "null" + } else if b + .as_any() + .downcast_ref::() + .is_some() + { + "missing" + } else if b + .as_any() + .downcast_ref::() + .is_some() + { + "void" + } else { + "" + } } _ => "", } @@ -23,11 +39,7 @@ impl MirInterpreter { if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") || std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1") { - let keys: Vec = self - .regs - .keys() - .map(|k| format!("{:?}", k)) - .collect(); + let keys: Vec = self.regs.keys().map(|k| format!("{:?}", k)).collect(); eprintln!( "[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}", id, @@ -39,8 +51,16 @@ impl MirInterpreter { } // Dev-time safety valve: tolerate undefined registers as Void when enabled let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1") - || std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) - || std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false); + || std::env::var("HAKO_PHI_VERIFY") + .ok() + .map(|v| v.to_ascii_lowercase()) + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false) + || std::env::var("NYASH_PHI_VERIFY") + .ok() + .map(|v| v.to_ascii_lowercase()) + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false); if tolerate { return Ok(VMValue::Void); } @@ -87,21 +107,43 @@ impl MirInterpreter { v }; (norm(a), norm(b)) - } else { (a, b) }; + } else { + (a, b) + }; // Dev: nullish trace for binop if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() { - let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a), crate::backend::abi_util::tag_of_vm(&b)); + let (ak, bk) = ( + crate::backend::abi_util::tag_of_vm(&a), + crate::backend::abi_util::tag_of_vm(&b), + ); let (an, bn) = (Self::tag_nullish(&a), Self::tag_nullish(&b)); - let op_s = match op { BinaryOp::Add=>"Add", BinaryOp::Sub=>"Sub", BinaryOp::Mul=>"Mul", BinaryOp::Div=>"Div", BinaryOp::Mod=>"Mod", BinaryOp::BitAnd=>"BitAnd", BinaryOp::BitOr=>"BitOr", BinaryOp::BitXor=>"BitXor", BinaryOp::And=>"And", BinaryOp::Or=>"Or", BinaryOp::Shl=>"Shl", BinaryOp::Shr=>"Shr" }; + let op_s = match op { + BinaryOp::Add => "Add", + BinaryOp::Sub => "Sub", + BinaryOp::Mul => "Mul", + BinaryOp::Div => "Div", + BinaryOp::Mod => "Mod", + BinaryOp::BitAnd => "BitAnd", + BinaryOp::BitOr => "BitOr", + BinaryOp::BitXor => "BitXor", + BinaryOp::And => "And", + BinaryOp::Or => "Or", + BinaryOp::Shl => "Shl", + BinaryOp::Shr => "Shr", + }; eprintln!("{{\"ev\":\"binop\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn); } Ok(match (op, a, b) { // Dev-only safety valves for Add (guarded by tolerance or --dev): // - Treat Void as 0 for numeric + // - Treat Void as empty string for string + - (Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => Integer(y), + (Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => { + Integer(y) + } (Add, VMValue::Void, Float(y)) | (Add, Float(y), VMValue::Void) if tolerate => Float(y), - (Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => String(s), + (Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => { + String(s) + } // Dev-only safety valve for Sub (guarded): treat Void as 0 (Sub, Integer(x), VMValue::Void) if tolerate => Integer(x), (Sub, VMValue::Void, Integer(y)) if tolerate => Integer(0 - y), @@ -129,7 +171,7 @@ impl MirInterpreter { (BitOr, Integer(x), Integer(y)) => Integer(x | y), (BitXor, Integer(x), Integer(y)) => Integer(x ^ y), (And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y), - (Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y), + (Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y), (Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)), (Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)), (opk, va, vb) => { @@ -142,7 +184,7 @@ impl MirInterpreter { return Err(VMError::TypeError(format!( "unsupported binop {:?} on {:?} and {:?}", opk, va, vb - ))) + ))); } }) } @@ -162,7 +204,9 @@ impl MirInterpreter { v }; (norm(a), norm(b)) - } else { (a, b) }; + } else { + (a, b) + }; // Dev-only safety valve: tolerate Void in comparisons when enabled or in --dev // → treat Void as 0 for numeric, empty for string let (a2, b2) = if tolerate { @@ -195,9 +239,19 @@ impl MirInterpreter { }; // Dev: nullish trace for compare if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() { - let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a2), crate::backend::abi_util::tag_of_vm(&b2)); + let (ak, bk) = ( + crate::backend::abi_util::tag_of_vm(&a2), + crate::backend::abi_util::tag_of_vm(&b2), + ); let (an, bn) = (Self::tag_nullish(&a2), Self::tag_nullish(&b2)); - let op_s = match op { CompareOp::Eq=>"Eq", CompareOp::Ne=>"Ne", CompareOp::Lt=>"Lt", CompareOp::Le=>"Le", CompareOp::Gt=>"Gt", CompareOp::Ge=>"Ge" }; + let op_s = match op { + CompareOp::Eq => "Eq", + CompareOp::Ne => "Ne", + CompareOp::Lt => "Lt", + CompareOp::Le => "Le", + CompareOp::Gt => "Gt", + CompareOp::Ge => "Ge", + }; eprintln!("{{\"ev\":\"cmp\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn); } let result = match (op, &a3, &b3) { @@ -230,7 +284,6 @@ impl MirInterpreter { }; Ok(result) } - } // ---- Box trace (dev-only observer) ---- @@ -243,11 +296,15 @@ impl MirInterpreter { fn box_trace_filter_match(class_name: &str) -> bool { if let Ok(filt) = std::env::var("NYASH_BOX_TRACE_FILTER") { let want = filt.trim(); - if want.is_empty() { return true; } + if want.is_empty() { + return true; + } // comma/space separated tokens; match if any token is contained in class for tok in want.split(|c: char| c == ',' || c.is_whitespace()) { let t = tok.trim(); - if !t.is_empty() && class_name.contains(t) { return true; } + if !t.is_empty() && class_name.contains(t) { + return true; + } } false } else { @@ -272,34 +329,49 @@ impl MirInterpreter { } pub(super) fn box_trace_emit_new(&self, class_name: &str, argc: usize) { - if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; } + if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { + return; + } eprintln!( "{{\"ev\":\"new\",\"class\":\"{}\",\"argc\":{}}}", - Self::json_escape(class_name), argc + Self::json_escape(class_name), + argc ); } pub(super) fn box_trace_emit_call(&self, class_name: &str, method: &str, argc: usize) { - if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; } + if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { + return; + } eprintln!( "{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{}}}", - Self::json_escape(class_name), Self::json_escape(method), argc + Self::json_escape(class_name), + Self::json_escape(method), + argc ); } pub(super) fn box_trace_emit_get(&self, class_name: &str, field: &str, val_kind: &str) { - if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; } + if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { + return; + } eprintln!( "{{\"ev\":\"get\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}", - Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind) + Self::json_escape(class_name), + Self::json_escape(field), + Self::json_escape(val_kind) ); } pub(super) fn box_trace_emit_set(&self, class_name: &str, field: &str, val_kind: &str) { - if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; } + if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { + return; + } eprintln!( "{{\"ev\":\"set\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}", - Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind) + Self::json_escape(class_name), + Self::json_escape(field), + Self::json_escape(val_kind) ); } } @@ -312,7 +384,9 @@ impl MirInterpreter { } pub(super) fn print_trace_emit(&self, val: &VMValue) { - if !Self::print_trace_enabled() { return; } + if !Self::print_trace_enabled() { + return; + } let (kind, class, nullish) = match val { VMValue::Integer(_) => ("Integer", "".to_string(), None), VMValue::Float(_) => ("Float", "".to_string(), None), @@ -324,17 +398,43 @@ impl MirInterpreter { // Prefer InstanceBox.class_name when available if let Some(inst) = b.as_any().downcast_ref::() { let tag = if crate::config::env::null_missing_box_enabled() { - if b.as_any().downcast_ref::().is_some() { Some("null") } - else if b.as_any().downcast_ref::().is_some() { Some("missing") } - else { None } - } else { None }; + if b.as_any() + .downcast_ref::() + .is_some() + { + Some("null") + } else if b + .as_any() + .downcast_ref::() + .is_some() + { + Some("missing") + } else { + None + } + } else { + None + }; ("BoxRef", inst.class_name.clone(), tag) } else { let tag = if crate::config::env::null_missing_box_enabled() { - if b.as_any().downcast_ref::().is_some() { Some("null") } - else if b.as_any().downcast_ref::().is_some() { Some("missing") } - else { None } - } else { None }; + if b.as_any() + .downcast_ref::() + .is_some() + { + Some("null") + } else if b + .as_any() + .downcast_ref::() + .is_some() + { + Some("missing") + } else { + None + } + } else { + None + }; ("BoxRef", b.type_name().to_string(), tag) } } diff --git a/src/backend/mir_interpreter/method_router.rs b/src/backend/mir_interpreter/method_router.rs index fc7fd3fd..de0c5167 100644 --- a/src/backend/mir_interpreter/method_router.rs +++ b/src/backend/mir_interpreter/method_router.rs @@ -12,8 +12,8 @@ */ use super::{MirFunction, MirInterpreter}; -use serde_json::json; use crate::backend::vm::{VMError, VMValue}; +use serde_json::json; #[derive(Debug, Clone)] struct ParsedSig<'a> { @@ -25,16 +25,25 @@ struct ParsedSig<'a> { fn parse_method_signature(name: &str) -> Option> { let dot = name.find('.')?; let slash = name.rfind('/')?; - if dot >= slash { return None; } + if dot >= slash { + return None; + } let class = &name[..dot]; let method = &name[dot + 1..slash]; let arity_str = &name[slash + 1..]; - Some(ParsedSig { class, method, arity_str }) + Some(ParsedSig { + class, + method, + arity_str, + }) } fn extract_instance_box_class(arg0: &VMValue) -> Option { if let VMValue::BoxRef(bx) = arg0 { - if let Some(inst) = bx.as_any().downcast_ref::() { + if let Some(inst) = bx + .as_any() + .downcast_ref::() + { return Some(inst.class_name.clone()); } } @@ -47,7 +56,12 @@ fn reroute_to_correct_method( parsed: &ParsedSig<'_>, arg_vals: Option<&[VMValue]>, ) -> Option> { - let target = format!("{}.{}{}", recv_cls, parsed.method, format!("/{}", parsed.arity_str)); + let target = format!( + "{}.{}{}", + recv_cls, + parsed.method, + format!("/{}", parsed.arity_str) + ); if let Some(f) = interp.functions.get(&target).cloned() { // Debug: emit class-reroute event (dev-only) crate::debug::hub::emit( @@ -149,7 +163,10 @@ fn try_special_method( if parsed.method == "is_eof" && parsed.arity_str == "0" { if let Some(args) = arg_vals { if let VMValue::BoxRef(bx) = &args[0] { - if let Some(inst) = bx.as_any().downcast_ref::() { + if let Some(inst) = bx + .as_any() + .downcast_ref::() + { if recv_cls == "JsonToken" { let is = match inst.get_field_ng("type") { Some(crate::value::NyashValue::String(ref s)) => s == "EOF", @@ -158,8 +175,14 @@ fn try_special_method( return Some(Ok(VMValue::Bool(is))); } if recv_cls == "JsonScanner" { - let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 }; - let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 }; + let pos = match inst.get_field_ng("position") { + Some(crate::value::NyashValue::Integer(i)) => i, + _ => 0, + }; + let len = match inst.get_field_ng("length") { + Some(crate::value::NyashValue::Integer(i)) => i, + _ => 0, + }; return Some(Ok(VMValue::Bool(pos >= len))); } } @@ -183,16 +206,35 @@ pub(super) fn pre_exec_reroute( func: &MirFunction, arg_vals: Option<&[VMValue]>, ) -> Option> { - let args = match arg_vals { Some(a) => a, None => return None }; - if args.is_empty() { return None; } - let parsed = match parse_method_signature(func.signature.name.as_str()) { Some(p) => p, None => return None }; - let recv_cls = match extract_instance_box_class(&args[0]) { Some(c) => c, None => return None }; + let args = match arg_vals { + Some(a) => a, + None => return None, + }; + if args.is_empty() { + return None; + } + let parsed = match parse_method_signature(func.signature.name.as_str()) { + Some(p) => p, + None => return None, + }; + let recv_cls = match extract_instance_box_class(&args[0]) { + Some(c) => c, + None => return None, + }; // Always consider special re-routes (e.g., toString→stringify) even when class matches - if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { return Some(r); } - if recv_cls == parsed.class { return None; } + if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { + return Some(r); + } + if recv_cls == parsed.class { + return None; + } // Class mismatch: reroute to same method on the receiver's class - if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { return Some(r); } + if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { + return Some(r); + } // Narrow special fallback (e.g., is_eof) - if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); } + if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { + return Some(r); + } None } diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index 88c9d741..52a27adb 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -71,20 +71,32 @@ impl MirInterpreter { } /// Register static box declarations (called from vm.rs during setup) - pub fn register_static_box_decl(&mut self, name: String, decl: crate::core::model::BoxDeclaration) { + pub fn register_static_box_decl( + &mut self, + name: String, + decl: crate::core::model::BoxDeclaration, + ) { self.static_box_decls.insert(name, decl); } /// Ensure static box singleton instance exists, create if not /// Returns mutable reference to the singleton instance - fn ensure_static_box_instance(&mut self, box_name: &str) -> Result<&mut crate::instance_v2::InstanceBox, VMError> { + fn ensure_static_box_instance( + &mut self, + box_name: &str, + ) -> Result<&mut crate::instance_v2::InstanceBox, VMError> { // Check if instance already exists if !self.static_boxes.contains_key(box_name) { // Get declaration - let decl = self.static_box_decls.get(box_name) - .ok_or_else(|| VMError::InvalidInstruction( - format!("static box declaration not found: {}", box_name) - ))? + let decl = self + .static_box_decls + .get(box_name) + .ok_or_else(|| { + VMError::InvalidInstruction(format!( + "static box declaration not found: {}", + box_name + )) + })? .clone(); // Create instance from declaration @@ -97,15 +109,20 @@ impl MirInterpreter { self.static_boxes.insert(box_name.to_string(), instance); if std::env::var("NYASH_VM_STATIC_TRACE").ok().as_deref() == Some("1") { - eprintln!("[vm-static] created singleton instance for static box: {}", box_name); + eprintln!( + "[vm-static] created singleton instance for static box: {}", + box_name + ); } } // Return mutable reference - self.static_boxes.get_mut(box_name) - .ok_or_else(|| VMError::InvalidInstruction( - format!("static box instance not found after creation: {}", box_name) + self.static_boxes.get_mut(box_name).ok_or_else(|| { + VMError::InvalidInstruction(format!( + "static box instance not found after creation: {}", + box_name )) + }) } /// Check if a function name represents a static box method @@ -169,7 +186,12 @@ impl MirInterpreter { // Build helpful error message let mut names: Vec<&String> = module.functions.keys().collect(); names.sort(); - let avail = names.into_iter().take(12).cloned().collect::>().join(", "); + let avail = names + .into_iter() + .take(12) + .cloned() + .collect::>() + .join(", "); let tried = candidates.join(", "); let msg = format!( "entry function not found. searched: [{}]. available: [{}]. hint: define 'static box Main {{ method main(args){{ ... }} }}' or set NYASH_ENTRY=Name", @@ -200,9 +222,13 @@ impl MirInterpreter { argv_list = out; } } else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") { - if let Ok(v) = serde_json::from_str::>(&s) { argv_list = v; } + if let Ok(v) = serde_json::from_str::>(&s) { + argv_list = v; + } } else if let Ok(s) = std::env::var("NYASH_ARGV") { - if let Ok(v) = serde_json::from_str::>(&s) { argv_list = v; } + if let Ok(v) = serde_json::from_str::>(&s) { + argv_list = v; + } } // Construct ArrayBox of StringBox let array = crate::boxes::array::ArrayBox::new(); diff --git a/src/backend/mir_interpreter/utils/conversion_helpers.rs b/src/backend/mir_interpreter/utils/conversion_helpers.rs index f4eb9426..708fc8ec 100644 --- a/src/backend/mir_interpreter/utils/conversion_helpers.rs +++ b/src/backend/mir_interpreter/utils/conversion_helpers.rs @@ -3,8 +3,8 @@ //! レジスタ値の読み込み+型変換チェーンを統一します。 use super::super::*; -use crate::mir::ValueId; use crate::box_trait::NyashBox; +use crate::mir::ValueId; impl MirInterpreter { /// レジスタ値をBoxとして読み込む @@ -53,7 +53,13 @@ impl MirInterpreter { VMValue::Bool(_) => "Bool", VMValue::String(_) => "String", VMValue::Void => "Void", - VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_int", "Integer", &b.type_name())), + VMValue::BoxRef(b) => { + return Err(self.err_type_mismatch( + "load_as_int", + "Integer", + &b.type_name(), + )) + } VMValue::Future(_) => "Future", }; Err(self.err_type_mismatch("load_as_int", "Integer", type_name)) @@ -83,7 +89,9 @@ impl MirInterpreter { VMValue::Bool(_) => "Bool", VMValue::String(_) => "String", VMValue::Void => "Void", - VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name())), + VMValue::BoxRef(b) => { + return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name())) + } VMValue::Future(_) => "Future", }; Err(self.err_type_mismatch("load_as_bool", "Bool", type_name)) diff --git a/src/backend/mir_interpreter/utils/error_helpers.rs b/src/backend/mir_interpreter/utils/error_helpers.rs index a070ad5c..7e8e16b5 100644 --- a/src/backend/mir_interpreter/utils/error_helpers.rs +++ b/src/backend/mir_interpreter/utils/error_helpers.rs @@ -42,7 +42,10 @@ impl ErrorBuilder { #[inline] #[allow(dead_code)] pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError { - VMError::InvalidInstruction(format!("{} expects {} type, got {}", method, expected, actual)) + VMError::InvalidInstruction(format!( + "{} expects {} type, got {}", + method, expected, actual + )) } /// Index out of bounds error @@ -60,7 +63,10 @@ impl ErrorBuilder { #[inline] #[allow(dead_code)] pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError { - VMError::InvalidInstruction(format!("{} index out of bounds: {} >= {}", method, index, len)) + VMError::InvalidInstruction(format!( + "{} index out of bounds: {} >= {}", + method, index, len + )) } /// Unsupported operation error diff --git a/src/backend/mir_interpreter/utils/mod.rs b/src/backend/mir_interpreter/utils/mod.rs index aa5485ae..d39de444 100644 --- a/src/backend/mir_interpreter/utils/mod.rs +++ b/src/backend/mir_interpreter/utils/mod.rs @@ -1,11 +1,11 @@ //! MIR Interpreter共通ユーティリティ -pub mod destination_helpers; pub mod arg_validation; -pub mod receiver_helpers; -pub mod error_helpers; pub mod conversion_helpers; +pub mod destination_helpers; +pub mod error_helpers; pub mod naming; +pub mod receiver_helpers; // Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation // Selective re-export (only naming is widely used via utils::normalize_arity_suffix) diff --git a/src/backend/mir_interpreter/utils/naming.rs b/src/backend/mir_interpreter/utils/naming.rs index 9962b975..dd59a709 100644 --- a/src/backend/mir_interpreter/utils/naming.rs +++ b/src/backend/mir_interpreter/utils/naming.rs @@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str { None => name, } } - diff --git a/src/backend/mir_interpreter/utils/receiver_helpers.rs b/src/backend/mir_interpreter/utils/receiver_helpers.rs index 42f0981c..f8ef3cc0 100644 --- a/src/backend/mir_interpreter/utils/receiver_helpers.rs +++ b/src/backend/mir_interpreter/utils/receiver_helpers.rs @@ -23,9 +23,7 @@ impl MirInterpreter { let receiver_value = self.reg_load(receiver)?; match receiver_value { VMValue::BoxRef(b) => Ok(b), - _ => Err(VMError::InvalidInstruction( - "receiver must be Box".into(), - )), + _ => Err(VMError::InvalidInstruction("receiver must be Box".into())), } } } diff --git a/src/box_arithmetic.rs b/src/box_arithmetic.rs index a5926a2a..cb79a768 100644 --- a/src/box_arithmetic.rs +++ b/src/box_arithmetic.rs @@ -7,5 +7,5 @@ // Re-export all arithmetic operations from the dedicated arithmetic module pub use crate::boxes::arithmetic::{ - AddBox, SubtractBox, MultiplyBox, DivideBox, ModuloBox, CompareBox, + AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox, }; diff --git a/src/box_factory/builtin_impls/array_box.rs b/src/box_factory/builtin_impls/array_box.rs index 7d1622a5..5f76cc3b 100644 --- a/src/box_factory/builtin_impls/array_box.rs +++ b/src/box_factory/builtin_impls/array_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.4: Delete this file to remove builtin ArrayBox support */ -use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; +use crate::box_trait::NyashBox; /// Create builtin ArrayBox instance /// @@ -31,4 +31,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/bool_box.rs b/src/box_factory/builtin_impls/bool_box.rs index 5d2f2405..b1079b03 100644 --- a/src/box_factory/builtin_impls/bool_box.rs +++ b/src/box_factory/builtin_impls/bool_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.3: Delete this file to remove builtin BoolBox support */ -use crate::box_trait::{NyashBox, BoolBox}; use crate::box_factory::RuntimeError; +use crate::box_trait::{BoolBox, NyashBox}; /// Create builtin BoolBox instance /// @@ -35,4 +35,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/console_box.rs b/src/box_factory/builtin_impls/console_box.rs index 3aa3dc9b..e19b9852 100644 --- a/src/box_factory/builtin_impls/console_box.rs +++ b/src/box_factory/builtin_impls/console_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.6: Delete this file to remove builtin ConsoleBox support (LAST) */ -use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; +use crate::box_trait::NyashBox; /// Create builtin ConsoleBox instance /// @@ -32,4 +32,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/file_box.rs b/src/box_factory/builtin_impls/file_box.rs index 0ac19a98..d0437878 100644 --- a/src/box_factory/builtin_impls/file_box.rs +++ b/src/box_factory/builtin_impls/file_box.rs @@ -7,8 +7,8 @@ * 🎯 Core-ro mode: This is used directly */ -use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; +use crate::box_trait::NyashBox; use crate::boxes::file::core_ro::CoreRoFileIo; use crate::boxes::file::FileBox; use std::sync::Arc; @@ -28,9 +28,7 @@ pub fn create(_args: &[Box]) -> Result, RuntimeE }); } - eprintln!( - "[FileBox] Using builtin core-ro fallback implementation" - ); + eprintln!("[FileBox] Using builtin core-ro fallback implementation"); // Create FileBox with core-ro provider directly // Don't rely on global provider_lock which may not be initialized diff --git a/src/box_factory/builtin_impls/integer_box.rs b/src/box_factory/builtin_impls/integer_box.rs index d83ef328..f09b011c 100644 --- a/src/box_factory/builtin_impls/integer_box.rs +++ b/src/box_factory/builtin_impls/integer_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.2: Delete this file to remove builtin IntegerBox support */ -use crate::box_trait::{NyashBox, IntegerBox}; use crate::box_factory::RuntimeError; +use crate::box_trait::{IntegerBox, NyashBox}; /// Create builtin IntegerBox instance /// @@ -35,4 +35,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/map_box.rs b/src/box_factory/builtin_impls/map_box.rs index 94d0af17..ce114cdc 100644 --- a/src/box_factory/builtin_impls/map_box.rs +++ b/src/box_factory/builtin_impls/map_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.5: Delete this file to remove builtin MapBox support */ -use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; +use crate::box_trait::NyashBox; /// Create builtin MapBox instance /// @@ -31,4 +31,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/mod.rs b/src/box_factory/builtin_impls/mod.rs index 4165a373..73d03190 100644 --- a/src/box_factory/builtin_impls/mod.rs +++ b/src/box_factory/builtin_impls/mod.rs @@ -15,15 +15,15 @@ */ // Phase 2.1-2.6: Delete these modules one by one -pub mod string_box; // DELETE: Phase 2.1 (plugin ready) -pub mod integer_box; // DELETE: Phase 2.2 (plugin ready) -pub mod bool_box; // DELETE: Phase 2.3 (plugin needed) -pub mod array_box; // DELETE: Phase 2.4 (plugin check) -pub mod map_box; // DELETE: Phase 2.5 (plugin check) -pub mod console_box; // DELETE: Phase 2.6 (LAST - critical for logging) +pub mod array_box; // DELETE: Phase 2.4 (plugin check) +pub mod bool_box; // DELETE: Phase 2.3 (plugin needed) +pub mod console_box; +pub mod integer_box; // DELETE: Phase 2.2 (plugin ready) +pub mod map_box; // DELETE: Phase 2.5 (plugin check) +pub mod string_box; // DELETE: Phase 2.1 (plugin ready) // DELETE: Phase 2.6 (LAST - critical for logging) // Fallback support (Phase 15.5: Fallback Guarantee) -pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes +pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes // Special consideration -pub mod null_box; // DISCUSS: Keep as primitive? \ No newline at end of file +pub mod null_box; // DISCUSS: Keep as primitive? diff --git a/src/box_factory/builtin_impls/null_box.rs b/src/box_factory/builtin_impls/null_box.rs index 591b0cd4..c4227d8b 100644 --- a/src/box_factory/builtin_impls/null_box.rs +++ b/src/box_factory/builtin_impls/null_box.rs @@ -5,8 +5,8 @@ * 📋 Discussion needed: Is null a language primitive or plugin concern? */ -use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; +use crate::box_trait::NyashBox; /// Create builtin NullBox instance /// @@ -26,4 +26,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/builtin_impls/string_box.rs b/src/box_factory/builtin_impls/string_box.rs index 7839f4d0..fbfc07ca 100644 --- a/src/box_factory/builtin_impls/string_box.rs +++ b/src/box_factory/builtin_impls/string_box.rs @@ -5,8 +5,8 @@ * 🎯 Phase 2.1: Delete this file to remove builtin StringBox support */ -use crate::box_trait::{NyashBox, StringBox}; use crate::box_factory::RuntimeError; +use crate::box_trait::{NyashBox, StringBox}; /// Create builtin StringBox instance /// @@ -35,4 +35,4 @@ mod tests { let result = create(&[]).unwrap(); assert!(result.as_any().downcast_ref::().is_some()); } -} \ No newline at end of file +} diff --git a/src/box_factory/mod.rs b/src/box_factory/mod.rs index 13263f7b..4b488ec0 100644 --- a/src/box_factory/mod.rs +++ b/src/box_factory/mod.rs @@ -138,7 +138,10 @@ impl UnifiedBoxRegistry { Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT! }; - eprintln!("[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)", policy); + eprintln!( + "[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)", + policy + ); Self::with_policy(policy) } @@ -175,7 +178,7 @@ impl UnifiedBoxRegistry { if std::env::var("NYASH_USE_PLUGIN_BUILTINS").is_ok() { if let Ok(types) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") { if types.split(',').any(|t| t.trim() == name) { - return false; // 予約型として扱わない + return false; // 予約型として扱わない } } } @@ -229,19 +232,19 @@ impl UnifiedBoxRegistry { match self.policy { FactoryPolicy::StrictPluginFirst => match factory_type { - FactoryType::Plugin => 0, // Highest priority - FactoryType::User => 1, // Medium priority - FactoryType::Builtin => 2, // Lowest priority + FactoryType::Plugin => 0, // Highest priority + FactoryType::User => 1, // Medium priority + FactoryType::Builtin => 2, // Lowest priority }, FactoryPolicy::CompatPluginFirst => match factory_type { - FactoryType::Plugin => 0, // Highest priority - FactoryType::Builtin => 1, // Medium priority - FactoryType::User => 2, // Lowest priority + FactoryType::Plugin => 0, // Highest priority + FactoryType::Builtin => 1, // Medium priority + FactoryType::User => 2, // Lowest priority }, FactoryPolicy::BuiltinFirst => match factory_type { - FactoryType::Builtin => 0, // Highest priority (current default) - FactoryType::User => 1, // Medium priority - FactoryType::Plugin => 2, // Lowest priority + FactoryType::Builtin => 0, // Highest priority (current default) + FactoryType::User => 1, // Medium priority + FactoryType::Plugin => 2, // Lowest priority }, } } else { @@ -271,7 +274,9 @@ impl UnifiedBoxRegistry { // Prefer plugin-builtins when enabled and provider is available in v2 registry // BUT: Skip if plugins are explicitly disabled let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1"); - if !plugins_disabled && std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") { + if !plugins_disabled + && std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") + { use crate::runtime::{get_global_registry, BoxProvider}; // Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none) let allow: Vec = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") @@ -338,7 +343,8 @@ impl UnifiedBoxRegistry { Err(e) => { // FileBox special case: handle fallback based on mode if name == "FileBox" { - if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e) { + if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e) + { return fallback_result; } } @@ -386,14 +392,23 @@ impl UnifiedBoxRegistry { match mode { provider_registry::FileBoxMode::PluginOnly => { // Fail-Fast: return the original error immediately - eprintln!("[FileBox] Plugin creation failed in plugin-only mode: {}", original_error); + eprintln!( + "[FileBox] Plugin creation failed in plugin-only mode: {}", + original_error + ); Some(Err(RuntimeError::InvalidOperation { - message: format!("FileBox plugin creation failed (plugin-only mode): {}", original_error), + message: format!( + "FileBox plugin creation failed (plugin-only mode): {}", + original_error + ), })) } provider_registry::FileBoxMode::Auto => { // Auto mode: try fallback to builtin/core-ro - eprintln!("[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}", original_error); + eprintln!( + "[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}", + original_error + ); // Try builtin factory if available for factory in &self.factories { diff --git a/src/box_factory/plugin.rs b/src/box_factory/plugin.rs index 2df102d5..fca83f3b 100644 --- a/src/box_factory/plugin.rs +++ b/src/box_factory/plugin.rs @@ -6,8 +6,8 @@ */ use super::BoxFactory; -use crate::box_trait::NyashBox; use super::RuntimeError; +use crate::box_trait::NyashBox; use crate::runtime::get_global_registry; /// Factory for plugin-based Box types @@ -29,11 +29,20 @@ impl BoxFactory for PluginBoxFactory { ) -> Result, RuntimeError> { // Check if plugins are disabled let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1"); - eprintln!("[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}", - std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref().unwrap_or("not set"), name); + eprintln!( + "[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}", + std::env::var("NYASH_DISABLE_PLUGINS") + .ok() + .as_deref() + .unwrap_or("not set"), + name + ); if plugins_disabled { return Err(RuntimeError::InvalidOperation { - message: format!("Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}", name), + message: format!( + "Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}", + name + ), }); } diff --git a/src/box_factory/user_defined.rs b/src/box_factory/user_defined.rs index 8c225bcb..325d2660 100644 --- a/src/box_factory/user_defined.rs +++ b/src/box_factory/user_defined.rs @@ -6,9 +6,9 @@ */ use super::BoxFactory; +use super::{RuntimeError, SharedState}; use crate::box_trait::NyashBox; use crate::instance_v2::InstanceBox; -use super::{RuntimeError, SharedState}; /// Factory for user-defined Box types pub struct UserDefinedBoxFactory { diff --git a/src/box_operators.rs b/src/box_operators.rs index 1d835b71..197d34ce 100644 --- a/src/box_operators.rs +++ b/src/box_operators.rs @@ -16,16 +16,14 @@ use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; use crate::boxes::FloatBox; -use crate::operator_traits::{ - DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError, -}; +use crate::operator_traits::{DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError}; // Phase 1-2: Import macros, helpers, and static implementations from separate modules -mod macros; mod helpers; +mod macros; mod static_ops; -pub use helpers::{concat_result, can_repeat}; +pub use helpers::{can_repeat, concat_result}; pub use macros::impl_static_numeric_ops; // Phase 2: Static implementations are now in static_ops.rs @@ -392,16 +390,27 @@ impl OperatorResolver { #[inline] fn try_dyn_left_add(left: &dyn NyashBox, right: &dyn NyashBox) -> Option> { if let Some(int_box) = left.as_any().downcast_ref::() { - if let Some(result) = int_box.try_add(right) { return Some(result); } + if let Some(result) = int_box.try_add(right) { + return Some(result); + } } if let Some(str_box) = left.as_any().downcast_ref::() { - if let Some(result) = str_box.try_add(right) { return Some(result); } + if let Some(result) = str_box.try_add(right) { + return Some(result); + } } - if let Some(float_box) = left.as_any().downcast_ref::() { - if let Some(result) = float_box.try_add(right) { return Some(result); } + if let Some(float_box) = left + .as_any() + .downcast_ref::() + { + if let Some(result) = float_box.try_add(right) { + return Some(result); + } } if let Some(bool_box) = left.as_any().downcast_ref::() { - if let Some(result) = bool_box.try_add(right) { return Some(result); } + if let Some(result) = bool_box.try_add(right) { + return Some(result); + } } None } @@ -409,13 +418,22 @@ impl OperatorResolver { #[inline] fn try_dyn_left_sub(left: &dyn NyashBox, right: &dyn NyashBox) -> Option> { if let Some(int_box) = left.as_any().downcast_ref::() { - if let Some(result) = int_box.try_sub(right) { return Some(result); } + if let Some(result) = int_box.try_sub(right) { + return Some(result); + } } - if let Some(float_box) = left.as_any().downcast_ref::() { - if let Some(result) = float_box.try_sub(right) { return Some(result); } + if let Some(float_box) = left + .as_any() + .downcast_ref::() + { + if let Some(result) = float_box.try_sub(right) { + return Some(result); + } } if let Some(bool_box) = left.as_any().downcast_ref::() { - if let Some(result) = bool_box.try_sub(right) { return Some(result); } + if let Some(result) = bool_box.try_sub(right) { + return Some(result); + } } None } @@ -423,16 +441,27 @@ impl OperatorResolver { #[inline] fn try_dyn_left_mul(left: &dyn NyashBox, right: &dyn NyashBox) -> Option> { if let Some(int_box) = left.as_any().downcast_ref::() { - if let Some(result) = int_box.try_mul(right) { return Some(result); } + if let Some(result) = int_box.try_mul(right) { + return Some(result); + } } if let Some(str_box) = left.as_any().downcast_ref::() { - if let Some(result) = str_box.try_mul(right) { return Some(result); } + if let Some(result) = str_box.try_mul(right) { + return Some(result); + } } - if let Some(float_box) = left.as_any().downcast_ref::() { - if let Some(result) = float_box.try_mul(right) { return Some(result); } + if let Some(float_box) = left + .as_any() + .downcast_ref::() + { + if let Some(result) = float_box.try_mul(right) { + return Some(result); + } } if let Some(bool_box) = left.as_any().downcast_ref::() { - if let Some(result) = bool_box.try_mul(right) { return Some(result); } + if let Some(result) = bool_box.try_mul(right) { + return Some(result); + } } None } @@ -442,7 +471,10 @@ impl OperatorResolver { if let Some(int_box) = left.as_any().downcast_ref::() { return int_box.try_div(right); } - if let Some(float_box) = left.as_any().downcast_ref::() { + if let Some(float_box) = left + .as_any() + .downcast_ref::() + { return float_box.try_div(right); } if let Some(bool_box) = left.as_any().downcast_ref::() { @@ -458,7 +490,9 @@ impl OperatorResolver { // Try to cast to concrete types first and use their DynamicAdd implementation // This approach uses the concrete types rather than trait objects - if let Some(result) = Self::try_dyn_left_add(left, right) { return Ok(result); } + if let Some(result) = Self::try_dyn_left_add(left, right) { + return Ok(result); + } Err(OperatorError::UnsupportedOperation { operator: "+".to_string(), @@ -472,7 +506,9 @@ impl OperatorResolver { left: &dyn NyashBox, right: &dyn NyashBox, ) -> Result, OperatorError> { - if let Some(result) = Self::try_dyn_left_sub(left, right) { return Ok(result); } + if let Some(result) = Self::try_dyn_left_sub(left, right) { + return Ok(result); + } Err(OperatorError::UnsupportedOperation { operator: "-".to_string(), @@ -486,7 +522,9 @@ impl OperatorResolver { left: &dyn NyashBox, right: &dyn NyashBox, ) -> Result, OperatorError> { - if let Some(result) = Self::try_dyn_left_mul(left, right) { return Ok(result); } + if let Some(result) = Self::try_dyn_left_mul(left, right) { + return Ok(result); + } Err(OperatorError::UnsupportedOperation { operator: "*".to_string(), @@ -500,7 +538,9 @@ impl OperatorResolver { left: &dyn NyashBox, right: &dyn NyashBox, ) -> Result, OperatorError> { - if let Some(result) = Self::try_dyn_left_div(left, right) { return Ok(result); } + if let Some(result) = Self::try_dyn_left_div(left, right) { + return Ok(result); + } Err(OperatorError::UnsupportedOperation { operator: "/".to_string(), diff --git a/src/box_operators/helpers.rs b/src/box_operators/helpers.rs index e18da699..5035cc2d 100644 --- a/src/box_operators/helpers.rs +++ b/src/box_operators/helpers.rs @@ -60,4 +60,4 @@ pub fn concat_result(left: &dyn NyashBox, right: &dyn NyashBox) -> Box bool { (0..=10_000).contains(×) -} \ No newline at end of file +} diff --git a/src/box_operators/macros.rs b/src/box_operators/macros.rs index 1b95e895..b261b65b 100644 --- a/src/box_operators/macros.rs +++ b/src/box_operators/macros.rs @@ -28,21 +28,21 @@ macro_rules! impl_static_numeric_ops { impl crate::operator_traits::NyashAdd<$ty> for $ty { type Output = $ty; fn add(self, rhs: $ty) -> Self::Output { - < $ty >::new(self.value + rhs.value) + <$ty>::new(self.value + rhs.value) } } impl crate::operator_traits::NyashSub<$ty> for $ty { type Output = $ty; fn sub(self, rhs: $ty) -> Self::Output { - < $ty >::new(self.value - rhs.value) + <$ty>::new(self.value - rhs.value) } } impl crate::operator_traits::NyashMul<$ty> for $ty { type Output = $ty; fn mul(self, rhs: $ty) -> Self::Output { - < $ty >::new(self.value * rhs.value) + <$ty>::new(self.value * rhs.value) } } @@ -52,7 +52,7 @@ macro_rules! impl_static_numeric_ops { if rhs.value == $zero { Err(crate::operator_traits::OperatorError::DivisionByZero) } else { - Ok(< $ty >::new(self.value / rhs.value)) + Ok(<$ty>::new(self.value / rhs.value)) } } } @@ -60,4 +60,4 @@ macro_rules! impl_static_numeric_ops { } // Re-export the macro for external use -pub use impl_static_numeric_ops; \ No newline at end of file +pub use impl_static_numeric_ops; diff --git a/src/box_operators/static_ops.rs b/src/box_operators/static_ops.rs index 00e25d31..04f441c2 100644 --- a/src/box_operators/static_ops.rs +++ b/src/box_operators/static_ops.rs @@ -6,8 +6,8 @@ use crate::box_trait::{BoolBox, IntegerBox, StringBox}; use crate::boxes::FloatBox; -use crate::operator_traits::{NyashAdd, NyashMul}; use crate::impl_static_numeric_ops; +use crate::operator_traits::{NyashAdd, NyashMul}; // ===== Macro-generated static implementations ===== diff --git a/src/box_trait.rs b/src/box_trait.rs index 2d3b9bf9..498e1d9a 100644 --- a/src/box_trait.rs +++ b/src/box_trait.rs @@ -160,15 +160,7 @@ pub trait NyashBox: BoxCore + Debug { // ===== Basic Box Types (Re-exported from basic module) ===== // Re-export all basic box types from the dedicated basic module -pub use crate::boxes::basic::{ - BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox, -}; - - - - - - +pub use crate::boxes::basic::{BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox}; // Old Box implementations have been moved to separate files // ArrayBox is now defined in boxes::array module diff --git a/src/boxes/arithmetic/add_box.rs b/src/boxes/arithmetic/add_box.rs index 62621aaf..0af66734 100644 --- a/src/boxes/arithmetic/add_box.rs +++ b/src/boxes/arithmetic/add_box.rs @@ -137,4 +137,4 @@ impl Display for AddBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/arithmetic/compare_box.rs b/src/boxes/arithmetic/compare_box.rs index 9ea57d29..8673a6ca 100644 --- a/src/boxes/arithmetic/compare_box.rs +++ b/src/boxes/arithmetic/compare_box.rs @@ -76,4 +76,4 @@ impl CompareBox { let right_str = right.to_string_box(); BoolBox::new(left_str.value >= right_str.value) } -} \ No newline at end of file +} diff --git a/src/boxes/arithmetic/divide_box.rs b/src/boxes/arithmetic/divide_box.rs index db4d48e1..e1bb9f66 100644 --- a/src/boxes/arithmetic/divide_box.rs +++ b/src/boxes/arithmetic/divide_box.rs @@ -124,4 +124,4 @@ impl Display for DivideBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/arithmetic/mod.rs b/src/boxes/arithmetic/mod.rs index 59d8e38a..1216a4aa 100644 --- a/src/boxes/arithmetic/mod.rs +++ b/src/boxes/arithmetic/mod.rs @@ -13,19 +13,19 @@ // Individual arithmetic operation implementations mod add_box; -mod subtract_box; -mod multiply_box; +mod compare_box; mod divide_box; mod modulo_box; -mod compare_box; +mod multiply_box; +mod subtract_box; // Re-export all arithmetic box types pub use add_box::AddBox; -pub use subtract_box::SubtractBox; -pub use multiply_box::MultiplyBox; +pub use compare_box::CompareBox; pub use divide_box::DivideBox; pub use modulo_box::ModuloBox; -pub use compare_box::CompareBox; +pub use multiply_box::MultiplyBox; +pub use subtract_box::SubtractBox; // Re-export for convenience - common pattern in arithmetic operations pub use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; @@ -125,4 +125,4 @@ mod tests { assert_eq!(CompareBox::greater(&left, &right).value, false); assert_eq!(CompareBox::equals(&left, &right).value, false); } -} \ No newline at end of file +} diff --git a/src/boxes/arithmetic/multiply_box.rs b/src/boxes/arithmetic/multiply_box.rs index 0edee3d7..97a06ce6 100644 --- a/src/boxes/arithmetic/multiply_box.rs +++ b/src/boxes/arithmetic/multiply_box.rs @@ -115,4 +115,4 @@ impl Display for MultiplyBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/arithmetic/subtract_box.rs b/src/boxes/arithmetic/subtract_box.rs index 814b5eb6..2a4946a1 100644 --- a/src/boxes/arithmetic/subtract_box.rs +++ b/src/boxes/arithmetic/subtract_box.rs @@ -116,4 +116,4 @@ impl Display for SubtractBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/array/mod.rs b/src/boxes/array/mod.rs index 47b70286..97663dbc 100644 --- a/src/boxes/array/mod.rs +++ b/src/boxes/array/mod.rs @@ -42,11 +42,11 @@ impl ArrayBox { None => { let strict = std::env::var("HAKO_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false) || std::env::var("NYASH_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false); if strict { Box::new(StringBox::new("[array/empty/pop] empty array")) @@ -92,11 +92,11 @@ impl ArrayBox { None => { let strict = std::env::var("HAKO_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false) || std::env::var("NYASH_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false); if strict { // Mark OOB occurrence for runner policies (Gate‑C strict fail, etc.) @@ -127,11 +127,11 @@ impl ArrayBox { } else { let strict = std::env::var("HAKO_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false) || std::env::var("NYASH_OOB_STRICT") .ok() - .map(|v| matches!(v.as_str(), "1"|"true"|"on")) + .map(|v| matches!(v.as_str(), "1" | "true" | "on")) .unwrap_or(false); if strict { crate::runtime::observe::mark_oob(); diff --git a/src/boxes/basic/bool_box.rs b/src/boxes/basic/bool_box.rs index b1804060..523064b9 100644 --- a/src/boxes/basic/bool_box.rs +++ b/src/boxes/basic/bool_box.rs @@ -2,7 +2,7 @@ //! //! Implements the core BoolBox type for true/false values. -use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox}; +use crate::box_trait::{BoxBase, BoxCore, NyashBox, StringBox}; use std::any::Any; use std::fmt::{Debug, Display}; @@ -83,4 +83,4 @@ impl Display for BoolBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/basic/error_box.rs b/src/boxes/basic/error_box.rs index 7e87bfeb..94ecbb8f 100644 --- a/src/boxes/basic/error_box.rs +++ b/src/boxes/basic/error_box.rs @@ -2,7 +2,7 @@ //! //! Implements the ErrorBox type for representing error information. -use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox}; +use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use std::any::Any; use std::fmt::{Debug, Display}; @@ -79,4 +79,4 @@ impl Display for ErrorBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/basic/file_box.rs b/src/boxes/basic/file_box.rs index 5f10ccb7..760d86e4 100644 --- a/src/boxes/basic/file_box.rs +++ b/src/boxes/basic/file_box.rs @@ -9,4 +9,4 @@ )] // Re-export the new FileBox implementation for backward compatibility -pub use crate::boxes::file::FileBox; \ No newline at end of file +pub use crate::boxes::file::FileBox; diff --git a/src/boxes/basic/integer_box.rs b/src/boxes/basic/integer_box.rs index b8cbe4bf..2e57d945 100644 --- a/src/boxes/basic/integer_box.rs +++ b/src/boxes/basic/integer_box.rs @@ -2,7 +2,7 @@ //! //! Implements the core IntegerBox type for 64-bit signed integers. -use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox}; +use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use std::any::Any; use std::fmt::{Debug, Display}; @@ -79,4 +79,4 @@ impl Display for IntegerBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/basic/mod.rs b/src/boxes/basic/mod.rs index cfe89aad..6e09641c 100644 --- a/src/boxes/basic/mod.rs +++ b/src/boxes/basic/mod.rs @@ -4,17 +4,17 @@ //! fundamental data types in Nyash: String, Integer, Boolean, Void, File, and Error. // Individual basic box implementations -mod string_box; -mod integer_box; mod bool_box; -mod void_box; -mod file_box; mod error_box; +mod file_box; +mod integer_box; +mod string_box; +mod void_box; // Re-export all basic box types -pub use string_box::StringBox; -pub use integer_box::IntegerBox; pub use bool_box::BoolBox; -pub use void_box::VoidBox; +pub use error_box::ErrorBox; pub use file_box::FileBox; -pub use error_box::ErrorBox; \ No newline at end of file +pub use integer_box::IntegerBox; +pub use string_box::StringBox; +pub use void_box::VoidBox; diff --git a/src/boxes/basic/string_box.rs b/src/boxes/basic/string_box.rs index 5d2487e5..105e800a 100644 --- a/src/boxes/basic/string_box.rs +++ b/src/boxes/basic/string_box.rs @@ -2,7 +2,7 @@ //! //! Implements the core StringBox type with all string manipulation methods. -use crate::box_trait::{NyashBox, BoxCore, BoxBase}; +use crate::box_trait::{BoxBase, BoxCore, NyashBox}; use crate::boxes::ArrayBox; use std::any::Any; use std::fmt::{Debug, Display}; diff --git a/src/boxes/basic/void_box.rs b/src/boxes/basic/void_box.rs index 2de8e87d..b447f820 100644 --- a/src/boxes/basic/void_box.rs +++ b/src/boxes/basic/void_box.rs @@ -2,7 +2,7 @@ //! //! Implements the core VoidBox type representing empty or null results. -use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox}; +use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use std::any::Any; use std::fmt::{Debug, Display}; @@ -75,4 +75,4 @@ impl Display for VoidBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } -} \ No newline at end of file +} diff --git a/src/boxes/debug_box.rs b/src/boxes/debug_box.rs index 2192d880..0049772e 100644 --- a/src/boxes/debug_box.rs +++ b/src/boxes/debug_box.rs @@ -99,9 +99,9 @@ * - call stackは直近100件まで自動保持 */ +use crate::box_factory::RuntimeError; use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox}; use crate::instance_v2::InstanceBox; -use crate::box_factory::RuntimeError; use chrono::Local; use std::any::Any; use std::collections::HashMap; diff --git a/src/boxes/egui_box.rs b/src/boxes/egui_box.rs index 0a1342f7..2338ce11 100644 --- a/src/boxes/egui_box.rs +++ b/src/boxes/egui_box.rs @@ -33,8 +33,8 @@ * - `run()`はブロッキング動作(アプリ終了まで制御を返さない) */ -use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use crate::box_factory::RuntimeError; +use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use eframe::{self, egui, epaint::Vec2}; use std::any::Any; use std::sync::{Arc, RwLock}; diff --git a/src/boxes/file/box_shim.rs b/src/boxes/file/box_shim.rs index ff2c25d0..c725cfae 100644 --- a/src/boxes/file/box_shim.rs +++ b/src/boxes/file/box_shim.rs @@ -1,8 +1,8 @@ //! Thin FileBox shim that delegates to a selected provider. //! Not wired into the registry yet (safe placeholder). +use super::provider::{FileCaps, FileIo, FileResult}; use std::sync::Arc; -use super::provider::{FileIo, FileCaps, FileResult}; #[allow(dead_code)] pub struct FileBoxShim { @@ -16,9 +16,16 @@ impl FileBoxShim { let caps = provider.caps(); Self { provider, caps } } - pub fn open(&self, path: &str) -> FileResult<()> { self.provider.open(path) } - pub fn read(&self) -> FileResult { self.provider.read() } - pub fn close(&self) -> FileResult<()> { self.provider.close() } - pub fn caps(&self) -> FileCaps { self.caps } + pub fn open(&self, path: &str) -> FileResult<()> { + self.provider.open(path) + } + pub fn read(&self) -> FileResult { + self.provider.read() + } + pub fn close(&self) -> FileResult<()> { + self.provider.close() + } + pub fn caps(&self) -> FileCaps { + self.caps + } } - diff --git a/src/boxes/file/builtin_factory.rs b/src/boxes/file/builtin_factory.rs index 21794b14..515e8c15 100644 --- a/src/boxes/file/builtin_factory.rs +++ b/src/boxes/file/builtin_factory.rs @@ -3,10 +3,12 @@ //! Provides ProviderFactory implementation for the builtin FileBox (core-ro). //! This is auto-registered when feature "builtin-filebox" is enabled. -use std::sync::Arc; -use crate::boxes::file::provider::FileIo; use crate::boxes::file::core_ro::CoreRoFileIo; -use crate::runner::modes::common_util::provider_registry::{ProviderFactory, register_provider_factory}; +use crate::boxes::file::provider::FileIo; +use crate::runner::modes::common_util::provider_registry::{ + register_provider_factory, ProviderFactory, +}; +use std::sync::Arc; /// Builtin FileBox factory (static registration) pub struct BuiltinFileBoxFactory; diff --git a/src/boxes/file/mod.rs b/src/boxes/file/mod.rs index 40bcc26e..35326451 100644 --- a/src/boxes/file/mod.rs +++ b/src/boxes/file/mod.rs @@ -3,10 +3,10 @@ // 参考: 既存Boxの設計思想 // SSOT provider design (ring‑0/1) — modules are currently placeholders -pub mod provider; // trait FileIo / FileCaps / FileError -pub mod core_ro; // Core read‑only provider -pub mod box_shim; // Thin delegating shim -pub mod builtin_factory; // Builtin FileBox ProviderFactory +pub mod box_shim; // Thin delegating shim +pub mod builtin_factory; +pub mod core_ro; // Core read‑only provider +pub mod provider; // trait FileIo / FileCaps / FileError // Builtin FileBox ProviderFactory use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox}; use crate::runtime::provider_lock; @@ -64,7 +64,8 @@ impl FileBox { .ok_or("FileBox provider not initialized")? .clone(); - provider.open(path) + provider + .open(path) .map_err(|e| format!("Failed to open: {}", e))?; Ok(FileBox { @@ -76,8 +77,7 @@ impl FileBox { pub fn read_to_string(&self) -> Result { if let Some(ref provider) = self.provider { - provider.read() - .map_err(|e| format!("Read failed: {}", e)) + provider.read().map_err(|e| format!("Read failed: {}", e)) } else { Err("No provider available".to_string()) } @@ -85,7 +85,8 @@ impl FileBox { pub fn write_all(&self, _buf: &[u8]) -> Result<(), String> { // Fail-Fast by capability: consult provider caps - let caps = self.provider + let caps = self + .provider .as_ref() .map(|p| p.caps()) .or_else(|| provider_lock::get_filebox_caps()) @@ -107,15 +108,20 @@ impl FileBox { /// ファイルに内容を書き込む pub fn write(&self, _content: Box) -> Box { - let caps = self.provider + let caps = self + .provider .as_ref() .map(|p| p.caps()) .or_else(|| provider_lock::get_filebox_caps()) .unwrap_or_else(|| provider::FileCaps::read_only()); if !caps.write { - return Box::new(StringBox::new("Error: write unsupported by provider (read-only)")); + return Box::new(StringBox::new( + "Error: write unsupported by provider (read-only)", + )); } - Box::new(StringBox::new("Error: write supported but not implemented in this build")) + Box::new(StringBox::new( + "Error: write supported but not implemented in this build", + )) } /// ファイルが存在するかチェック @@ -126,28 +132,38 @@ impl FileBox { /// ファイルを削除 pub fn delete(&self) -> Box { - let caps = self.provider + let caps = self + .provider .as_ref() .map(|p| p.caps()) .or_else(|| provider_lock::get_filebox_caps()) .unwrap_or_else(|| provider::FileCaps::read_only()); if !caps.write { - return Box::new(StringBox::new("Error: delete unsupported by provider (read-only)")); + return Box::new(StringBox::new( + "Error: delete unsupported by provider (read-only)", + )); } - Box::new(StringBox::new("Error: delete supported but not implemented in this build")) + Box::new(StringBox::new( + "Error: delete supported but not implemented in this build", + )) } /// ファイルをコピー pub fn copy(&self, _dest: &str) -> Box { - let caps = self.provider + let caps = self + .provider .as_ref() .map(|p| p.caps()) .or_else(|| provider_lock::get_filebox_caps()) .unwrap_or_else(|| provider::FileCaps::read_only()); if !caps.write { - return Box::new(StringBox::new("Error: copy unsupported by provider (read-only)")); + return Box::new(StringBox::new( + "Error: copy unsupported by provider (read-only)", + )); } - Box::new(StringBox::new("Error: copy supported but not implemented in this build")) + Box::new(StringBox::new( + "Error: copy supported but not implemented in this build", + )) } } diff --git a/src/boxes/file/provider.rs b/src/boxes/file/provider.rs index ddc046b1..3929d1bd 100644 --- a/src/boxes/file/provider.rs +++ b/src/boxes/file/provider.rs @@ -12,7 +12,12 @@ pub struct FileCaps { } impl FileCaps { - pub const fn read_only() -> Self { Self { read: true, write: false } } + pub const fn read_only() -> Self { + Self { + read: true, + write: false, + } + } } /// Unified error type (thin placeholder for now) @@ -40,9 +45,10 @@ pub trait FileIo: Send + Sync { pub fn normalize_newlines(s: &str) -> String { let mut out = String::with_capacity(s.len()); for b in s.as_bytes() { - if *b == b'\r' { continue; } + if *b == b'\r' { + continue; + } out.push(*b as char); } out } - diff --git a/src/boxes/http_message_box.rs b/src/boxes/http_message_box.rs index f927a892..04e18ba7 100644 --- a/src/boxes/http_message_box.rs +++ b/src/boxes/http_message_box.rs @@ -279,7 +279,11 @@ impl HTTPResponseBox { } /// ステータスコード・メッセージ設定 - pub fn set_status(&self, code: Box, message: Box) -> Box { + pub fn set_status( + &self, + code: Box, + message: Box, + ) -> Box { let code_val = code.to_string_box().value.parse::().unwrap_or(200); let message_val = message.to_string_box().value; *self.status_code.lock().unwrap() = code_val; @@ -288,7 +292,11 @@ impl HTTPResponseBox { } /// ヘッダー設定 - pub fn set_header(&self, name: Box, value: Box) -> Box { + pub fn set_header( + &self, + name: Box, + value: Box, + ) -> Box { let name_str = name.to_string_box().value; let value_str = value.to_string_box().value; self.headers.lock().unwrap().insert(name_str, value_str); @@ -326,7 +334,10 @@ impl HTTPResponseBox { let version = self.http_version.lock().unwrap().clone(); let status_code = *self.status_code.lock().unwrap(); let status_message = self.status_message.lock().unwrap().clone(); - response.push_str(&format!("{} {} {}\r\n", version, status_code, status_message)); + response.push_str(&format!( + "{} {} {}\r\n", + version, status_code, status_message + )); // Headers for (name, value) in self.headers.lock().unwrap().iter() { @@ -335,7 +346,8 @@ impl HTTPResponseBox { // Content-Length if not already set let body_len = { self.body.lock().unwrap().len() }; - let need_len = { !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 }; + let need_len = + { !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 }; if need_len { response.push_str(&format!("Content-Length: {}\r\n", body_len)); } @@ -367,10 +379,11 @@ impl HTTPResponseBox { let response = HTTPResponseBox::new(); *response.status_code.lock().unwrap() = 200; *response.status_message.lock().unwrap() = "OK".to_string(); - response.headers.lock().unwrap().insert( - "Content-Type".to_string(), - "application/json".to_string(), - ); + response + .headers + .lock() + .unwrap() + .insert("Content-Type".to_string(), "application/json".to_string()); *response.body.lock().unwrap() = content.to_string_box().value; response } @@ -384,7 +397,8 @@ impl HTTPResponseBox { "Content-Type".to_string(), "text/html; charset=utf-8".to_string(), ); - *response.body.lock().unwrap() = "

404 - Not Found

".to_string(); + *response.body.lock().unwrap() = + "

404 - Not Found

".to_string(); response } } diff --git a/src/boxes/map_box.rs b/src/boxes/map_box.rs index f5f26aad..f6edb8b1 100644 --- a/src/boxes/map_box.rs +++ b/src/boxes/map_box.rs @@ -161,7 +161,10 @@ impl MapBox { } value.clone_box() } - None => Box::new(StringBox::new(&format!("[map/missing] Key not found: {}", key_str))), + None => Box::new(StringBox::new(&format!( + "[map/missing] Key not found: {}", + key_str + ))), } } @@ -202,7 +205,10 @@ impl MapBox { .unwrap() .values() .map(|v| { - if v.as_any().downcast_ref::().is_some() { + if v.as_any() + .downcast_ref::() + .is_some() + { v.share_box() } else { v.clone_box() diff --git a/src/boxes/missing_box.rs b/src/boxes/missing_box.rs index 7386b829..c91e9546 100644 --- a/src/boxes/missing_box.rs +++ b/src/boxes/missing_box.rs @@ -16,28 +16,48 @@ pub struct MissingBox { impl MissingBox { pub fn new() -> Self { - Self { base: BoxBase::new() } + Self { + base: BoxBase::new(), + } } - pub fn is_missing(&self) -> bool { true } + pub fn is_missing(&self) -> bool { + true + } } impl BoxCore for MissingBox { - fn box_id(&self) -> u64 { self.base.id } - fn parent_type_id(&self) -> Option { self.base.parent_type_id } + fn box_id(&self) -> u64 { + self.base.id + } + fn parent_type_id(&self) -> Option { + self.base.parent_type_id + } fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { // 開発時の可視性向上のための文字列表現。prod では基本的に表面化させない想定。 write!(f, "(missing)") } - fn as_any(&self) -> &dyn Any { self } - fn as_any_mut(&mut self) -> &mut dyn Any { self } + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } } impl NyashBox for MissingBox { - fn type_name(&self) -> &'static str { "MissingBox" } - fn to_string_box(&self) -> StringBox { StringBox::new("(missing)") } - fn clone_box(&self) -> Box { Box::new(self.clone()) } - fn share_box(&self) -> Box { self.clone_box() } + fn type_name(&self) -> &'static str { + "MissingBox" + } + fn to_string_box(&self) -> StringBox { + StringBox::new("(missing)") + } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + fn share_box(&self) -> Box { + self.clone_box() + } fn equals(&self, other: &dyn NyashBox) -> BoolBox { // 欠損どうしは論理同値とみなすが、通常の等価比較は境界で禁止される想定。 BoolBox::new(other.as_any().downcast_ref::().is_some()) @@ -45,6 +65,7 @@ impl NyashBox for MissingBox { } impl Display for MissingBox { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.fmt_box(f) + } } - diff --git a/src/boxes/mod.rs b/src/boxes/mod.rs index 0c98efbf..e6c6133b 100644 --- a/src/boxes/mod.rs +++ b/src/boxes/mod.rs @@ -145,8 +145,8 @@ pub use egui_box::EguiBox; #[cfg(target_arch = "wasm32")] pub use web::{WebCanvasBox, WebConsoleBox, WebDisplayBox}; -pub mod null_box; pub mod missing_box; +pub mod null_box; // High-priority Box types pub mod array; @@ -168,8 +168,8 @@ pub mod intent_box; pub mod p2p_box; // null関数も再エクスポート -pub use null_box::{null, NullBox}; pub use missing_box::MissingBox; +pub use null_box::{null, NullBox}; // High-priority Box types re-export pub use array::ArrayBox; diff --git a/src/cli/args.rs b/src/cli/args.rs index 9b6bf00c..133dbb4a 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -1,7 +1,7 @@ +use super::utils::parse_debug_fuel; +use super::CliConfig; use clap::{Arg, ArgMatches, Command}; use serde_json; -use super::CliConfig; -use super::utils::parse_debug_fuel; pub fn parse() -> CliConfig { let argv: Vec = std::env::args().collect(); @@ -13,10 +13,7 @@ pub fn parse() -> CliConfig { } // Provide HEX-escaped JSON as an alternate robust path for multiline/special bytes // Each arg is encoded as lowercase hex of its UTF-8 bytes - let hex_args: Vec = script_args - .iter() - .map(|s| hex_encode_utf8(s)) - .collect(); + let hex_args: Vec = script_args.iter().map(|s| hex_encode_utf8(s)).collect(); if let Ok(hex_json) = serde_json::to_string(&hex_args) { std::env::set_var("NYASH_SCRIPT_ARGS_HEX_JSON", hex_json); } @@ -118,8 +115,12 @@ fn hex_encode_utf8(s: &str) -> String { } pub fn from_matches(matches: &ArgMatches) -> CliConfig { - if matches.get_flag("stage3") { std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1"); } - if let Some(a) = matches.get_one::("ny-compiler-args") { std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); } + if matches.get_flag("stage3") { + std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1"); + } + if let Some(a) = matches.get_one::("ny-compiler-args") { + std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); + } let cfg = CliConfig { file: matches.get_one::("file").cloned(), debug_fuel: parse_debug_fuel(matches.get_one::("debug-fuel").unwrap()), @@ -134,7 +135,11 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { compile_native: matches.get_flag("compile-native") || matches.get_flag("aot"), output_file: matches.get_one::("output").cloned(), benchmark: matches.get_flag("benchmark"), - iterations: matches.get_one::("iterations").unwrap().parse().unwrap_or(10), + iterations: matches + .get_one::("iterations") + .unwrap() + .parse() + .unwrap_or(10), vm_stats: matches.get_flag("vm-stats"), vm_stats_json: matches.get_flag("vm-stats-json"), jit_exec: matches.get_flag("jit-exec"), @@ -145,7 +150,9 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { jit_events_compile: matches.get_flag("jit-events-compile"), jit_events_runtime: matches.get_flag("jit-events-runtime"), jit_events_path: matches.get_one::("jit-events-path").cloned(), - jit_threshold: matches.get_one::("jit-threshold").and_then(|s| s.parse::().ok()), + jit_threshold: matches + .get_one::("jit-threshold") + .and_then(|s| s.parse::().ok()), jit_phi_min: matches.get_flag("jit-phi-min"), jit_hostcall: matches.get_flag("jit-hostcall"), jit_handle_debug: matches.get_flag("jit-handle-debug"), @@ -158,7 +165,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { run_task: matches.get_one::("run-task").cloned(), load_ny_plugins: matches.get_flag("load-ny-plugins"), gc_mode: matches.get_one::("gc").cloned(), - parser_ny: matches.get_one::("parser").map(|s| s == "ny").unwrap_or(false), + parser_ny: matches + .get_one::("parser") + .map(|s| s == "ny") + .unwrap_or(false), ny_parser_pipe: matches.get_flag("ny-parser-pipe"), json_file: matches.get_one::("json-file").cloned(), mir_json_file: matches.get_one::("mir-json-file").cloned(), @@ -168,7 +178,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { build_aot: matches.get_one::("build-aot").cloned(), build_profile: matches.get_one::("build-profile").cloned(), build_target: matches.get_one::("build-target").cloned(), - cli_usings: matches.get_many::("using").map(|v| v.cloned().collect()).unwrap_or_else(|| Vec::new()), + cli_usings: matches + .get_many::("using") + .map(|v| v.cloned().collect()) + .unwrap_or_else(|| Vec::new()), emit_mir_json: matches.get_one::("emit-mir-json").cloned(), program_json_to_mir: matches.get_one::("program-json-to-mir").cloned(), emit_exe: matches.get_one::("emit-exe").cloned(), @@ -179,42 +192,94 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { macro_ctx_json: matches.get_one::("macro-ctx-json").cloned(), }; - if cfg.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); } - if cfg.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); } - if cfg.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); } - if cfg.jit_exec { std::env::set_var("NYASH_JIT_EXEC", "1"); } - if cfg.jit_stats { std::env::set_var("NYASH_JIT_STATS", "1"); } - if cfg.jit_stats_json { std::env::set_var("NYASH_JIT_STATS_JSON", "1"); } - if cfg.jit_dump { std::env::set_var("NYASH_JIT_DUMP", "1"); } - if cfg.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); } - if cfg.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); } - if cfg.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); } - if let Some(p) = &cfg.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); } - if let Some(t) = cfg.jit_threshold { std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); } - if cfg.jit_phi_min { std::env::set_var("NYASH_JIT_PHI_MIN", "1"); } - if cfg.jit_hostcall { std::env::set_var("NYASH_JIT_HOSTCALL", "1"); } - if cfg.jit_handle_debug { std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1"); } - if cfg.jit_native_f64 { std::env::set_var("NYASH_JIT_NATIVE_F64", "1"); } - if cfg.jit_native_bool { std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1"); } - if cfg.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); } - if cfg.jit_direct { std::env::set_var("NYASH_JIT_DIRECT", "1"); } - if let Some(gc) = &cfg.gc_mode { std::env::set_var("NYASH_GC_MODE", gc); } + if cfg.cli_verbose { + std::env::set_var("NYASH_CLI_VERBOSE", "1"); + } + if cfg.vm_stats { + std::env::set_var("NYASH_VM_STATS", "1"); + } + if cfg.vm_stats_json { + std::env::set_var("NYASH_VM_STATS_JSON", "1"); + } + if cfg.jit_exec { + std::env::set_var("NYASH_JIT_EXEC", "1"); + } + if cfg.jit_stats { + std::env::set_var("NYASH_JIT_STATS", "1"); + } + if cfg.jit_stats_json { + std::env::set_var("NYASH_JIT_STATS_JSON", "1"); + } + if cfg.jit_dump { + std::env::set_var("NYASH_JIT_DUMP", "1"); + } + if cfg.jit_events { + std::env::set_var("NYASH_JIT_EVENTS", "1"); + } + if cfg.jit_events_compile { + std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); + } + if cfg.jit_events_runtime { + std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); + } + if let Some(p) = &cfg.jit_events_path { + std::env::set_var("NYASH_JIT_EVENTS_PATH", p); + } + if let Some(t) = cfg.jit_threshold { + std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); + } + if cfg.jit_phi_min { + std::env::set_var("NYASH_JIT_PHI_MIN", "1"); + } + if cfg.jit_hostcall { + std::env::set_var("NYASH_JIT_HOSTCALL", "1"); + } + if cfg.jit_handle_debug { + std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1"); + } + if cfg.jit_native_f64 { + std::env::set_var("NYASH_JIT_NATIVE_F64", "1"); + } + if cfg.jit_native_bool { + std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1"); + } + if cfg.jit_only { + std::env::set_var("NYASH_JIT_ONLY", "1"); + } + if cfg.jit_direct { + std::env::set_var("NYASH_JIT_DIRECT", "1"); + } + if let Some(gc) = &cfg.gc_mode { + std::env::set_var("NYASH_GC_MODE", gc); + } if matches.get_flag("run-tests") { std::env::set_var("NYASH_RUN_TESTS", "1"); - if let Some(filter) = matches.get_one::("test-filter") { std::env::set_var("NYASH_TEST_FILTER", filter); } + if let Some(filter) = matches.get_one::("test-filter") { + std::env::set_var("NYASH_TEST_FILTER", filter); + } if let Some(entry) = matches.get_one::("test-entry") { let v = entry.as_str(); - if v == "wrap" || v == "override" { std::env::set_var("NYASH_TEST_ENTRY", v); } + if v == "wrap" || v == "override" { + std::env::set_var("NYASH_TEST_ENTRY", v); + } } if let Some(ret) = matches.get_one::("test-return") { let v = ret.as_str(); - if v == "tests" || v == "original" { std::env::set_var("NYASH_TEST_RETURN", v); } + if v == "tests" || v == "original" { + std::env::set_var("NYASH_TEST_RETURN", v); + } } } - if matches.get_flag("macro-preexpand") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1"); } - if matches.get_flag("macro-preexpand-auto") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto"); } - if matches.get_flag("macro-top-level-allow") { std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1"); } + if matches.get_flag("macro-preexpand") { + std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1"); + } + if matches.get_flag("macro-preexpand-auto") { + std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto"); + } + if matches.get_flag("macro-top-level-allow") { + std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1"); + } if let Some(p) = matches.get_one::("macro-profile") { match p.as_str() { "dev" | "ci-fast" | "strict" => { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index e51c20aa..1c1d4211 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -6,7 +6,6 @@ mod args; mod groups; mod utils; - /// Command-line configuration structure #[derive(Debug, Clone)] pub struct CliConfig { @@ -69,14 +68,22 @@ pub struct CliConfig { pub macro_ctx_json: Option, } -pub use groups::{BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig, ParserPipeConfig}; +pub use groups::{ + BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig, + ParserPipeConfig, +}; impl CliConfig { - pub fn parse() -> Self { args::parse() } + pub fn parse() -> Self { + args::parse() + } pub fn as_groups(&self) -> CliGroups { CliGroups { - input: InputConfig { file: self.file.clone(), cli_usings: self.cli_usings.clone() }, + input: InputConfig { + file: self.file.clone(), + cli_usings: self.cli_usings.clone(), + }, debug: DebugConfig { debug_fuel: self.debug_fuel, dump_ast: self.dump_ast, diff --git a/src/cli/utils.rs b/src/cli/utils.rs index 03632709..85a7bdb2 100644 --- a/src/cli/utils.rs +++ b/src/cli/utils.rs @@ -6,4 +6,3 @@ pub fn parse_debug_fuel(value: &str) -> Option { value.parse::().ok() } } - diff --git a/src/config/env.rs b/src/config/env.rs index 5a7b77ad..4b05ca8e 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -121,7 +121,9 @@ pub fn await_max_ms() -> u64 { /// Enable MIR PHI non-generation for Bridge compatibility mode only. /// フェーズM.2: MirBuilder/LoopBuilderでPHI統一済み、Bridge層の互換性制御のみ /// Default: PHI-ON (Phase 15 direction), override with NYASH_MIR_NO_PHI=1 -pub fn mir_no_phi() -> bool { env_bool("NYASH_MIR_NO_PHI") } +pub fn mir_no_phi() -> bool { + env_bool("NYASH_MIR_NO_PHI") +} /// Allow verifier to skip SSA/dominance/merge checks for PHI-less MIR. pub fn verify_allow_no_phi() -> bool { @@ -131,11 +133,15 @@ pub fn verify_allow_no_phi() -> bool { /// Enable strict edge-copy policy verification in PHI-off mode. /// When enabled, merge blocks must receive merged values via predecessor copies only, /// and the merge block itself must not introduce a self-copy to the merged destination. -pub fn verify_edge_copy_strict() -> bool { env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") } +pub fn verify_edge_copy_strict() -> bool { + env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") +} /// Enforce purity of return blocks: no side-effecting instructions allowed before Return /// Default: OFF. Enable with NYASH_VERIFY_RET_PURITY=1 in dev/profiling sessions. -pub fn verify_ret_purity() -> bool { env_bool("NYASH_VERIFY_RET_PURITY") } +pub fn verify_ret_purity() -> bool { + env_bool("NYASH_VERIFY_RET_PURITY") +} // ---- LLVM harness toggle (llvmlite) ---- pub fn llvm_use_harness() -> bool { @@ -172,7 +178,9 @@ pub fn env_bool_default(key: &str, default: bool) -> bool { /// Global fail-fast policy for runtime fallbacks. /// Default: ON (true) to prohibit silent/different-route fallbacks in Rust layer. /// Set NYASH_FAIL_FAST=0 to temporarily allow legacy fallbacks during bring-up. -pub fn fail_fast() -> bool { env_bool_default("NYASH_FAIL_FAST", true) } +pub fn fail_fast() -> bool { + env_bool_default("NYASH_FAIL_FAST", true) +} // VM legacy by-name call fallback was removed (Phase 2 complete). @@ -204,7 +212,9 @@ pub fn plugin_only() -> bool { /// Core-13 "pure" mode: after normalization, only the 13 canonical ops are allowed. /// If enabled, the optimizer will try lightweight rewrites for Load/Store/NewBox/Unary, /// and the final verifier will reject any remaining non-Core-13 ops. -pub fn mir_core13_pure() -> bool { env_bool("NYASH_MIR_CORE13_PURE") } +pub fn mir_core13_pure() -> bool { + env_bool("NYASH_MIR_CORE13_PURE") +} /// Enable heuristic pre-pin of comparison operands in if/loop headers. /// Default: OFF (0). Set NYASH_MIR_PREPIN=1 to enable. @@ -235,14 +245,26 @@ pub fn opt_diag_fail() -> bool { // ---- Legacy compatibility (dev-only) ---- /// Enable legacy InstanceBox fields (SharedNyashBox map) for compatibility. /// Default: OFF. Set NYASH_LEGACY_FIELDS_ENABLE=1 to materialize and use legacy fields. -pub fn legacy_fields_enable() -> bool { env_bool("NYASH_LEGACY_FIELDS_ENABLE") } +pub fn legacy_fields_enable() -> bool { + env_bool("NYASH_LEGACY_FIELDS_ENABLE") +} // ---- GC/Runtime tracing (execution-affecting visibility) ---- -pub fn gc_trace() -> bool { env_bool("NYASH_GC_TRACE") } -pub fn gc_barrier_trace() -> bool { env_bool("NYASH_GC_BARRIER_TRACE") } -pub fn runtime_checkpoint_trace() -> bool { env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE") } -pub fn vm_pic_stats() -> bool { env_bool("NYASH_VM_PIC_STATS") } -pub fn vm_vt_trace() -> bool { env_bool("NYASH_VM_VT_TRACE") } +pub fn gc_trace() -> bool { + env_bool("NYASH_GC_TRACE") +} +pub fn gc_barrier_trace() -> bool { + env_bool("NYASH_GC_BARRIER_TRACE") +} +pub fn runtime_checkpoint_trace() -> bool { + env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE") +} +pub fn vm_pic_stats() -> bool { + env_bool("NYASH_VM_PIC_STATS") +} +pub fn vm_vt_trace() -> bool { + env_bool("NYASH_VM_VT_TRACE") +} pub fn vm_pic_trace() -> bool { std::env::var("NYASH_VM_PIC_TRACE").ok().as_deref() == Some("1") } @@ -349,7 +371,11 @@ pub fn extern_trace() -> bool { // ---- Operator Boxes adopt defaults ---- /// CompareOperator.apply adopt: default ON (prod/devともに採用) pub fn operator_box_compare_adopt() -> bool { - match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT") + .ok() + .as_deref() + .map(|v| v.to_ascii_lowercase()) + { Some(ref s) if s == "0" || s == "false" || s == "off" => false, Some(ref s) if s == "1" || s == "true" || s == "on" => true, _ => true, // default ON @@ -357,7 +383,11 @@ pub fn operator_box_compare_adopt() -> bool { } /// AddOperator.apply adopt: default OFF(順次昇格のため) pub fn operator_box_add_adopt() -> bool { - match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT") + .ok() + .as_deref() + .map(|v| v.to_ascii_lowercase()) + { Some(ref s) if s == "0" || s == "false" || s == "off" => false, _ => true, // default ON (promoted after validation) } @@ -415,9 +445,15 @@ pub fn enable_using() -> bool { pub fn using_profile() -> String { std::env::var("NYASH_USING_PROFILE").unwrap_or_else(|_| "dev".to_string()) } -pub fn using_is_prod() -> bool { using_profile().eq_ignore_ascii_case("prod") } -pub fn using_is_ci() -> bool { using_profile().eq_ignore_ascii_case("ci") } -pub fn using_is_dev() -> bool { using_profile().eq_ignore_ascii_case("dev") } +pub fn using_is_prod() -> bool { + using_profile().eq_ignore_ascii_case("prod") +} +pub fn using_is_ci() -> bool { + using_profile().eq_ignore_ascii_case("ci") +} +pub fn using_is_dev() -> bool { + using_profile().eq_ignore_ascii_case("dev") +} /// Allow `using "path"` statements in source (dev-only by default). pub fn allow_using_file() -> bool { // SSOT 徹底: 全プロファイルで既定禁止(nyash.toml を唯一の真実に) @@ -432,7 +468,11 @@ pub fn allow_using_file() -> bool { /// 1) Explicit env `NYASH_USING_AST` = 1/true/on → enabled, = 0/false/off → disabled /// 2) Default by profile: dev/ci → ON, prod → OFF pub fn using_ast_enabled() -> bool { - match std::env::var("NYASH_USING_AST").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + match std::env::var("NYASH_USING_AST") + .ok() + .as_deref() + .map(|v| v.to_ascii_lowercase()) + { Some(ref s) if s == "1" || s == "true" || s == "on" => true, Some(ref s) if s == "0" || s == "false" || s == "off" => false, _ => !using_is_prod(), // dev/ci → true, prod → false @@ -443,7 +483,11 @@ pub fn using_ast_enabled() -> bool { /// - dev/ci: default true (allow, with WARN) /// Override with NYASH_VM_USER_INSTANCE_BOXCALL={0|1} pub fn vm_allow_user_instance_boxcall() -> bool { - match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL") + .ok() + .as_deref() + .map(|v| v.to_ascii_lowercase()) + { Some(ref s) if s == "0" || s == "false" || s == "off" => false, Some(ref s) if s == "1" || s == "true" || s == "on" => true, _ => !using_is_prod(), @@ -630,7 +674,10 @@ fn warn_alias_once(alias: &str, primary: &str) { let set = WARNED_ALIASES.get_or_init(|| Mutex::new(HashSet::new())); if let Ok(mut s) = set.lock() { if !s.contains(alias) { - eprintln!("[deprecate/env] '{}' is deprecated; use '{}'", alias, primary); + eprintln!( + "[deprecate/env] '{}' is deprecated; use '{}'", + alias, primary + ); s.insert(alias.to_string()); } } @@ -652,7 +699,9 @@ pub fn llvm_opt_level() -> String { /// Gate‑C(Core) route request (primary: NYASH_GATE_C_CORE; alias: HAKO_GATE_C_CORE) pub fn gate_c_core() -> bool { - if env_bool("NYASH_GATE_C_CORE") { return true; } + if env_bool("NYASH_GATE_C_CORE") { + return true; + } if env_bool("HAKO_GATE_C_CORE") { warn_alias_once("HAKO_GATE_C_CORE", "NYASH_GATE_C_CORE"); return true; diff --git a/src/config/provider_env.rs b/src/config/provider_env.rs index f7a3b56b..cd9ecea9 100644 --- a/src/config/provider_env.rs +++ b/src/config/provider_env.rs @@ -14,11 +14,18 @@ pub enum ProviderPolicy { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum FileBoxMode { Auto, CoreRo, PluginOnly } +pub enum FileBoxMode { + Auto, + CoreRo, + PluginOnly, +} /// Read global provider policy (affects Auto mode only) pub fn provider_policy_from_env() -> ProviderPolicy { - match std::env::var("HAKO_PROVIDER_POLICY").unwrap_or_else(|_| "strict-plugin-first".to_string()).as_str() { + match std::env::var("HAKO_PROVIDER_POLICY") + .unwrap_or_else(|_| "strict-plugin-first".to_string()) + .as_str() + { "safe-core-first" => ProviderPolicy::SafeCoreFirst, "static-preferred" => ProviderPolicy::StaticPreferred, _ => ProviderPolicy::StrictPluginFirst, @@ -27,13 +34,18 @@ pub fn provider_policy_from_env() -> ProviderPolicy { /// Read FileBox mode from environment variables pub fn filebox_mode_from_env() -> FileBoxMode { - match std::env::var("NYASH_FILEBOX_MODE").unwrap_or_else(|_| "auto".to_string()).as_str() { + match std::env::var("NYASH_FILEBOX_MODE") + .unwrap_or_else(|_| "auto".to_string()) + .as_str() + { "core-ro" => FileBoxMode::CoreRo, "plugin-only" => FileBoxMode::PluginOnly, _ => { if std::env::var("NYASH_DISABLE_PLUGINS").as_deref() == Ok("1") { FileBoxMode::CoreRo - } else { FileBoxMode::Auto } + } else { + FileBoxMode::Auto + } } } } @@ -44,4 +56,3 @@ pub fn filebox_mode_from_env() -> FileBoxMode { pub fn allow_filebox_fallback_override(quiet_pipe: bool) -> bool { quiet_pipe || crate::config::env::env_bool("NYASH_FILEBOX_ALLOW_FALLBACK") } - diff --git a/src/debug/hub.rs b/src/debug/hub.rs index 9c816108..40603043 100644 --- a/src/debug/hub.rs +++ b/src/debug/hub.rs @@ -9,7 +9,13 @@ static EMIT_COUNTER: AtomicU64 = AtomicU64::new(0); /// - NYASH_DEBUG_ENABLE=1 master gate /// - NYASH_DEBUG_KINDS=resolve,ssa allowed cats (comma-separated) /// - NYASH_DEBUG_SINK=path file to append JSONL events -pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str>, meta: serde_json::Value) { +pub fn emit( + cat: &str, + kind: &str, + fn_name: Option<&str>, + region_id: Option<&str>, + meta: serde_json::Value, +) { if std::env::var("NYASH_DEBUG_ENABLE").ok().as_deref() != Some("1") { return; } @@ -25,7 +31,9 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str .unwrap_or(1); if sample_every > 1 { let n = EMIT_COUNTER.fetch_add(1, Ordering::Relaxed) + 1; - if n % sample_every != 0 { return; } + if n % sample_every != 0 { + return; + } } let sink = match std::env::var("NYASH_DEBUG_SINK") { Ok(s) if !s.is_empty() => s, @@ -41,7 +49,11 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str "kind": kind, "meta": meta, }); - if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&sink) { + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&sink) + { let _ = writeln!(f, "{}", obj.to_string()); } } diff --git a/src/debug/mod.rs b/src/debug/mod.rs index 99d03f52..e6317df4 100644 --- a/src/debug/mod.rs +++ b/src/debug/mod.rs @@ -1,2 +1,2 @@ -pub mod log; pub mod hub; +pub mod log; diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index d94cb9c1..25bb6e51 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { return Some(*t); } + if *k == word { + return Some(*t); + } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", - "global", - "function", - "static", - "if", - "loop", - "break", - "return", - "print", - "nowait", - "include", - "local", - "outbox", - "try", - "throw", - "using", - "from", + "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", + "include", "local", "outbox", "try", "throw", "using", "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ - "add", - "sub", - "mul", - "div", - "and", - "or", - "eq", - "ne", -]; \ No newline at end of file +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; diff --git a/src/host_providers/llvm_codegen.rs b/src/host_providers/llvm_codegen.rs index 551610aa..a81ef83e 100644 --- a/src/host_providers/llvm_codegen.rs +++ b/src/host_providers/llvm_codegen.rs @@ -1,8 +1,8 @@ +use std::ffi::{CStr, CString}; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; -use std::ffi::{CString, CStr}; pub struct Opts { pub out: Option, @@ -13,9 +13,13 @@ pub struct Opts { fn resolve_ny_llvmc() -> PathBuf { if let Ok(s) = std::env::var("NYASH_NY_LLVM_COMPILER") { - if !s.is_empty() { return PathBuf::from(s); } + if !s.is_empty() { + return PathBuf::from(s); + } + } + if let Ok(p) = which::which("ny-llvmc") { + return p; } - if let Ok(p) = which::which("ny-llvmc") { return p; } PathBuf::from("target/release/ny-llvmc") } @@ -25,7 +29,10 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result // Optional provider selection (C-API) — guarded by env flags // NYASH_LLVM_USE_CAPI=1 and HAKO_V1_EXTERN_PROVIDER_C_ABI=1 if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1") - && std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() == Some("1") + && std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI") + .ok() + .as_deref() + == Some("1") { // Basic shape check first if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") { @@ -37,11 +44,19 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result let tmp_dir = std::env::temp_dir(); let in_path = tmp_dir.join("hako_llvm_in.json"); { - let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; - f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + let mut f = fs::File::create(&in_path) + .map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + f.write_all(mir_json.as_bytes()) + .map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + } + let out_path = if let Some(p) = opts.out.clone() { + p + } else { + tmp_dir.join("hako_llvm_out.o") + }; + if let Some(parent) = out_path.parent() { + let _ = fs::create_dir_all(parent); } - let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") }; - if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); } match compile_via_capi(&in_path, &out_path) { Ok(()) => return Ok(out_path), Err(e) => { @@ -74,26 +89,41 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result let tmp_dir = std::env::temp_dir(); let in_path = tmp_dir.join("hako_llvm_in.json"); { - let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; - f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + let mut f = + fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + f.write_all(mir_json.as_bytes()) + .map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; } // Output path - let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") }; - if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); } + let out_path = if let Some(p) = opts.out.clone() { + p + } else { + tmp_dir.join("hako_llvm_out.o") + }; + if let Some(parent) = out_path.parent() { + let _ = fs::create_dir_all(parent); + } // Build command: ny-llvmc --in --emit obj --out let mut cmd = Command::new(&ny_llvmc); - cmd.arg("--in").arg(&in_path) - .arg("--emit").arg("obj") - .arg("--out").arg(&out_path); - if let Some(nyrt) = opts.nyrt.as_ref() { cmd.arg("--nyrt").arg(nyrt); } + cmd.arg("--in") + .arg(&in_path) + .arg("--emit") + .arg("obj") + .arg("--out") + .arg(&out_path); + if let Some(nyrt) = opts.nyrt.as_ref() { + cmd.arg("--nyrt").arg(nyrt); + } if let Some(level) = opts.opt_level.as_ref() { cmd.env("HAKO_LLVM_OPT_LEVEL", level); cmd.env("NYASH_LLVM_OPT_LEVEL", level); } - let status = cmd.status().map_err(|e| format!("[llvmemit/spawn/error] {}", e))?; + let status = cmd + .status() + .map_err(|e| format!("[llvmemit/spawn/error] {}", e))?; if !status.success() { let code = status.code().unwrap_or(1); let tag = format!("[llvmemit/ny-llvmc/failed status={}]", code); @@ -121,19 +151,28 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> { unsafe { // Resolve library path let mut candidates: Vec = Vec::new(); - if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } } + if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { + if !p.is_empty() { + candidates.push(PathBuf::from(p)); + } + } candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so")); candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so")); - let lib_path = candidates.into_iter().find(|p| p.exists()) + let lib_path = candidates + .into_iter() + .find(|p| p.exists()) .ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?; let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?; // Symbol: int hako_llvmc_compile_json(const char*, const char*, char**) - type CompileFn = unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int; + type CompileFn = + unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int; let func: libloading::Symbol = lib .get(b"hako_llvmc_compile_json\0") .map_err(|e| format!("dlsym failed: {}", e))?; - let cin = CString::new(json_in.to_string_lossy().as_bytes()).map_err(|_| "invalid json path".to_string())?; - let cout = CString::new(obj_out.to_string_lossy().as_bytes()).map_err(|_| "invalid out path".to_string())?; + let cin = CString::new(json_in.to_string_lossy().as_bytes()) + .map_err(|_| "invalid json path".to_string())?; + let cout = CString::new(obj_out.to_string_lossy().as_bytes()) + .map_err(|_| "invalid out path".to_string())?; let mut err_ptr: *mut c_char = std::ptr::null_mut(); // Avoid recursive FFI-in-FFI: force inner AOT to use CLI path let prev = std::env::var("HAKO_AOT_USE_FFI").ok(); @@ -156,17 +195,31 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> { ); } - let rc = func(cin.as_ptr(), cout.as_ptr(), &mut err_ptr as *mut *mut c_char); - if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); } + let rc = func( + cin.as_ptr(), + cout.as_ptr(), + &mut err_ptr as *mut *mut c_char, + ); + if let Some(v) = prev { + std::env::set_var("HAKO_AOT_USE_FFI", v); + } else { + std::env::remove_var("HAKO_AOT_USE_FFI"); + } if rc != 0 { - let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "compile failed".to_string() }; + let msg = if !err_ptr.is_null() { + CStr::from_ptr(err_ptr).to_string_lossy().to_string() + } else { + "compile failed".to_string() + }; // Free error string (allocated by C side) if !err_ptr.is_null() { free(err_ptr as *mut c_void); } return Err(msg); } - if !obj_out.exists() { return Err("object not produced".into()); } + if !obj_out.exists() { + return Err("object not produced".into()); + } Ok(()) } } @@ -177,13 +230,19 @@ fn compile_via_capi(_json_in: &Path, _obj_out: &Path) -> Result<(), String> { } /// Link an object to an executable via C-API FFI bundle. -pub fn link_object_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) -> Result<(), String> { +pub fn link_object_capi( + obj_in: &Path, + exe_out: &Path, + extra_ldflags: Option<&str>, +) -> Result<(), String> { // Compute effective ldflags let mut eff: Option = extra_ldflags.map(|s| s.to_string()); let empty = eff.as_deref().map(|s| s.trim().is_empty()).unwrap_or(true); if empty { if let Ok(s) = std::env::var("HAKO_AOT_LDFLAGS") { - if !s.trim().is_empty() { eff = Some(s); } + if !s.trim().is_empty() { + eff = Some(s); + } } } if eff.is_none() { @@ -224,35 +283,68 @@ fn link_via_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) -> unsafe { let mut candidates: Vec = Vec::new(); - if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } } + if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { + if !p.is_empty() { + candidates.push(PathBuf::from(p)); + } + } candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so")); candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so")); - let lib_path = candidates.into_iter().find(|p| p.exists()) + let lib_path = candidates + .into_iter() + .find(|p| p.exists()) .ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?; let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?; // int hako_llvmc_link_obj(const char*, const char*, const char*, char**) - type LinkFn = unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, *mut *mut c_char) -> c_int; + type LinkFn = unsafe extern "C" fn( + *const c_char, + *const c_char, + *const c_char, + *mut *mut c_char, + ) -> c_int; let func: libloading::Symbol = lib .get(b"hako_llvmc_link_obj\0") .map_err(|e| format!("dlsym failed: {}", e))?; - let cobj = CString::new(obj_in.to_string_lossy().as_bytes()).map_err(|_| "invalid obj path".to_string())?; - let cexe = CString::new(exe_out.to_string_lossy().as_bytes()).map_err(|_| "invalid exe path".to_string())?; + let cobj = CString::new(obj_in.to_string_lossy().as_bytes()) + .map_err(|_| "invalid obj path".to_string())?; + let cexe = CString::new(exe_out.to_string_lossy().as_bytes()) + .map_err(|_| "invalid exe path".to_string())?; let ldflags_owned; - let cflags_ptr = if let Some(s) = extra_ldflags { ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?; ldflags_owned.as_ptr() } else { std::ptr::null() }; + let cflags_ptr = if let Some(s) = extra_ldflags { + ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?; + ldflags_owned.as_ptr() + } else { + std::ptr::null() + }; let mut err_ptr: *mut c_char = std::ptr::null_mut(); // Avoid recursive FFI-in-FFI let prev = std::env::var("HAKO_AOT_USE_FFI").ok(); std::env::set_var("HAKO_AOT_USE_FFI", "0"); - let rc = func(cobj.as_ptr(), cexe.as_ptr(), cflags_ptr, &mut err_ptr as *mut *mut c_char); - if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); } + let rc = func( + cobj.as_ptr(), + cexe.as_ptr(), + cflags_ptr, + &mut err_ptr as *mut *mut c_char, + ); + if let Some(v) = prev { + std::env::set_var("HAKO_AOT_USE_FFI", v); + } else { + std::env::remove_var("HAKO_AOT_USE_FFI"); + } if rc != 0 { - let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "link failed".to_string() }; + let msg = if !err_ptr.is_null() { + CStr::from_ptr(err_ptr).to_string_lossy().to_string() + } else { + "link failed".to_string() + }; if !err_ptr.is_null() { free(err_ptr as *mut c_void); } return Err(msg); } - if !exe_out.exists() { return Err("exe not produced".into()); } + if !exe_out.exists() { + return Err("exe not produced".into()); + } Ok(()) } } @@ -263,21 +355,31 @@ fn link_via_capi(_obj_in: &Path, _exe_out: &Path, _extra: Option<&str>) -> Resul } fn resolve_python3() -> Option { - if let Ok(p) = which::which("python3") { return Some(p); } - if let Ok(p) = which::which("python") { return Some(p); } + if let Ok(p) = which::which("python3") { + return Some(p); + } + if let Ok(p) = which::which("python") { + return Some(p); + } None } fn resolve_llvmlite_harness() -> Option { if let Ok(root) = std::env::var("NYASH_ROOT") { let p = PathBuf::from(root).join("tools/llvmlite_harness.py"); - if p.exists() { return Some(p); } + if p.exists() { + return Some(p); + } } let p = PathBuf::from("tools/llvmlite_harness.py"); - if p.exists() { return Some(p); } + if p.exists() { + return Some(p); + } // Also try repo-relative (target may run elsewhere) let p2 = PathBuf::from("../tools/llvmlite_harness.py"); - if p2.exists() { return Some(p2); } + if p2.exists() { + return Some(p2); + } None } @@ -303,17 +405,27 @@ fn mir_json_to_object_llvmlite(mir_json: &str, opts: &Opts) -> Result --out let status = Command::new(&py) .arg(&harness) - .arg("--in").arg(&in_path) - .arg("--out").arg(&out_path) + .arg("--in") + .arg(&in_path) + .arg("--out") + .arg(&out_path) .status() .map_err(|e| format!("[llvmemit/llvmlite/spawn/error] {}", e))?; if !status.success() { diff --git a/src/host_providers/mir_builder.rs b/src/host_providers/mir_builder.rs index 294f73c1..974a6d5b 100644 --- a/src/host_providers/mir_builder.rs +++ b/src/host_providers/mir_builder.rs @@ -11,7 +11,10 @@ pub fn program_json_to_mir_json(program_json: &str) -> Result { } /// Convert Program(JSON v0) to MIR(JSON v0) with using imports support. -pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMap) -> Result { +pub fn program_json_to_mir_json_with_imports( + program_json: &str, + imports: HashMap, +) -> Result { // Basic header check if !program_json.contains("\"version\"") || !program_json.contains("\"kind\"") { let tag = "[mirbuilder/input/invalid] missing version/kind keys"; @@ -20,14 +23,15 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa } // Parse Program(JSON v0) into a MIR Module with imports - let module = match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) { - Ok(m) => m, - Err(e) => { - let tag = format!("[mirbuilder/parse/error] {}", e); - eprintln!("{}", tag); - return Err(tag); - } - }; + let module = + match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) { + Ok(m) => m, + Err(e) => { + let tag = format!("[mirbuilder/parse/error] {}", e); + eprintln!("{}", tag); + return Err(tag); + } + }; // Emit MIR(JSON) to a temporary file (reuse existing emitter), then read back let tmp_dir = std::env::temp_dir(); @@ -57,8 +61,12 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa if let Some(funcs) = m.get("functions").cloned() { let v1 = serde_json::json!({"schema_version":"1.0","functions": funcs}); serde_json::to_string(&v1).unwrap_or(s0) - } else { s0 } - } else { s0 } + } else { + s0 + } + } else { + s0 + } } _ => s0, }; @@ -128,7 +136,10 @@ mod tests { let mir_json = result.unwrap(); // MIR JSON should contain functions - assert!(mir_json.contains("functions"), "MIR JSON should contain functions"); + assert!( + mir_json.contains("functions"), + "MIR JSON should contain functions" + ); eprintln!("[test] MIR JSON generated successfully with MatI64 imports"); } } diff --git a/src/host_providers/mod.rs b/src/host_providers/mod.rs index 82aa764d..9a4115f2 100644 --- a/src/host_providers/mod.rs +++ b/src/host_providers/mod.rs @@ -1,3 +1,2 @@ -pub mod mir_builder; pub mod llvm_codegen; - +pub mod mir_builder; diff --git a/src/instance_v2.rs b/src/instance_v2.rs index 91f126ee..2732fac3 100644 --- a/src/instance_v2.rs +++ b/src/instance_v2.rs @@ -50,7 +50,7 @@ impl Clone for InstanceBox { class_name: self.class_name.clone(), fields_ng: Arc::clone(&self.fields_ng), // Shared reference methods: Arc::clone(&self.methods), - inner_content: None, // inner_content cannot be cloned (Box) + inner_content: None, // inner_content cannot be cloned (Box) base: BoxBase::new(), // Fresh base for clone finalized: Arc::clone(&self.finalized), fields: self.fields.as_ref().map(Arc::clone), @@ -108,7 +108,11 @@ impl InstanceBox { base: BoxBase::new(), finalized: Arc::new(Mutex::new(false)), // レガシー互換フィールド(既定OFF) - fields: if legacy_enabled { Some(Arc::new(Mutex::new(legacy_field_map))) } else { None }, + fields: if legacy_enabled { + Some(Arc::new(Mutex::new(legacy_field_map))) + } else { + None + }, init_field_order: fields, weak_fields_union: std::collections::HashSet::new(), in_finalization: Arc::new(Mutex::new(false)), @@ -240,10 +244,7 @@ impl InstanceBox { } /// レガシー互換:weak field取得 - pub fn get_weak_field( - &self, - field_name: &str, - ) -> Option { + pub fn get_weak_field(&self, field_name: &str) -> Option { self.get_field_ng(field_name) } diff --git a/src/lib.rs b/src/lib.rs index 586ad75e..fe300164 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,7 @@ pub mod runtime; // Unified Grammar scaffolding pub mod grammar; pub mod syntax; // syntax sugar config and helpers -// Execution runner (CLI coordinator) + // Execution runner (CLI coordinator) pub mod runner; pub mod runner_hv1_inline_guard {} pub mod using; // using resolver scaffolding (Phase 15) @@ -76,7 +76,9 @@ pub mod host_providers; pub mod providers; // C‑ABI PoC shim (20.36/20.37) -pub mod abi { pub mod nyrt_shim; } +pub mod abi { + pub mod nyrt_shim; +} // Expose the macro engine module under a raw identifier; the source lives under `src/macro/`. #[path = "macro/mod.rs"] @@ -91,12 +93,8 @@ pub mod tests; // Re-export main types for easy access pub use ast::{ASTNode, BinaryOperator, LiteralValue}; pub use box_arithmetic::{AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox}; -pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox}; -pub use environment::{Environment, PythonCompatEnvironment}; pub use box_factory::RuntimeError; -pub use parser::{NyashParser, ParseError}; -pub use tokenizer::{NyashTokenizer, Token, TokenType}; -pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports +pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox}; pub use boxes::console_box::ConsoleBox; pub use boxes::debug_box::DebugBox; pub use boxes::map_box::MapBox; @@ -106,8 +104,12 @@ pub use boxes::random_box::RandomBox; pub use boxes::sound_box::SoundBox; pub use boxes::time_box::{DateTimeBox, TimeBox, TimerBox}; pub use channel_box::{ChannelBox, MessageBox}; +pub use environment::{Environment, PythonCompatEnvironment}; pub use instance_v2::InstanceBox; // 🎯 新実装テスト(nyash_rustパス使用) pub use method_box::{BoxType, EphemeralInstance, FunctionDefinition, MethodBox}; +pub use parser::{NyashParser, ParseError}; +pub use tokenizer::{NyashTokenizer, Token, TokenType}; +pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports pub use value::NyashValue; diff --git a/src/macro/ast_json.rs b/src/macro/ast_json.rs index 5fcd8734..0220be03 100644 --- a/src/macro/ast_json.rs +++ b/src/macro/ast_json.rs @@ -1,5 +1,5 @@ +use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator}; use serde_json::{json, Value}; -use nyash_rust::ast::{ASTNode, LiteralValue, BinaryOperator, UnaryOperator, Span}; pub fn ast_to_json(ast: &ASTNode) -> Value { match ast.clone() { @@ -7,7 +7,9 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "kind": "Program", "statements": statements.into_iter().map(|s| ast_to_json(&s)).collect::>() }), - ASTNode::Loop { condition, body, .. } => json!({ + ASTNode::Loop { + condition, body, .. + } => json!({ "kind": "Loop", "condition": ast_to_json(&condition), "body": body.into_iter().map(|s| ast_to_json(&s)).collect::>() @@ -27,18 +29,32 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "target": ast_to_json(&target), "value": ast_to_json(&value), }), - ASTNode::Local { variables, initial_values, .. } => json!({ + ASTNode::Local { + variables, + initial_values, + .. + } => json!({ "kind": "Local", "variables": variables, "inits": initial_values.into_iter().map(|opt| opt.map(|v| ast_to_json(&v))).collect::>() }), - ASTNode::If { condition, then_body, else_body, .. } => json!({ + ASTNode::If { + condition, + then_body, + else_body, + .. + } => json!({ "kind": "If", "condition": ast_to_json(&condition), "then": then_body.into_iter().map(|s| ast_to_json(&s)).collect::>(), "else": else_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::>()), }), - ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => json!({ + ASTNode::TryCatch { + try_body, + catch_clauses, + finally_body, + .. + } => json!({ "kind": "TryCatch", "try": try_body.into_iter().map(|s| ast_to_json(&s)).collect::>(), "catch": catch_clauses.into_iter().map(|cc| json!({ @@ -48,7 +64,14 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { })).collect::>(), "cleanup": finally_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::>()) }), - ASTNode::FunctionDeclaration { name, params, body, is_static, is_override, .. } => json!({ + ASTNode::FunctionDeclaration { + name, + params, + body, + is_static, + is_override, + .. + } => json!({ "kind": "FunctionDeclaration", "name": name, "params": params, @@ -58,24 +81,38 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { }), ASTNode::Variable { name, .. } => json!({"kind":"Variable","name":name}), ASTNode::Literal { value, .. } => json!({"kind":"Literal","value": lit_to_json(&value)}), - ASTNode::BinaryOp { operator, left, right, .. } => json!({ + ASTNode::BinaryOp { + operator, + left, + right, + .. + } => json!({ "kind":"BinaryOp", "op": bin_to_str(&operator), "left": ast_to_json(&left), "right": ast_to_json(&right), }), - ASTNode::UnaryOp { operator, operand, .. } => json!({ + ASTNode::UnaryOp { + operator, operand, .. + } => json!({ "kind":"UnaryOp", "op": un_to_str(&operator), "operand": ast_to_json(&operand), }), - ASTNode::MethodCall { object, method, arguments, .. } => json!({ + ASTNode::MethodCall { + object, + method, + arguments, + .. + } => json!({ "kind":"MethodCall", "object": ast_to_json(&object), "method": method, "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::>() }), - ASTNode::FunctionCall { name, arguments, .. } => json!({ + ASTNode::FunctionCall { + name, arguments, .. + } => json!({ "kind":"FunctionCall", "name": name, "arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::>() @@ -88,7 +125,12 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { "kind":"Map", "entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::>() }), - ASTNode::MatchExpr { scrutinee, arms, else_expr, .. } => json!({ + ASTNode::MatchExpr { + scrutinee, + arms, + else_expr, + .. + } => json!({ "kind":"MatchExpr", "scrutinee": ast_to_json(&scrutinee), "arms": arms.into_iter().map(|(lit, body)| json!({ @@ -108,45 +150,163 @@ pub fn json_to_ast(v: &Value) -> Option { let k = v.get("kind")?.as_str()?; Some(match k { "Program" => { - let stmts = v.get("statements")?.as_array()?.iter().filter_map(json_to_ast).collect::>(); - ASTNode::Program { statements: stmts, span: Span::unknown() } + let stmts = v + .get("statements")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect::>(); + ASTNode::Program { + statements: stmts, + span: Span::unknown(), + } } "Loop" => ASTNode::Loop { condition: Box::new(json_to_ast(v.get("condition")?)?), - body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::>(), + body: v + .get("body")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect::>(), span: Span::unknown(), }, - "Print" => ASTNode::Print { expression: Box::new(json_to_ast(v.get("expression")?)?), span: Span::unknown() }, - "Return" => ASTNode::Return { value: v.get("value").and_then(json_to_ast).map(Box::new), span: Span::unknown() }, - "Break" => ASTNode::Break { span: Span::unknown() }, - "Continue" => ASTNode::Continue { span: Span::unknown() }, - "Assignment" => ASTNode::Assignment { target: Box::new(json_to_ast(v.get("target")?)?), value: Box::new(json_to_ast(v.get("value")?)?), span: Span::unknown() }, - "Local" => { - let vars = v.get("variables")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect(); - let inits = v.get("inits")?.as_array()?.iter().map(|initv| { - if initv.is_null() { None } else { json_to_ast(initv).map(Box::new) } - }).collect(); - ASTNode::Local { variables: vars, initial_values: inits, span: Span::unknown() } + "Print" => ASTNode::Print { + expression: Box::new(json_to_ast(v.get("expression")?)?), + span: Span::unknown(), + }, + "Return" => ASTNode::Return { + value: v.get("value").and_then(json_to_ast).map(Box::new), + span: Span::unknown(), + }, + "Break" => ASTNode::Break { + span: Span::unknown(), + }, + "Continue" => ASTNode::Continue { + span: Span::unknown(), + }, + "Assignment" => ASTNode::Assignment { + target: Box::new(json_to_ast(v.get("target")?)?), + value: Box::new(json_to_ast(v.get("value")?)?), + span: Span::unknown(), + }, + "Local" => { + let vars = v + .get("variables")? + .as_array()? + .iter() + .filter_map(|s| s.as_str().map(|x| x.to_string())) + .collect(); + let inits = v + .get("inits")? + .as_array()? + .iter() + .map(|initv| { + if initv.is_null() { + None + } else { + json_to_ast(initv).map(Box::new) + } + }) + .collect(); + ASTNode::Local { + variables: vars, + initial_values: inits, + span: Span::unknown(), + } + } + "If" => ASTNode::If { + condition: Box::new(json_to_ast(v.get("condition")?)?), + then_body: v + .get("then")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect::>(), + else_body: v.get("else").and_then(|a| { + a.as_array() + .map(|arr| arr.iter().filter_map(json_to_ast).collect::>()) + }), + span: Span::unknown(), }, - "If" => ASTNode::If { condition: Box::new(json_to_ast(v.get("condition")?)?), then_body: v.get("then")?.as_array()?.iter().filter_map(json_to_ast).collect::>(), else_body: v.get("else").and_then(|a| a.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::>())), span: Span::unknown() }, "FunctionDeclaration" => ASTNode::FunctionDeclaration { name: v.get("name")?.as_str()?.to_string(), - params: v.get("params")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect(), - body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect(), + params: v + .get("params")? + .as_array()? + .iter() + .filter_map(|s| s.as_str().map(|x| x.to_string())) + .collect(), + body: v + .get("body")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect(), is_static: v.get("static").and_then(|b| b.as_bool()).unwrap_or(false), is_override: v.get("override").and_then(|b| b.as_bool()).unwrap_or(false), span: Span::unknown(), }, - "Variable" => ASTNode::Variable { name: v.get("name")?.as_str()?.to_string(), span: Span::unknown() }, - "Literal" => ASTNode::Literal { value: json_to_lit(v.get("value")?)?, span: Span::unknown() }, - "BinaryOp" => ASTNode::BinaryOp { operator: str_to_bin(v.get("op")?.as_str()?)?, left: Box::new(json_to_ast(v.get("left")?)?), right: Box::new(json_to_ast(v.get("right")?)?), span: Span::unknown() }, - "UnaryOp" => ASTNode::UnaryOp { operator: str_to_un(v.get("op")?.as_str()?)?, operand: Box::new(json_to_ast(v.get("operand")?)?), span: Span::unknown() }, - "MethodCall" => ASTNode::MethodCall { object: Box::new(json_to_ast(v.get("object")?)?), method: v.get("method")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() }, - "FunctionCall" => ASTNode::FunctionCall { name: v.get("name")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() }, - "Array" => ASTNode::ArrayLiteral { elements: v.get("elements")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() }, - "Map" => ASTNode::MapLiteral { entries: v.get("entries")?.as_array()?.iter().filter_map(|e| { - Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?)) - }).collect(), span: Span::unknown() }, + "Variable" => ASTNode::Variable { + name: v.get("name")?.as_str()?.to_string(), + span: Span::unknown(), + }, + "Literal" => ASTNode::Literal { + value: json_to_lit(v.get("value")?)?, + span: Span::unknown(), + }, + "BinaryOp" => ASTNode::BinaryOp { + operator: str_to_bin(v.get("op")?.as_str()?)?, + left: Box::new(json_to_ast(v.get("left")?)?), + right: Box::new(json_to_ast(v.get("right")?)?), + span: Span::unknown(), + }, + "UnaryOp" => ASTNode::UnaryOp { + operator: str_to_un(v.get("op")?.as_str()?)?, + operand: Box::new(json_to_ast(v.get("operand")?)?), + span: Span::unknown(), + }, + "MethodCall" => ASTNode::MethodCall { + object: Box::new(json_to_ast(v.get("object")?)?), + method: v.get("method")?.as_str()?.to_string(), + arguments: v + .get("arguments")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect(), + span: Span::unknown(), + }, + "FunctionCall" => ASTNode::FunctionCall { + name: v.get("name")?.as_str()?.to_string(), + arguments: v + .get("arguments")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect(), + span: Span::unknown(), + }, + "Array" => ASTNode::ArrayLiteral { + elements: v + .get("elements")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect(), + span: Span::unknown(), + }, + "Map" => ASTNode::MapLiteral { + entries: v + .get("entries")? + .as_array()? + .iter() + .filter_map(|e| { + Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?)) + }) + .collect(), + span: Span::unknown(), + }, "MatchExpr" => { let scr = json_to_ast(v.get("scrutinee")?)?; let arms_json = v.get("arms")?.as_array()?.iter(); @@ -166,18 +326,47 @@ pub fn json_to_ast(v: &Value) -> Option { } } "TryCatch" => { - let try_b = v.get("try")?.as_array()?.iter().filter_map(json_to_ast).collect::>(); + let try_b = v + .get("try")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect::>(); let mut catches = Vec::new(); if let Some(arr) = v.get("catch").and_then(|x| x.as_array()) { for c in arr.iter() { - let exc_t = match c.get("type") { Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()), _ => None }; - let var = match c.get("var") { Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()), _ => None }; - let body = c.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::>(); - catches.push(nyash_rust::ast::CatchClause { exception_type: exc_t, variable_name: var, body, span: Span::unknown() }); + let exc_t = match c.get("type") { + Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()), + _ => None, + }; + let var = match c.get("var") { + Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()), + _ => None, + }; + let body = c + .get("body")? + .as_array()? + .iter() + .filter_map(json_to_ast) + .collect::>(); + catches.push(nyash_rust::ast::CatchClause { + exception_type: exc_t, + variable_name: var, + body, + span: Span::unknown(), + }); } } - let cleanup = v.get("cleanup").and_then(|cl| cl.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::>())); - ASTNode::TryCatch { try_body: try_b, catch_clauses: catches, finally_body: cleanup, span: Span::unknown() } + let cleanup = v.get("cleanup").and_then(|cl| { + cl.as_array() + .map(|arr| arr.iter().filter_map(json_to_ast).collect::>()) + }); + ASTNode::TryCatch { + try_body: try_b, + catch_clauses: catches, + finally_body: cleanup, + span: Span::unknown(), + } } _ => return None, }) diff --git a/src/macro/ctx.rs b/src/macro/ctx.rs index c313c5c2..3cfd1ef4 100644 --- a/src/macro/ctx.rs +++ b/src/macro/ctx.rs @@ -32,14 +32,18 @@ impl MacroCtx { } } - pub fn gensym(&self, prefix: &str) -> String { gensym(prefix) } + pub fn gensym(&self, prefix: &str) -> String { + gensym(prefix) + } pub fn report(&self, level: &str, message: &str) { eprintln!("[macro][{}] {}", level, message); } pub fn get_env(&self, key: &str) -> Option { - if !self.caps.env { return None; } + if !self.caps.env { + return None; + } std::env::var(key).ok() } } diff --git a/src/macro/engine.rs b/src/macro/engine.rs index 40212c28..a54eb023 100644 --- a/src/macro/engine.rs +++ b/src/macro/engine.rs @@ -1,5 +1,5 @@ use nyash_rust::ast::Span; -use nyash_rust::{ASTNode, ast::LiteralValue, ast::BinaryOperator}; +use nyash_rust::{ast::BinaryOperator, ast::LiteralValue, ASTNode}; use std::time::Instant; /// HIR Patch description (MVP placeholder) @@ -16,10 +16,20 @@ pub struct MacroEngine { impl MacroEngine { pub fn new() -> Self { - let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES").ok().and_then(|v| v.parse().ok()).unwrap_or(32); - let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW").ok().and_then(|v| v.parse().ok()).unwrap_or(8); + let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(32); + let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(8); let trace = std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1"); - Self { max_passes, cycle_window, trace } + Self { + max_passes, + cycle_window, + trace, + } } /// Expand all macros with depth/cycle guards and return patched AST. @@ -29,25 +39,48 @@ impl MacroEngine { let mut history: std::collections::VecDeque = std::collections::VecDeque::new(); for pass in 0..self.max_passes { let t0 = Instant::now(); - let before_len = crate::r#macro::ast_json::ast_to_json(&cur).to_string().len(); + let before_len = crate::r#macro::ast_json::ast_to_json(&cur) + .to_string() + .len(); let next0 = self.expand_node(&cur); // Apply user MacroBoxes once per pass (if enabled) let next = crate::r#macro::macro_box::expand_all_once(&next0); - let after_len = crate::r#macro::ast_json::ast_to_json(&next).to_string().len(); + let after_len = crate::r#macro::ast_json::ast_to_json(&next) + .to_string() + .len(); let dt = t0.elapsed(); - if self.trace { eprintln!("[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}", pass, (next != cur), before_len, after_len, dt); } + if self.trace { + eprintln!( + "[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}", + pass, + (next != cur), + before_len, + after_len, + dt + ); + } jsonl_trace(pass, before_len, after_len, next != cur, dt); - if next == cur { return (cur, patches); } + if next == cur { + return (cur, patches); + } // cycle detection in small window if history.iter().any(|h| *h == next) { - eprintln!("[macro][engine] cycle detected at pass {} — stopping expansion", pass); + eprintln!( + "[macro][engine] cycle detected at pass {} — stopping expansion", + pass + ); return (cur, patches); } history.push_back(cur); - if history.len() > self.cycle_window { let _ = history.pop_front(); } + if history.len() > self.cycle_window { + let _ = history.pop_front(); + } cur = next; } - eprintln!("[macro][engine] max passes ({}) exceeded — stopping expansion", self.max_passes); + eprintln!( + "[macro][engine] max passes ({}) exceeded — stopping expansion", + self.max_passes + ); (cur, patches) } @@ -57,23 +90,55 @@ impl MacroEngine { if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro][visit] Program: statements={}", statements.len()); } - let new_stmts = statements.into_iter().map(|n| { - if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { - eprintln!("[macro][visit] child kind...",); - } - self.expand_node(&n) - }).collect(); - ASTNode::Program { statements: new_stmts, span } + let new_stmts = statements + .into_iter() + .map(|n| { + if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { + eprintln!("[macro][visit] child kind...",); + } + self.expand_node(&n) + }) + .collect(); + ASTNode::Program { + statements: new_stmts, + span, + } } - ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, mut methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span } => { + ASTNode::BoxDeclaration { + name, + fields, + public_fields, + private_fields, + mut methods, + constructors, + init_fields, + weak_fields, + is_interface, + extends, + implements, + type_parameters, + is_static, + static_init, + span, + } => { if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { - eprintln!("[macro][visit] BoxDeclaration: {} (fields={})", name, fields.len()); + eprintln!( + "[macro][visit] BoxDeclaration: {} (fields={})", + name, + fields.len() + ); } // Derive set: default Equals+ToString when macro is enabled - let derive_all = std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1"); - let derive_set = std::env::var("NYASH_MACRO_DERIVE").ok().unwrap_or_else(|| "Equals,ToString".to_string()); + let derive_all = + std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1"); + let derive_set = std::env::var("NYASH_MACRO_DERIVE") + .ok() + .unwrap_or_else(|| "Equals,ToString".to_string()); if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { - eprintln!("[macro][derive] box={} derive_all={} set={}", name, derive_all, derive_set); + eprintln!( + "[macro][derive] box={} derive_all={} set={}", + name, derive_all, derive_set + ); } let want_equals = derive_all || derive_set.contains("Equals"); let want_tostring = derive_all || derive_set.contains("ToString"); @@ -81,19 +146,43 @@ impl MacroEngine { let field_view: &Vec = &public_fields; if want_equals && !methods.contains_key("equals") { if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { - eprintln!("[macro][derive] equals for {} (public fields: {})", name, field_view.len()); + eprintln!( + "[macro][derive] equals for {} (public fields: {})", + name, + field_view.len() + ); } let m = build_equals_method(&name, field_view); methods.insert("equals".to_string(), m); } if want_tostring && !methods.contains_key("toString") { if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { - eprintln!("[macro][derive] toString for {} (public fields: {})", name, field_view.len()); + eprintln!( + "[macro][derive] toString for {} (public fields: {})", + name, + field_view.len() + ); } let m = build_tostring_method(&name, field_view); methods.insert("toString".to_string(), m); } - ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span } + ASTNode::BoxDeclaration { + name, + fields, + public_fields, + private_fields, + methods, + constructors, + init_fields, + weak_fields, + is_interface, + extends, + implements, + type_parameters, + is_static, + static_init, + span, + } } other => other, } @@ -102,7 +191,9 @@ impl MacroEngine { fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std::time::Duration) { if let Ok(path) = std::env::var("NYASH_MACRO_TRACE_JSONL") { - if path.is_empty() { return; } + if path.is_empty() { + return; + } let rec = serde_json::json!({ "event": "macro_pass", "pass": pass, @@ -110,17 +201,24 @@ fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std: "before_bytes": before, "after_bytes": after, "dt_us": dt.as_micros() as u64, - }).to_string(); - let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| { - use std::io::Write; - writeln!(f, "{}", rec) - }); + }) + .to_string(); + let _ = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "{}", rec) + }); } } fn me_field(name: &str) -> ASTNode { ASTNode::FieldAccess { - object: Box::new(ASTNode::Me { span: Span::unknown() }), + object: Box::new(ASTNode::Me { + span: Span::unknown(), + }), field: name.to_string(), span: Span::unknown(), } @@ -128,30 +226,56 @@ fn me_field(name: &str) -> ASTNode { fn var_field(var: &str, field: &str) -> ASTNode { ASTNode::FieldAccess { - object: Box::new(ASTNode::Variable { name: var.to_string(), span: Span::unknown() }), + object: Box::new(ASTNode::Variable { + name: var.to_string(), + span: Span::unknown(), + }), field: field.to_string(), span: Span::unknown(), } } fn bin_add(lhs: ASTNode, rhs: ASTNode) -> ASTNode { - ASTNode::BinaryOp { operator: BinaryOperator::Add, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() } + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(lhs), + right: Box::new(rhs), + span: Span::unknown(), + } } fn bin_and(lhs: ASTNode, rhs: ASTNode) -> ASTNode { - ASTNode::BinaryOp { operator: BinaryOperator::And, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() } + ASTNode::BinaryOp { + operator: BinaryOperator::And, + left: Box::new(lhs), + right: Box::new(rhs), + span: Span::unknown(), + } } fn bin_eq(lhs: ASTNode, rhs: ASTNode) -> ASTNode { - ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() } + ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(lhs), + right: Box::new(rhs), + span: Span::unknown(), + } } -fn lit_str(s: &str) -> ASTNode { ASTNode::Literal { value: LiteralValue::String(s.to_string()), span: Span::unknown() } } +fn lit_str(s: &str) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::String(s.to_string()), + span: Span::unknown(), + } +} fn build_equals_method(_box_name: &str, fields: &Vec) -> ASTNode { // equals(other) { return me.f1 == other.f1 && ...; } let cond = if fields.is_empty() { - ASTNode::Literal { value: LiteralValue::Bool(true), span: Span::unknown() } + ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + } } else { let mut it = fields.iter(); let first = it.next().unwrap(); @@ -166,7 +290,10 @@ fn build_equals_method(_box_name: &str, fields: &Vec) -> ASTNode { ASTNode::FunctionDeclaration { name: "equals".to_string(), params: vec![param_name.clone()], - body: vec![ASTNode::Return { value: Some(Box::new(cond)), span: Span::unknown() }], + body: vec![ASTNode::Return { + value: Some(Box::new(cond)), + span: Span::unknown(), + }], is_static: false, is_override: false, span: Span::unknown(), @@ -178,7 +305,9 @@ fn build_tostring_method(box_name: &str, fields: &Vec) -> ASTNode { let mut expr = lit_str(&format!("{}(", box_name)); let mut first = true; for f in fields { - if !first { expr = bin_add(expr, lit_str(",")); } + if !first { + expr = bin_add(expr, lit_str(",")); + } first = false; expr = bin_add(expr, me_field(f)); } @@ -186,7 +315,10 @@ fn build_tostring_method(box_name: &str, fields: &Vec) -> ASTNode { ASTNode::FunctionDeclaration { name: "toString".to_string(), params: vec![], - body: vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }], + body: vec![ASTNode::Return { + value: Some(Box::new(expr)), + span: Span::unknown(), + }], is_static: false, is_override: false, span: Span::unknown(), diff --git a/src/macro/macro_box.rs b/src/macro/macro_box.rs index c831e7c3..103b783b 100644 --- a/src/macro/macro_box.rs +++ b/src/macro/macro_box.rs @@ -1,5 +1,5 @@ -use std::sync::{Mutex, OnceLock}; use nyash_rust::ASTNode; +use std::sync::{Mutex, OnceLock}; /// MacroBox API — user-extensible macro expansion units (experimental) /// @@ -33,13 +33,17 @@ pub fn register(m: &'static dyn MacroBox) { /// Legacy env `NYASH_MACRO_BOX=1` still forces ON, but by default we /// synchronize with the macro system gate so user macros run when macros are enabled. pub fn enabled() -> bool { - if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") { return true; } + if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") { + return true; + } super::enabled() } /// Expand AST by applying all registered MacroBoxes in order once. pub fn expand_all_once(ast: &ASTNode) -> ASTNode { - if !enabled() { return ast.clone(); } + if !enabled() { + return ast.clone(); + } let reg = registry(); let guard = reg.lock().expect("macro registry poisoned"); let mut cur = ast.clone(); @@ -55,39 +59,127 @@ pub fn expand_all_once(ast: &ASTNode) -> ASTNode { pub struct UppercasePrintMacro; impl MacroBox for UppercasePrintMacro { - fn name(&self) -> &'static str { "UppercasePrintMacro" } + fn name(&self) -> &'static str { + "UppercasePrintMacro" + } fn expand(&self, ast: &ASTNode) -> ASTNode { use nyash_rust::ast::{ASTNode as A, LiteralValue, Span}; fn go(n: &A) -> A { match n.clone() { - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|c| go(&c)).collect(), span }, + A::Program { statements, span } => A::Program { + statements: statements.into_iter().map(|c| go(&c)).collect(), + span, + }, A::Print { expression, span } => { match &*expression { - A::Literal { value: LiteralValue::String(s), .. } => { + A::Literal { + value: LiteralValue::String(s), + .. + } => { // Demo: if string starts with "UPPER:", uppercase the rest. if let Some(rest) = s.strip_prefix("UPPER:") { let up = rest.to_uppercase(); - A::Print { expression: Box::new(A::Literal { value: LiteralValue::String(up), span: Span::unknown() }), span } - } else { A::Print { expression: Box::new(go(&*expression)), span } } + A::Print { + expression: Box::new(A::Literal { + value: LiteralValue::String(up), + span: Span::unknown(), + }), + span, + } + } else { + A::Print { + expression: Box::new(go(&*expression)), + span, + } + } } - other => A::Print { expression: Box::new(go(other)), span } + other => A::Print { + expression: Box::new(go(other)), + span, + }, } } - A::Assignment { target, value, span } => A::Assignment { target: Box::new(go(&*target)), value: Box::new(go(&*value)), span }, - A::If { condition, then_body, else_body, span } => A::If { + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(go(&*target)), + value: Box::new(go(&*value)), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { condition: Box::new(go(&*condition)), then_body: then_body.into_iter().map(|c| go(&c)).collect(), else_body: else_body.map(|v| v.into_iter().map(|c| go(&c)).collect()), span, }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(go(v))), span }, - A::FieldAccess { object, field, span } => A::FieldAccess { object: Box::new(go(&*object)), field, span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(go(&*object)), method, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(go(&*left)), right: Box::new(go(&*right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(go(&*operand)), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|c| go(&c)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, go(&v))).collect(), span }, + A::Return { value, span } => A::Return { + value: value.as_ref().map(|v| Box::new(go(v))), + span, + }, + A::FieldAccess { + object, + field, + span, + } => A::FieldAccess { + object: Box::new(go(&*object)), + field, + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(go(&*object)), + method, + arguments: arguments.into_iter().map(|c| go(&c)).collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments.into_iter().map(|c| go(&c)).collect(), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(go(&*left)), + right: Box::new(go(&*right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(go(&*operand)), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements.into_iter().map(|c| go(&c)).collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries.into_iter().map(|(k, v)| (k, go(&v))).collect(), + span, + }, other => other, } } @@ -101,13 +193,17 @@ static INIT_FLAG: OnceLock<()> = OnceLock::new(); pub fn init_builtin() { INIT_FLAG.get_or_init(|| { // Explicit example toggle - if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") { register(&UppercasePrintMacro); } + if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") { + register(&UppercasePrintMacro); + } // Comma-separated names: NYASH_MACRO_BOX_ENABLE="UppercasePrintMacro,Other" if let Ok(list) = std::env::var("NYASH_MACRO_BOX_ENABLE") { for name in list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { match name { "UppercasePrintMacro" => register(&UppercasePrintMacro), - _ => { eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name); } + _ => { + eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name); + } } } } diff --git a/src/macro/macro_box_ny.rs b/src/macro/macro_box_ny.rs index 03a2225b..b4cb3f52 100644 --- a/src/macro/macro_box_ny.rs +++ b/src/macro/macro_box_ny.rs @@ -28,7 +28,9 @@ pub fn init_from_env() { if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() { eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; runner mode is managed automatically"); } - let Some(paths) = paths else { return; }; + let Some(paths) = paths else { + return; + }; for p in paths.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { if let Err(e) = try_load_one(p) { // Quiet by default; print only when tracing is enabled to reduce noise in normal runs @@ -47,35 +49,72 @@ fn try_load_one(path: &str) -> Result<(), String> { let prev_sugar = std::env::var("NYASH_SYNTAX_SUGAR_LEVEL").ok(); std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", "basic"); let ast_res = nyash_rust::parser::NyashParser::parse_from_string(&src); - if let Some(v) = prev_sugar { std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v); } else { std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL"); } + if let Some(v) = prev_sugar { + std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v); + } else { + std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL"); + } let ast = ast_res.map_err(|e| format!("parse error: {:?}", e))?; // Find a BoxDeclaration with static function expand(...) if let ASTNode::Program { statements, .. } = ast { // Capabilities: conservative scan before registration - if let Err(msg) = caps_allow_macro_source(&ASTNode::Program { statements: statements.clone(), span: nyash_rust::ast::Span::unknown() }) { + if let Err(msg) = caps_allow_macro_source(&ASTNode::Program { + statements: statements.clone(), + span: nyash_rust::ast::Span::unknown(), + }) { eprintln!("[macro][box_ny][caps] {} (in '{}')", msg, path); - if strict_enabled() { return Err(msg); } + if strict_enabled() { + return Err(msg); + } return Ok(()); } for st in &statements { - if let ASTNode::BoxDeclaration { name: box_name, methods, .. } = st { - if let Some(ASTNode::FunctionDeclaration { name: mname, body: exp_body, params, .. }) = methods.get("expand") { + if let ASTNode::BoxDeclaration { + name: box_name, + methods, + .. + } = st + { + if let Some(ASTNode::FunctionDeclaration { + name: mname, + body: exp_body, + params, + .. + }) = methods.get("expand") + { if mname == "expand" { let reg_name = derive_box_name(&box_name, methods.get("name")); // Prefer Nyash runner route by default (self-hosting). Child-proxy only when explicitly enabled. - let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true); + let use_child = std::env::var("NYASH_MACRO_BOX_CHILD") + .ok() + .map(|v| v != "0" && v != "false" && v != "off") + .unwrap_or(true); if use_child { let nm = reg_name; - let file_static: &'static str = Box::leak(path.to_string().into_boxed_str()); - crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static }))); - eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' for {}", nm, path); + let file_static: &'static str = + Box::leak(path.to_string().into_boxed_str()); + crate::r#macro::macro_box::register(Box::leak(Box::new( + NyChildMacroBox { + nm, + file: file_static, + }, + ))); + eprintln!( + "[macro][box_ny] registered child-proxy MacroBox '{}' for {}", + nm, path + ); } else { // Heuristic mapping by name first, otherwise inspect body pattern. let mut mapped = false; match reg_name { "UppercasePrintMacro" => { - crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro); - eprintln!("[macro][box_ny] registered built-in '{}' from {}", reg_name, path); + crate::r#macro::macro_box::register( + &crate::r#macro::macro_box::UppercasePrintMacro, + ); + eprintln!( + "[macro][box_ny] registered built-in '{}' from {}", + reg_name, path + ); mapped = true; } _ => {} @@ -83,14 +122,20 @@ fn try_load_one(path: &str) -> Result<(), String> { if !mapped { if expand_is_identity(exp_body, params) { let nm = reg_name; - crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm }))); + crate::r#macro::macro_box::register(Box::leak(Box::new( + NyIdentityMacroBox { nm }, + ))); eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity by body) from {}", nm, path); } else if expand_indicates_uppercase(exp_body, params) { - crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro); + crate::r#macro::macro_box::register( + &crate::r#macro::macro_box::UppercasePrintMacro, + ); eprintln!("[macro][box_ny] registered built-in 'UppercasePrintMacro' by body pattern from {}", path); } else { let nm = reg_name; - crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm }))); + crate::r#macro::macro_box::register(Box::leak(Box::new( + NyIdentityMacroBox { nm }, + ))); eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity: unknown body) from {}", nm, path); } } @@ -102,19 +147,38 @@ fn try_load_one(path: &str) -> Result<(), String> { } // Fallback: accept top-level `static function MacroBoxSpec.expand(json)` without a BoxDeclaration // Default OFF for safety; can be enabled via CLI/env - let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false); + let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW") + .ok() + .map(|v| v != "0" && v != "false" && v != "off") + .unwrap_or(false); for st in &statements { - if let ASTNode::FunctionDeclaration { is_static: true, name, .. } = st { + if let ASTNode::FunctionDeclaration { + is_static: true, + name, + .. + } = st + { if let Some((box_name, method)) = name.split_once('.') { if method == "expand" { let nm: &'static str = Box::leak(box_name.to_string().into_boxed_str()); - let file_static: &'static str = Box::leak(path.to_string().into_boxed_str()); - let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true); + let file_static: &'static str = + Box::leak(path.to_string().into_boxed_str()); + let use_child = std::env::var("NYASH_MACRO_BOX_CHILD") + .ok() + .map(|v| v != "0" && v != "false" && v != "off") + .unwrap_or(true); if use_child && allow_top { - crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static }))); + crate::r#macro::macro_box::register(Box::leak(Box::new( + NyChildMacroBox { + nm, + file: file_static, + }, + ))); eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' (top-level static) for {}", nm, path); } else { - crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm }))); + crate::r#macro::macro_box::register(Box::leak(Box::new( + NyIdentityMacroBox { nm }, + ))); eprintln!("[macro][box_ny] registered identity MacroBox '{}' (top-level static) for {}", nm, path); } return Ok(()); @@ -131,7 +195,11 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str { if let Some(ASTNode::FunctionDeclaration { body, .. }) = name_fn { if body.len() == 1 { if let ASTNode::Return { value: Some(v), .. } = &body[0] { - if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v { + if let ASTNode::Literal { + value: nyash_rust::ast::LiteralValue::String(s), + .. + } = &**v + { let owned = s.clone(); return Box::leak(owned.into_boxed_str()); } @@ -141,21 +209,33 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str { Box::leak(default.to_string().into_boxed_str()) } -pub(crate) struct NyIdentityMacroBox { nm: &'static str } +pub(crate) struct NyIdentityMacroBox { + nm: &'static str, +} impl super::macro_box::MacroBox for NyIdentityMacroBox { - fn name(&self) -> &'static str { self.nm } + fn name(&self) -> &'static str { + self.nm + } fn expand(&self, ast: &ASTNode) -> ASTNode { - if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP").ok().as_deref() == Some("1") { + if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP") + .ok() + .as_deref() + == Some("1") + { let j = crate::r#macro::ast_json::ast_to_json(ast); - if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) { return a2; } + if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) { + return a2; + } } ast.clone() } } fn expand_is_identity(body: &Vec, params: &Vec) -> bool { - if body.len() != 1 { return false; } + if body.len() != 1 { + return false; + } if let ASTNode::Return { value: Some(v), .. } = &body[0] { if let ASTNode::Variable { name, .. } = &**v { return params.get(0).map(|p| p == name).unwrap_or(false); @@ -165,11 +245,15 @@ fn expand_is_identity(body: &Vec, params: &Vec) -> bool { } fn expand_indicates_uppercase(body: &Vec, params: &Vec) -> bool { - if body.len() != 1 { return false; } + if body.len() != 1 { + return false; + } let p0 = params.get(0).cloned().unwrap_or_else(|| "ast".to_string()); match &body[0] { ASTNode::Return { value: Some(v), .. } => match &**v { - ASTNode::FunctionCall { name, arguments, .. } => { + ASTNode::FunctionCall { + name, arguments, .. + } => { if (name == "uppercase_print" || name == "upper_print") && arguments.len() == 1 { if let ASTNode::Variable { name: an, .. } = &arguments[0] { return an == &p0; @@ -184,32 +268,80 @@ fn expand_indicates_uppercase(body: &Vec, params: &Vec) -> bool } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MacroBehavior { Identity, Uppercase, ArrayPrependZero, MapInsertTag, LoopNormalize, IfMatchNormalize, ForForeachNormalize, EnvTagString } +pub enum MacroBehavior { + Identity, + Uppercase, + ArrayPrependZero, + MapInsertTag, + LoopNormalize, + IfMatchNormalize, + ForForeachNormalize, + EnvTagString, +} pub fn analyze_macro_file(path: &str) -> MacroBehavior { - let src = match std::fs::read_to_string(path) { Ok(s) => s, Err(_) => return MacroBehavior::Identity }; - let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) { Ok(a) => a, Err(_) => return MacroBehavior::Identity }; + let src = match std::fs::read_to_string(path) { + Ok(s) => s, + Err(_) => return MacroBehavior::Identity, + }; + let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) { + Ok(a) => a, + Err(_) => return MacroBehavior::Identity, + }; // Quick heuristics based on literals present in file fn ast_has_literal_string(a: &ASTNode, needle: &str) -> bool { use nyash_rust::ast::ASTNode as A; match a { - A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => s.contains(needle), - A::Program { statements, .. } => statements.iter().any(|n| ast_has_literal_string(n, needle)), + A::Literal { + value: nyash_rust::ast::LiteralValue::String(s), + .. + } => s.contains(needle), + A::Program { statements, .. } => { + statements.iter().any(|n| ast_has_literal_string(n, needle)) + } A::Print { expression, .. } => ast_has_literal_string(expression, needle), - A::Return { value, .. } => value.as_ref().map(|v| ast_has_literal_string(v, needle)).unwrap_or(false), - A::Assignment { target, value, .. } => ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle), - A::If { condition, then_body, else_body, .. } => { + A::Return { value, .. } => value + .as_ref() + .map(|v| ast_has_literal_string(v, needle)) + .unwrap_or(false), + A::Assignment { target, value, .. } => { + ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle) + } + A::If { + condition, + then_body, + else_body, + .. + } => { ast_has_literal_string(condition, needle) || then_body.iter().any(|n| ast_has_literal_string(n, needle)) - || else_body.as_ref().map(|v| v.iter().any(|n| ast_has_literal_string(n, needle))).unwrap_or(false) + || else_body + .as_ref() + .map(|v| v.iter().any(|n| ast_has_literal_string(n, needle))) + .unwrap_or(false) + } + A::FunctionDeclaration { body, .. } => { + body.iter().any(|n| ast_has_literal_string(n, needle)) + } + A::BinaryOp { left, right, .. } => { + ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle) } - A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_literal_string(n, needle)), - A::BinaryOp { left, right, .. } => ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle), A::UnaryOp { operand, .. } => ast_has_literal_string(operand, needle), - A::MethodCall { object, arguments, .. } => ast_has_literal_string(object, needle) || arguments.iter().any(|n| ast_has_literal_string(n, needle)), - A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_literal_string(n, needle)), - A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_literal_string(n, needle)), - A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_literal_string(v, needle)), + A::MethodCall { + object, arguments, .. + } => { + ast_has_literal_string(object, needle) + || arguments.iter().any(|n| ast_has_literal_string(n, needle)) + } + A::FunctionCall { arguments, .. } => { + arguments.iter().any(|n| ast_has_literal_string(n, needle)) + } + A::ArrayLiteral { elements, .. } => { + elements.iter().any(|n| ast_has_literal_string(n, needle)) + } + A::MapLiteral { entries, .. } => entries + .iter() + .any(|(_, v)| ast_has_literal_string(v, needle)), _ => false, } } @@ -218,29 +350,59 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior { match a { A::Program { statements, .. } => statements.iter().any(|n| ast_has_method(n, method)), A::Print { expression, .. } => ast_has_method(expression, method), - A::Return { value, .. } => value.as_ref().map(|v| ast_has_method(v, method)).unwrap_or(false), - A::Assignment { target, value, .. } => ast_has_method(target, method) || ast_has_method(value, method), - A::If { condition, then_body, else_body, .. } => ast_has_method(condition, method) - || then_body.iter().any(|n| ast_has_method(n, method)) - || else_body.as_ref().map(|v| v.iter().any(|n| ast_has_method(n, method))).unwrap_or(false), + A::Return { value, .. } => value + .as_ref() + .map(|v| ast_has_method(v, method)) + .unwrap_or(false), + A::Assignment { target, value, .. } => { + ast_has_method(target, method) || ast_has_method(value, method) + } + A::If { + condition, + then_body, + else_body, + .. + } => { + ast_has_method(condition, method) + || then_body.iter().any(|n| ast_has_method(n, method)) + || else_body + .as_ref() + .map(|v| v.iter().any(|n| ast_has_method(n, method))) + .unwrap_or(false) + } A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_method(n, method)), - A::BinaryOp { left, right, .. } => ast_has_method(left, method) || ast_has_method(right, method), + A::BinaryOp { left, right, .. } => { + ast_has_method(left, method) || ast_has_method(right, method) + } A::UnaryOp { operand, .. } => ast_has_method(operand, method), - A::MethodCall { object, method: m, arguments, .. } => m == method - || ast_has_method(object, method) - || arguments.iter().any(|n| ast_has_method(n, method)), - A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_method(n, method)), + A::MethodCall { + object, + method: m, + arguments, + .. + } => { + m == method + || ast_has_method(object, method) + || arguments.iter().any(|n| ast_has_method(n, method)) + } + A::FunctionCall { arguments, .. } => { + arguments.iter().any(|n| ast_has_method(n, method)) + } A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_method(n, method)), A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_method(v, method)), _ => false, } } // Detect array prepend-zero macro by pattern strings present in macro source - if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[") || ast_has_literal_string(&ast, "\"elements\":[") { + if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[") + || ast_has_literal_string(&ast, "\"elements\":[") + { return MacroBehavior::ArrayPrependZero; } // Detect map insert-tag macro by pattern strings - if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[") || ast_has_literal_string(&ast, "\"entries\":[") { + if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[") + || ast_has_literal_string(&ast, "\"entries\":[") + { return MacroBehavior::MapInsertTag; } // Detect upper-string macro by pattern or toUpperCase usage @@ -253,23 +415,47 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior { } if let ASTNode::Program { statements, .. } = ast { for st in statements { - if let ASTNode::BoxDeclaration { name: _, methods, .. } = st { + if let ASTNode::BoxDeclaration { + name: _, methods, .. + } = st + { // Detect LoopNormalize/IfMatchNormalize by name() returning a specific string - if let Some(ASTNode::FunctionDeclaration { name: mname, body, .. }) = methods.get("name") { + if let Some(ASTNode::FunctionDeclaration { + name: mname, body, .. + }) = methods.get("name") + { if mname == "name" { if body.len() == 1 { if let ASTNode::Return { value: Some(v), .. } = &body[0] { - if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v { - if s == "LoopNormalize" { return MacroBehavior::LoopNormalize; } - if s == "IfMatchNormalize" { return MacroBehavior::IfMatchNormalize; } - if s == "ForForeach" { return MacroBehavior::ForForeachNormalize; } - if s == "EnvTagString" { return MacroBehavior::EnvTagString; } + if let ASTNode::Literal { + value: nyash_rust::ast::LiteralValue::String(s), + .. + } = &**v + { + if s == "LoopNormalize" { + return MacroBehavior::LoopNormalize; + } + if s == "IfMatchNormalize" { + return MacroBehavior::IfMatchNormalize; + } + if s == "ForForeach" { + return MacroBehavior::ForForeachNormalize; + } + if s == "EnvTagString" { + return MacroBehavior::EnvTagString; + } } } } } } - if let Some(ASTNode::FunctionDeclaration { name: mname, body, params, .. }) = methods.get("expand") { + if let Some(ASTNode::FunctionDeclaration { + name: mname, + body, + params, + .. + }) = methods.get("expand") + { if mname == "expand" { if expand_indicates_uppercase(body, params) { return MacroBehavior::Uppercase; @@ -282,7 +468,10 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior { MacroBehavior::Identity } -struct NyChildMacroBox { nm: &'static str, file: &'static str } +struct NyChildMacroBox { + nm: &'static str, + file: &'static str, +} fn cap_enabled(name: &str) -> bool { match std::env::var(name).ok() { @@ -301,67 +490,148 @@ fn caps_allow_macro_source(ast: &ASTNode) -> Result<(), String> { fn scan(n: &A, seen: &mut Vec) { match n { A::New { class, .. } => seen.push(class.clone()), - A::Program { statements, .. } => for s in statements { scan(s, seen); }, - A::FunctionDeclaration { body, .. } => for s in body { scan(s, seen); }, - A::Assignment { target, value, .. } => { scan(target, seen); scan(value, seen); }, - A::Return { value, .. } => if let Some(v) = value { scan(v, seen); }, - A::If { condition, then_body, else_body, .. } => { - scan(condition, seen); - for s in then_body { scan(s, seen); } - if let Some(b) = else_body { for s in b { scan(s, seen); } } + A::Program { statements, .. } => { + for s in statements { + scan(s, seen); + } + } + A::FunctionDeclaration { body, .. } => { + for s in body { + scan(s, seen); + } + } + A::Assignment { target, value, .. } => { + scan(target, seen); + scan(value, seen); + } + A::Return { value, .. } => { + if let Some(v) = value { + scan(v, seen); + } + } + A::If { + condition, + then_body, + else_body, + .. + } => { + scan(condition, seen); + for s in then_body { + scan(s, seen); + } + if let Some(b) = else_body { + for s in b { + scan(s, seen); + } + } + } + A::BinaryOp { left, right, .. } => { + scan(left, seen); + scan(right, seen); } - A::BinaryOp { left, right, .. } => { scan(left, seen); scan(right, seen); } A::UnaryOp { operand, .. } => scan(operand, seen), - A::MethodCall { object, arguments, .. } => { scan(object, seen); for a in arguments { scan(a, seen); } } - A::FunctionCall { arguments, .. } => for a in arguments { scan(a, seen); }, - A::ArrayLiteral { elements, .. } => for e in elements { scan(e, seen); }, - A::MapLiteral { entries, .. } => for (_, v) in entries { scan(v, seen); }, + A::MethodCall { + object, arguments, .. + } => { + scan(object, seen); + for a in arguments { + scan(a, seen); + } + } + A::FunctionCall { arguments, .. } => { + for a in arguments { + scan(a, seen); + } + } + A::ArrayLiteral { elements, .. } => { + for e in elements { + scan(e, seen); + } + } + A::MapLiteral { entries, .. } => { + for (_, v) in entries { + scan(v, seen); + } + } _ => {} } } let mut boxes = Vec::new(); scan(ast, &mut boxes); - if !allow_io && boxes.iter().any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox") { + if !allow_io + && boxes + .iter() + .any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox") + { return Err("macro capability violation: IO (File/Path/Dir) denied".into()); } - if !allow_net && boxes.iter().any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox") { + if !allow_net + && boxes + .iter() + .any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox") + { return Err("macro capability violation: NET (HTTP/Socket) denied".into()); } Ok(()) } impl super::macro_box::MacroBox for NyChildMacroBox { - fn name(&self) -> &'static str { self.nm } + fn name(&self) -> &'static str { + self.nm + } fn expand(&self, ast: &ASTNode) -> ASTNode { // Parent-side proxy: prefer runner script (PyVM) when enabled; otherwise fallback to internal child mode. let exe = match std::env::current_exe() { Ok(p) => p, - Err(e) => { eprintln!("[macro-proxy] current_exe failed: {}", e); return ast.clone(); } + Err(e) => { + eprintln!("[macro-proxy] current_exe failed: {}", e); + return ast.clone(); + } }; // Prefer Nyash runner route by default for self-hosting; legacy env can force internal child with 0. - let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false); + let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER") + .ok() + .map(|v| v != "0" && v != "false" && v != "off") + .unwrap_or(false); if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() { - eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults"); + eprintln!( + "[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults" + ); } let mut cmd = std::process::Command::new(exe.clone()); // Build MacroCtx JSON once (caps only, MVP) let mctx = crate::r#macro::ctx::MacroCtx::from_env(); - let ctx_json = format!("{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}", mctx.caps.io, mctx.caps.net, mctx.caps.env); + let ctx_json = format!( + "{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}", + mctx.caps.io, mctx.caps.net, mctx.caps.env + ); if use_runner { // Synthesize a tiny runner that inlines the macro file and calls MacroBoxSpec.expand use std::io::Write as _; let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); - let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis(); + let ts = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); let tmp_path = tmp_dir.join(format!("macro_expand_runner_{}.hako", ts)); - let mut f = match std::fs::File::create(&tmp_path) { Ok(x) => x, Err(e) => { eprintln!("[macro-proxy] create tmp runner failed: {}", e); return ast.clone(); } }; + let mut f = match std::fs::File::create(&tmp_path) { + Ok(x) => x, + Err(e) => { + eprintln!("[macro-proxy] create tmp runner failed: {}", e); + return ast.clone(); + } + }; let macro_src = std::fs::read_to_string(self.file) .unwrap_or_else(|_| String::from("// failed to read macro file\n")); let script = format!( "{}\n\nfunction main(args) {{\n if args.length() == 0 {{\n print(\"{{}}\")\n return 0\n }}\n local j, r, ctx\n j = args.get(0)\n if args.length() > 1 {{ ctx = args.get(1) }} else {{ ctx = \"{{}}\" }}\n r = MacroBoxSpec.expand(j, ctx)\n print(r)\n return 0\n}}\n", macro_src ); - if let Err(e) = f.write_all(script.as_bytes()) { eprintln!("[macro-proxy] write tmp runner failed: {}", e); return ast.clone(); } + if let Err(e) = f.write_all(script.as_bytes()) { + eprintln!("[macro-proxy] write tmp runner failed: {}", e); + return ast.clone(); + } // Run Nyash runner script under PyVM: nyash --backend vm -- cmd.arg("--backend").arg("vm").arg(tmp_path); // Append script args after '--' @@ -372,7 +642,8 @@ impl super::macro_box::MacroBox for NyChildMacroBox { cmd.stdin(std::process::Stdio::null()); } else { // Internal child mode: --macro-expand-child with stdin JSON - cmd.arg("--macro-expand-child").arg(self.file) + cmd.arg("--macro-expand-child") + .arg(self.file) .stdin(std::process::Stdio::piped()); // Provide MacroCtx via env for internal child cmd.env("NYASH_MACRO_CTX_JSON", ctx_json.clone()); @@ -393,13 +664,21 @@ impl super::macro_box::MacroBox for NyChildMacroBox { cmd.env_remove("NYASH_MACRO_BOX_CHILD"); cmd.env_remove("NYASH_MACRO_BOX_CHILD_RUNNER"); // Timeout - let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000); + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(2000); // Spawn - let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { - eprintln!("[macro-proxy] spawn failed: {}", e); - if strict_enabled() { std::process::exit(2); } - return ast.clone(); - } }; + let mut child = match cmd.spawn() { + Ok(c) => c, + Err(e) => { + eprintln!("[macro-proxy] spawn failed: {}", e); + if strict_enabled() { + std::process::exit(2); + } + return ast.clone(); + } + }; // Write stdin only in internal child mode if !use_runner { if let Some(mut sin) = child.stdin.take() { @@ -415,34 +694,60 @@ impl super::macro_box::MacroBox for NyChildMacroBox { loop { match child.try_wait() { Ok(Some(_status)) => { - if let Some(mut so) = child.stdout.take() { use std::io::Read; let _ = so.read_to_string(&mut out); } + if let Some(mut so) = child.stdout.take() { + use std::io::Read; + let _ = so.read_to_string(&mut out); + } break; } Ok(None) => { if start.elapsed() >= Duration::from_millis(timeout_ms) { - let _ = child.kill(); let _ = child.wait(); + let _ = child.kill(); + let _ = child.wait(); eprintln!("[macro-proxy] timeout {} ms", timeout_ms); - if strict_enabled() { std::process::exit(124); } + if strict_enabled() { + std::process::exit(124); + } return ast.clone(); } std::thread::sleep(Duration::from_millis(5)); } - Err(e) => { eprintln!("[macro-proxy] wait error: {}", e); if strict_enabled() { std::process::exit(2); } return ast.clone(); } + Err(e) => { + eprintln!("[macro-proxy] wait error: {}", e); + if strict_enabled() { + std::process::exit(2); + } + return ast.clone(); + } } } // capture stderr for diagnostics and continue // Capture stderr for diagnostics let mut err = String::new(); - if let Some(mut se) = child.stderr.take() { use std::io::Read; let _ = se.read_to_string(&mut err); } + if let Some(mut se) = child.stderr.take() { + use std::io::Read; + let _ = se.read_to_string(&mut err); + } // Parse output JSON match serde_json::from_str::(&out) { Ok(v) => match crate::r#macro::ast_json::json_to_ast(&v) { Some(a) => a, - None => { eprintln!("[macro-proxy] child JSON did not map to AST. stderr=\n{}", err); if strict_enabled() { std::process::exit(2); } ast.clone() } + None => { + eprintln!( + "[macro-proxy] child JSON did not map to AST. stderr=\n{}", + err + ); + if strict_enabled() { + std::process::exit(2); + } + ast.clone() + } }, Err(e) => { eprintln!("[macro-proxy] invalid JSON from child: {}\n-- child stderr --\n{}\n-- end stderr --", e, err); - if strict_enabled() { std::process::exit(2); } + if strict_enabled() { + std::process::exit(2); + } ast.clone() } } diff --git a/src/macro/mod.rs b/src/macro/mod.rs index 2e69badf..c33f48e9 100644 --- a/src/macro/mod.rs +++ b/src/macro/mod.rs @@ -3,22 +3,28 @@ //! Goal: Provide minimal, typed interfaces for AST pattern matching and //! HIR patch based expansion. Backends (MIR/JIT/LLVM) remain unchanged. -pub mod pattern; +pub mod ast_json; +pub mod ctx; pub mod engine; pub mod macro_box; pub mod macro_box_ny; -pub mod ast_json; -pub mod ctx; +pub mod pattern; use nyash_rust::ASTNode; /// Enable/disable macro system via env gate. pub fn enabled() -> bool { // Default ON. Disable with NYASH_MACRO_DISABLE=1 or NYASH_MACRO_ENABLE=0/false/off. - if let Ok(v) = std::env::var("NYASH_MACRO_DISABLE") { if v == "1" { return false; } } + if let Ok(v) = std::env::var("NYASH_MACRO_DISABLE") { + if v == "1" { + return false; + } + } if let Ok(v) = std::env::var("NYASH_MACRO_ENABLE") { let v = v.to_ascii_lowercase(); - if v == "0" || v == "false" || v == "off" { return false; } + if v == "0" || v == "false" || v == "off" { + return false; + } return true; } true @@ -26,15 +32,21 @@ pub fn enabled() -> bool { /// A hook to dump AST for `--expand` (pre/post). Expansion is no-op for now. pub fn maybe_expand_and_dump(ast: &ASTNode, _dump_only: bool) -> ASTNode { - if !enabled() { return ast.clone(); } + if !enabled() { + return ast.clone(); + } // Initialize user macro boxes (if any, behind env gates) self::macro_box::init_builtin(); self::macro_box_ny::init_from_env(); - if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro] input AST: {:?}", ast); } + if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { + eprintln!("[macro] input AST: {:?}", ast); + } let mut eng = self::engine::MacroEngine::new(); let (out, _patches) = eng.expand(ast); let out2 = maybe_inject_test_harness(&out); - if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro] output AST: {:?}", out2); } + if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { + eprintln!("[macro] output AST: {:?}", out2); + } out2 } @@ -44,7 +56,11 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { } // Test call plan #[derive(Clone)] - struct TestPlan { label: String, setup: Option, call: nyash_rust::ASTNode } + struct TestPlan { + label: String, + setup: Option, + call: nyash_rust::ASTNode, + } // Collect tests (top-level and Box) let mut tests: Vec = Vec::new(); @@ -56,9 +72,16 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { // - Detailed per-test: { "Box.method": { "args": [...], "instance": {"ctor":"new|birth","args":[...] } } } // - Typed values inside args supported via objects (see json_to_ast below) #[derive(Clone, Default)] - struct InstanceSpec { ctor: String, args: Vec, type_args: Vec } + struct InstanceSpec { + ctor: String, + args: Vec, + type_args: Vec, + } #[derive(Clone, Default)] - struct TestArgSpec { args: Vec, instance: Option } + struct TestArgSpec { + args: Vec, + instance: Option, + } fn json_err(msg: &str) { eprintln!("[macro][test][args] {}", msg); @@ -67,76 +90,163 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { fn json_to_ast(v: &serde_json::Value) -> Result { use nyash_rust::ast::{ASTNode as A, LiteralValue, Span}; match v { - serde_json::Value::String(st) => Ok(A::Literal { value: LiteralValue::String(st.clone()), span: Span::unknown() }), - serde_json::Value::Bool(b) => Ok(A::Literal { value: LiteralValue::Bool(*b), span: Span::unknown() }), + serde_json::Value::String(st) => Ok(A::Literal { + value: LiteralValue::String(st.clone()), + span: Span::unknown(), + }), + serde_json::Value::Bool(b) => Ok(A::Literal { + value: LiteralValue::Bool(*b), + span: Span::unknown(), + }), serde_json::Value::Number(n) => { if let Some(i) = n.as_i64() { - Ok(A::Literal { value: LiteralValue::Integer(i), span: Span::unknown() }) + Ok(A::Literal { + value: LiteralValue::Integer(i), + span: Span::unknown(), + }) } else if let Some(f) = n.as_f64() { - Ok(A::Literal { value: LiteralValue::Float(f), span: Span::unknown() }) + Ok(A::Literal { + value: LiteralValue::Float(f), + span: Span::unknown(), + }) } else { Err("unsupported number literal".into()) } } - serde_json::Value::Null => Ok(A::Literal { value: LiteralValue::Null, span: Span::unknown() }), + serde_json::Value::Null => Ok(A::Literal { + value: LiteralValue::Null, + span: Span::unknown(), + }), serde_json::Value::Array(elems) => { // Treat nested arrays as ArrayLiteral by default let mut out = Vec::with_capacity(elems.len()); - for x in elems { out.push(json_to_ast(x)?); } - Ok(A::ArrayLiteral { elements: out, span: Span::unknown() }) + for x in elems { + out.push(json_to_ast(x)?); + } + Ok(A::ArrayLiteral { + elements: out, + span: Span::unknown(), + }) } serde_json::Value::Object(obj) => { // Typed shorthands accepted: {i:1}|{int:1}, {f:1.2}|{float:1.2}, {s:"x"}|{string:"x"}, {b:true}|{bool:true} - if let Some(v) = obj.get("i").or_else(|| obj.get("int")) { return json_to_ast(v); } - if let Some(v) = obj.get("f").or_else(|| obj.get("float")) { return json_to_ast(v); } - if let Some(v) = obj.get("s").or_else(|| obj.get("string")) { return json_to_ast(v); } - if let Some(v) = obj.get("b").or_else(|| obj.get("bool")) { return json_to_ast(v); } + if let Some(v) = obj.get("i").or_else(|| obj.get("int")) { + return json_to_ast(v); + } + if let Some(v) = obj.get("f").or_else(|| obj.get("float")) { + return json_to_ast(v); + } + if let Some(v) = obj.get("s").or_else(|| obj.get("string")) { + return json_to_ast(v); + } + if let Some(v) = obj.get("b").or_else(|| obj.get("bool")) { + return json_to_ast(v); + } if let Some(map) = obj.get("map") { if let Some(mo) = map.as_object() { - let mut ents: Vec<(String, nyash_rust::ASTNode)> = Vec::with_capacity(mo.len()); - for (k, vv) in mo { ents.push((k.clone(), json_to_ast(vv)?)); } - return Ok(A::MapLiteral { entries: ents, span: Span::unknown() }); - } else { return Err("map must be an object".into()); } + let mut ents: Vec<(String, nyash_rust::ASTNode)> = + Vec::with_capacity(mo.len()); + for (k, vv) in mo { + ents.push((k.clone(), json_to_ast(vv)?)); + } + return Ok(A::MapLiteral { + entries: ents, + span: Span::unknown(), + }); + } else { + return Err("map must be an object".into()); + } } if let Some(arr) = obj.get("array") { if let Some(va) = arr.as_array() { let mut out = Vec::with_capacity(va.len()); - for x in va { out.push(json_to_ast(x)?); } - return Ok(A::ArrayLiteral { elements: out, span: Span::unknown() }); - } else { return Err("array must be an array".into()); } + for x in va { + out.push(json_to_ast(x)?); + } + return Ok(A::ArrayLiteral { + elements: out, + span: Span::unknown(), + }); + } else { + return Err("array must be an array".into()); + } } if let Some(name) = obj.get("var").and_then(|v| v.as_str()) { - return Ok(A::Variable { name: name.to_string(), span: Span::unknown() }); + return Ok(A::Variable { + name: name.to_string(), + span: Span::unknown(), + }); } if let Some(name) = obj.get("call").and_then(|v| v.as_str()) { let mut args: Vec = Vec::new(); if let Some(va) = obj.get("args").and_then(|v| v.as_array()) { - for x in va { args.push(json_to_ast(x)?); } + for x in va { + args.push(json_to_ast(x)?); + } } - return Ok(A::FunctionCall { name: name.to_string(), arguments: args, span: Span::unknown() }); + return Ok(A::FunctionCall { + name: name.to_string(), + arguments: args, + span: Span::unknown(), + }); } if let Some(method) = obj.get("method").and_then(|v| v.as_str()) { - let objv = obj.get("object").ok_or_else(|| "method requires 'object'".to_string())?; + let objv = obj + .get("object") + .ok_or_else(|| "method requires 'object'".to_string())?; let object = json_to_ast(objv)?; let mut args: Vec = Vec::new(); if let Some(va) = obj.get("args").and_then(|v| v.as_array()) { - for x in va { args.push(json_to_ast(x)?); } + for x in va { + args.push(json_to_ast(x)?); + } } - return Ok(A::MethodCall { object: Box::new(object), method: method.to_string(), arguments: args, span: Span::unknown() }); + return Ok(A::MethodCall { + object: Box::new(object), + method: method.to_string(), + arguments: args, + span: Span::unknown(), + }); } if let Some(bx) = obj.get("box").and_then(|v| v.as_str()) { let mut args: Vec = Vec::new(); if let Some(va) = obj.get("args").and_then(|v| v.as_array()) { - for x in va { args.push(json_to_ast(x)?); } + for x in va { + args.push(json_to_ast(x)?); + } } - let type_args: Vec = obj.get("type_args").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()).unwrap_or_default(); + let type_args: Vec = obj + .get("type_args") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|x| x.as_str().map(|s| s.to_string())) + .collect() + }) + .unwrap_or_default(); let ctor = obj.get("ctor").and_then(|v| v.as_str()).unwrap_or("new"); if ctor == "new" { - return Ok(A::New { class: bx.to_string(), arguments: args, type_arguments: type_args, span: Span::unknown() }); + return Ok(A::New { + class: bx.to_string(), + arguments: args, + type_arguments: type_args, + span: Span::unknown(), + }); } else if ctor == "birth" { - return Ok(A::MethodCall { object: Box::new(A::Variable { name: bx.to_string(), span: Span::unknown() }), method: "birth".into(), arguments: args, span: Span::unknown() }); + return Ok(A::MethodCall { + object: Box::new(A::Variable { + name: bx.to_string(), + span: Span::unknown(), + }), + method: "birth".into(), + arguments: args, + span: Span::unknown(), + }); } else { - return Err(format!("unknown ctor '{}', expected 'new' or 'birth'", ctor)); + return Err(format!( + "unknown ctor '{}', expected 'new' or 'birth'", + ctor + )); } } Err("unknown object mapping for AST".into()) @@ -148,38 +258,90 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { match v { serde_json::Value::Array(arr) => { let mut out: Vec = Vec::new(); - for a in arr { match json_to_ast(a) { Ok(n) => out.push(n), Err(e) => { json_err(&format!("args element error: {}", e)); return None; } } } - Some(TestArgSpec { args: out, instance: None }) + for a in arr { + match json_to_ast(a) { + Ok(n) => out.push(n), + Err(e) => { + json_err(&format!("args element error: {}", e)); + return None; + } + } + } + Some(TestArgSpec { + args: out, + instance: None, + }) } serde_json::Value::Object(obj) => { let mut spec = TestArgSpec::default(); if let Some(a) = obj.get("args").and_then(|v| v.as_array()) { let mut out: Vec = Vec::new(); - for x in a { match json_to_ast(x) { Ok(n) => out.push(n), Err(e) => { json_err(&format!("args element error: {}", e)); return None; } } } + for x in a { + match json_to_ast(x) { + Ok(n) => out.push(n), + Err(e) => { + json_err(&format!("args element error: {}", e)); + return None; + } + } + } spec.args = out; } if let Some(inst) = obj.get("instance").and_then(|v| v.as_object()) { - let ctor = inst.get("ctor").and_then(|v| v.as_str()).unwrap_or("new").to_string(); - let type_args: Vec = inst.get("type_args").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()).unwrap_or_default(); + let ctor = inst + .get("ctor") + .and_then(|v| v.as_str()) + .unwrap_or("new") + .to_string(); + let type_args: Vec = inst + .get("type_args") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|x| x.as_str().map(|s| s.to_string())) + .collect() + }) + .unwrap_or_default(); let mut args: Vec = Vec::new(); if let Some(va) = inst.get("args").and_then(|v| v.as_array()) { - for x in va { match json_to_ast(x) { Ok(n) => args.push(n), Err(e) => { json_err(&format!("instance.args element error: {}", e)); return None; } } } + for x in va { + match json_to_ast(x) { + Ok(n) => args.push(n), + Err(e) => { + json_err(&format!("instance.args element error: {}", e)); + return None; + } + } + } } - spec.instance = Some(InstanceSpec { ctor, args, type_args }); + spec.instance = Some(InstanceSpec { + ctor, + args, + type_args, + }); } Some(spec) } - _ => { json_err("test value must be array or object"); None } + _ => { + json_err("test value must be array or object"); + None + } } } let args_map: Option> = (|| { if let Ok(s) = std::env::var("NYASH_TEST_ARGS_JSON") { - if s.trim().is_empty() { return None; } + if s.trim().is_empty() { + return None; + } if let Ok(v) = serde_json::from_str::(&s) { let mut map = std::collections::HashMap::new(); if let Some(obj) = v.as_object() { - for (k, vv) in obj { if let Some(spec) = parse_test_arg_spec(vv) { map.insert(k.clone(), spec); } } + for (k, vv) in obj { + if let Some(spec) = parse_test_arg_spec(vv) { + map.insert(k.clone(), spec); + } + } return Some(map); } } @@ -190,21 +352,48 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { for st in statements { match st { nyash_rust::ASTNode::FunctionDeclaration { name, params, .. } => { - if name == "main" { has_main_fn = true; _main_params_len = params.len(); } + if name == "main" { + has_main_fn = true; + _main_params_len = params.len(); + } if name.starts_with("test_") { let label = name.clone(); // select args: JSON map > defaults > skip let mut maybe_args: Option> = None; - if let Some(m) = &args_map { if let Some(v) = m.get(&label) { maybe_args = Some(v.args.clone()); } } - let args = if let Some(a) = maybe_args { a } - else if !params.is_empty() && std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") { - let mut a: Vec = Vec::new(); for _ in params { a.push(nyash_rust::ASTNode::Literal{ value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } a - } else if params.is_empty() { Vec::new() } - else { - eprintln!("[macro][test][args] missing args for {} (need {}), skipping (set NYASH_TEST_ARGS_DEFAULTS=1 for zero defaults)", label, params.len()); - continue - }; - tests.push(TestPlan { label, setup: None, call: nyash_rust::ASTNode::FunctionCall { name: name.clone(), arguments: args, span: nyash_rust::ast::Span::unknown() } }); + if let Some(m) = &args_map { + if let Some(v) = m.get(&label) { + maybe_args = Some(v.args.clone()); + } + } + let args = if let Some(a) = maybe_args { + a + } else if !params.is_empty() + && std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() + == Some("1") + { + let mut a: Vec = Vec::new(); + for _ in params { + a.push(nyash_rust::ASTNode::Literal { + value: nyash_rust::ast::LiteralValue::Integer(0), + span: nyash_rust::ast::Span::unknown(), + }); + } + a + } else if params.is_empty() { + Vec::new() + } else { + eprintln!("[macro][test][args] missing args for {} (need {}), skipping (set NYASH_TEST_ARGS_DEFAULTS=1 for zero defaults)", label, params.len()); + continue; + }; + tests.push(TestPlan { + label, + setup: None, + call: nyash_rust::ASTNode::FunctionCall { + name: name.clone(), + arguments: args, + span: nyash_rust::ast::Span::unknown(), + }, + }); } } _ => {} @@ -214,45 +403,101 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { // Collect Box tests: static and instance (no-arg only for instance) if let nyash_rust::ASTNode::Program { statements, .. } = ast { for st in statements { - if let nyash_rust::ASTNode::BoxDeclaration { name: box_name, methods, .. } = st { + if let nyash_rust::ASTNode::BoxDeclaration { + name: box_name, + methods, + .. + } = st + { for (mname, mnode) in methods { - if !mname.starts_with("test_") { continue; } - if let nyash_rust::ASTNode::FunctionDeclaration { is_static, params, .. } = mnode { + if !mname.starts_with("test_") { + continue; + } + if let nyash_rust::ASTNode::FunctionDeclaration { + is_static, params, .. + } = mnode + { if *is_static { // Static: BoxName.test_*() let mut args: Vec = Vec::new(); - if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { args = v.args.clone(); } } + if let Some(m) = &args_map { + if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { + args = v.args.clone(); + } + } if args.is_empty() && !params.is_empty() { - if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") { for _ in params { args.push(nyash_rust::ASTNode::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } } - else { + if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() + == Some("1") + { + for _ in params { + args.push(nyash_rust::ASTNode::Literal { + value: nyash_rust::ast::LiteralValue::Integer(0), + span: nyash_rust::ast::Span::unknown(), + }); + } + } else { eprintln!("[macro][test][args] missing args for {}.{} (need {}), skipping", box_name, mname, params.len()); continue; } } let call = nyash_rust::ASTNode::MethodCall { - object: Box::new(nyash_rust::ASTNode::Variable { name: box_name.clone(), span: nyash_rust::ast::Span::unknown() }), + object: Box::new(nyash_rust::ASTNode::Variable { + name: box_name.clone(), + span: nyash_rust::ast::Span::unknown(), + }), method: mname.clone(), arguments: args, span: nyash_rust::ast::Span::unknown(), }; - tests.push(TestPlan { label: format!("{}.{}", box_name, mname), setup: None, call }); + tests.push(TestPlan { + label: format!("{}.{}", box_name, mname), + setup: None, + call, + }); } else { // Instance: try new BoxName() then .test_*() let inst_var = format!("__t_{}", box_name.to_lowercase()); // Instance override via JSON let mut inst_ctor: Option = None; - if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { inst_ctor = v.instance.clone(); } } + if let Some(m) = &args_map { + if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { + inst_ctor = v.instance.clone(); + } + } let inst_init: nyash_rust::ASTNode = if let Some(spec) = inst_ctor { match spec.ctor.as_str() { - "new" => nyash_rust::ASTNode::New { class: box_name.clone(), arguments: spec.args, type_arguments: spec.type_args, span: nyash_rust::ast::Span::unknown() }, - "birth" => nyash_rust::ASTNode::MethodCall { object: Box::new(nyash_rust::ASTNode::Variable { name: box_name.clone(), span: nyash_rust::ast::Span::unknown() }), method: "birth".into(), arguments: spec.args, span: nyash_rust::ast::Span::unknown() }, + "new" => nyash_rust::ASTNode::New { + class: box_name.clone(), + arguments: spec.args, + type_arguments: spec.type_args, + span: nyash_rust::ast::Span::unknown(), + }, + "birth" => nyash_rust::ASTNode::MethodCall { + object: Box::new(nyash_rust::ASTNode::Variable { + name: box_name.clone(), + span: nyash_rust::ast::Span::unknown(), + }), + method: "birth".into(), + arguments: spec.args, + span: nyash_rust::ast::Span::unknown(), + }, other => { eprintln!("[macro][test][args] unknown ctor '{}' for {}.{}, using new()", other, box_name, mname); - nyash_rust::ASTNode::New { class: box_name.clone(), arguments: vec![], type_arguments: vec![], span: nyash_rust::ast::Span::unknown() } + nyash_rust::ASTNode::New { + class: box_name.clone(), + arguments: vec![], + type_arguments: vec![], + span: nyash_rust::ast::Span::unknown(), + } } } } else { - nyash_rust::ASTNode::New { class: box_name.clone(), arguments: vec![], type_arguments: vec![], span: nyash_rust::ast::Span::unknown() } + nyash_rust::ASTNode::New { + class: box_name.clone(), + arguments: vec![], + type_arguments: vec![], + span: nyash_rust::ast::Span::unknown(), + } }; let setup = nyash_rust::ASTNode::Local { variables: vec![inst_var.clone()], @@ -260,21 +505,40 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { span: nyash_rust::ast::Span::unknown(), }; let mut args: Vec = Vec::new(); - if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { args = v.args.clone(); } } + if let Some(m) = &args_map { + if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { + args = v.args.clone(); + } + } if args.is_empty() && !params.is_empty() { - if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") { for _ in params { args.push(nyash_rust::ASTNode::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } } - else { + if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() + == Some("1") + { + for _ in params { + args.push(nyash_rust::ASTNode::Literal { + value: nyash_rust::ast::LiteralValue::Integer(0), + span: nyash_rust::ast::Span::unknown(), + }); + } + } else { eprintln!("[macro][test][args] missing args for {}.{} (need {}), skipping", box_name, mname, params.len()); continue; } } let call = nyash_rust::ASTNode::MethodCall { - object: Box::new(nyash_rust::ASTNode::Variable { name: inst_var.clone(), span: nyash_rust::ast::Span::unknown() }), + object: Box::new(nyash_rust::ASTNode::Variable { + name: inst_var.clone(), + span: nyash_rust::ast::Span::unknown(), + }), method: mname.clone(), arguments: args, span: nyash_rust::ast::Span::unknown(), }; - tests.push(TestPlan { label: format!("{}.{}", box_name, mname), setup: Some(setup), call }); + tests.push(TestPlan { + label: format!("{}.{}", box_name, mname), + setup: Some(setup), + call, + }); } } } @@ -288,7 +552,9 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { } } if tests.is_empty() { - if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro][test] no tests found (functions starting with 'test_')"); } + if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { + eprintln!("[macro][test] no tests found (functions starting with 'test_')"); + } return ast.clone(); } // Decide entry policy when main exists @@ -297,34 +563,128 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { let ret_policy = std::env::var("NYASH_TEST_RETURN").ok(); // Some("tests"|"original") default tests // Build harness: top-level function main(args) { ... } - use nyash_rust::ast::{ASTNode as A, Span, LiteralValue, BinaryOperator}; + use nyash_rust::ast::{ASTNode as A, BinaryOperator, LiteralValue, Span}; let mut body: Vec = Vec::new(); // locals: pass=0, fail=0 - body.push(A::Local { variables: vec!["pass".into(), "fail".into()], initial_values: vec![Some(Box::new(A::Literal{ value: LiteralValue::Integer(0), span: Span::unknown()})), Some(Box::new(A::Literal{ value: LiteralValue::Integer(0), span: Span::unknown()}))], span: Span::unknown() }); + body.push(A::Local { + variables: vec!["pass".into(), "fail".into()], + initial_values: vec![ + Some(Box::new(A::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + })), + Some(Box::new(A::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + })), + ], + span: Span::unknown(), + }); for tp in &tests { // optional setup - if let Some(set) = tp.setup.clone() { body.push(set); } + if let Some(set) = tp.setup.clone() { + body.push(set); + } // local r = CALL - body.push(A::Local { variables: vec!["r".into()], initial_values: vec![Some(Box::new(tp.call.clone()))], span: Span::unknown() }); + body.push(A::Local { + variables: vec!["r".into()], + initial_values: vec![Some(Box::new(tp.call.clone()))], + span: Span::unknown(), + }); // if r { print("PASS t"); pass = pass + 1 } else { print("FAIL t"); fail = fail + 1 } - let pass_msg = A::Literal { value: LiteralValue::String(format!("PASS {}", tp.label)), span: Span::unknown() }; - let fail_msg = A::Literal { value: LiteralValue::String(format!("FAIL {}", tp.label)), span: Span::unknown() }; + let pass_msg = A::Literal { + value: LiteralValue::String(format!("PASS {}", tp.label)), + span: Span::unknown(), + }; + let fail_msg = A::Literal { + value: LiteralValue::String(format!("FAIL {}", tp.label)), + span: Span::unknown(), + }; let then_body = vec![ - A::Print { expression: Box::new(pass_msg), span: Span::unknown() }, - A::Assignment { target: Box::new(A::Variable{ name: "pass".into(), span: Span::unknown() }), value: Box::new(A::BinaryOp{ operator: BinaryOperator::Add, left: Box::new(A::Variable{ name: "pass".into(), span: Span::unknown() }), right: Box::new(A::Literal{ value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() }, + A::Print { + expression: Box::new(pass_msg), + span: Span::unknown(), + }, + A::Assignment { + target: Box::new(A::Variable { + name: "pass".into(), + span: Span::unknown(), + }), + value: Box::new(A::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(A::Variable { + name: "pass".into(), + span: Span::unknown(), + }), + right: Box::new(A::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, ]; let else_body = vec![ - A::Print { expression: Box::new(fail_msg), span: Span::unknown() }, - A::Assignment { target: Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() }), value: Box::new(A::BinaryOp{ operator: BinaryOperator::Add, left: Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() }), right: Box::new(A::Literal{ value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() }, + A::Print { + expression: Box::new(fail_msg), + span: Span::unknown(), + }, + A::Assignment { + target: Box::new(A::Variable { + name: "fail".into(), + span: Span::unknown(), + }), + value: Box::new(A::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(A::Variable { + name: "fail".into(), + span: Span::unknown(), + }), + right: Box::new(A::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, ]; - body.push(A::If { condition: Box::new(A::Variable { name: "r".into(), span: Span::unknown() }), then_body, else_body: Some(else_body), span: Span::unknown() }); + body.push(A::If { + condition: Box::new(A::Variable { + name: "r".into(), + span: Span::unknown(), + }), + then_body, + else_body: Some(else_body), + span: Span::unknown(), + }); } // print summary and return fail - body.push(A::Print { expression: Box::new(A::Literal{ value: LiteralValue::String(format!("Summary: {} tests", tests.len())), span: Span::unknown() }), span: Span::unknown() }); - body.push(A::Return { value: Some(Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() })), span: Span::unknown() }); + body.push(A::Print { + expression: Box::new(A::Literal { + value: LiteralValue::String(format!("Summary: {} tests", tests.len())), + span: Span::unknown(), + }), + span: Span::unknown(), + }); + body.push(A::Return { + value: Some(Box::new(A::Variable { + name: "fail".into(), + span: Span::unknown(), + })), + span: Span::unknown(), + }); // Build harness main body as above let make_harness_main = |body: Vec| -> A { - A::FunctionDeclaration { name: "main".into(), params: vec!["args".into()], body, is_static: false, is_override: false, span: Span::unknown() } + A::FunctionDeclaration { + name: "main".into(), + params: vec!["args".into()], + body, + is_static: false, + is_override: false, + span: Span::unknown(), + } }; // Transform AST according to policy @@ -334,27 +694,64 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { let mut orig_call_fn: Option = None; for st in statements { match st { - A::FunctionDeclaration { name, params, body: orig_body, is_static, is_override, span: fspan } if name == "main" => { + A::FunctionDeclaration { + name, + params, + body: orig_body, + is_static, + is_override, + span: fspan, + } if name == "main" => { if has_main_fn && (force || entry_mode.is_some()) { // rename original main let new_name = "__ny_orig_main".to_string(); - out_stmts.push(A::FunctionDeclaration { name: new_name.clone(), params: params.clone(), body: orig_body.clone(), is_static, is_override, span: fspan }); + out_stmts.push(A::FunctionDeclaration { + name: new_name.clone(), + params: params.clone(), + body: orig_body.clone(), + is_static, + is_override, + span: fspan, + }); _renamed_main = true; if entry_mode.as_deref() == Some("wrap") { - let args_exprs = if params.len() >= 1 { vec![A::Variable { name: "args".into(), span: nyash_rust::ast::Span::unknown() }] } else { vec![] }; - orig_call_fn = Some(A::FunctionCall { name: new_name, arguments: args_exprs, span: nyash_rust::ast::Span::unknown() }); + let args_exprs = if params.len() >= 1 { + vec![A::Variable { + name: "args".into(), + span: nyash_rust::ast::Span::unknown(), + }] + } else { + vec![] + }; + orig_call_fn = Some(A::FunctionCall { + name: new_name, + arguments: args_exprs, + span: nyash_rust::ast::Span::unknown(), + }); } } else { // keep as-is (no injection) - out_stmts.push(A::FunctionDeclaration { name, params, body: orig_body, is_static, is_override, span: fspan }); + out_stmts.push(A::FunctionDeclaration { + name, + params, + body: orig_body, + is_static, + is_override, + span: fspan, + }); } } other => out_stmts.push(other), } } if has_main_fn && !(force || entry_mode.is_some()) { - if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro][test] existing main detected; skip harness (set --test-entry or NYASH_TEST_FORCE=1)"); } - return nyash_rust::ASTNode::Program { statements: out_stmts, span }; + if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { + eprintln!("[macro][test] existing main detected; skip harness (set --test-entry or NYASH_TEST_FORCE=1)"); + } + return nyash_rust::ASTNode::Program { + statements: out_stmts, + span, + }; } // Compose harness main now let mut body2 = body; @@ -362,9 +759,19 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { if let Some(call) = orig_call_fn.take() { if ret_policy.as_deref() == Some("original") { // local __ny_orig_ret = __ny_orig_main(args) - body2.push(A::Local { variables: vec!["__ny_orig_ret".into()], initial_values: vec![Some(Box::new(call))], span: nyash_rust::ast::Span::unknown() }); + body2.push(A::Local { + variables: vec!["__ny_orig_ret".into()], + initial_values: vec![Some(Box::new(call))], + span: nyash_rust::ast::Span::unknown(), + }); // return __ny_orig_ret - body2.push(A::Return { value: Some(Box::new(A::Variable { name: "__ny_orig_ret".into(), span: nyash_rust::ast::Span::unknown() })), span: nyash_rust::ast::Span::unknown() }); + body2.push(A::Return { + value: Some(Box::new(A::Variable { + name: "__ny_orig_ret".into(), + span: nyash_rust::ast::Span::unknown(), + })), + span: nyash_rust::ast::Span::unknown(), + }); } else { // default: tests policy; still call original but ignore result body2.push(call); @@ -373,7 +780,10 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode { } let harness_fn = make_harness_main(body2); out_stmts.push(harness_fn); - return nyash_rust::ASTNode::Program { statements: out_stmts, span }; + return nyash_rust::ASTNode::Program { + statements: out_stmts, + span, + }; } ast.clone() } diff --git a/src/macro/pattern.rs b/src/macro/pattern.rs index cfc9a454..350d5717 100644 --- a/src/macro/pattern.rs +++ b/src/macro/pattern.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use nyash_rust::ASTNode; +use std::collections::HashMap; /// Minimal pattern trait — MVP pub trait MacroPattern { @@ -10,35 +10,71 @@ pub trait MacroPattern { pub struct AstBuilder; impl AstBuilder { - pub fn new() -> Self { Self } + pub fn new() -> Self { + Self + } pub fn quote(&self, code: &str) -> ASTNode { // MVP: parse string into AST using existing parser match nyash_rust::parser::NyashParser::parse_from_string(code) { Ok(ast) => ast, - Err(_) => ASTNode::Program { statements: vec![], span: nyash_rust::ast::Span::unknown() }, + Err(_) => ASTNode::Program { + statements: vec![], + span: nyash_rust::ast::Span::unknown(), + }, } } pub fn unquote(&self, template: &ASTNode, _bindings: &HashMap) -> ASTNode { // Replace Variables named like "$name" with corresponding bound AST fn is_placeholder(name: &str) -> Option<&str> { - if name.starts_with('$') && name.len() > 1 { Some(&name[1..]) } else { None } + if name.starts_with('$') && name.len() > 1 { + Some(&name[1..]) + } else { + None + } } fn is_variadic(name: &str) -> Option<&str> { - if name.starts_with("$...") && name.len() > 4 { Some(&name[4..]) } else { None } + if name.starts_with("$...") && name.len() > 4 { + Some(&name[4..]) + } else { + None + } } fn subst(node: &ASTNode, binds: &HashMap) -> ASTNode { match node.clone() { ASTNode::Variable { name, .. } => { if let Some(k) = is_placeholder(&name) { - if let Some(v) = binds.get(k) { return v.clone(); } + if let Some(v) = binds.get(k) { + return v.clone(); + } } node.clone() } - ASTNode::BinaryOp { operator, left, right, span } => ASTNode::BinaryOp { - operator, left: Box::new(subst(&left, binds)), right: Box::new(subst(&right, binds)), span + ASTNode::BinaryOp { + operator, + left, + right, + span, + } => ASTNode::BinaryOp { + operator, + left: Box::new(subst(&left, binds)), + right: Box::new(subst(&right, binds)), + span, }, - ASTNode::UnaryOp { operator, operand, span } => ASTNode::UnaryOp { operator, operand: Box::new(subst(&operand, binds)), span }, - ASTNode::MethodCall { object, method, arguments, span } => { + ASTNode::UnaryOp { + operator, + operand, + span, + } => ASTNode::UnaryOp { + operator, + operand: Box::new(subst(&operand, binds)), + span, + }, + ASTNode::MethodCall { + object, + method, + arguments, + span, + } => { let mut out_args: Vec = Vec::new(); let mut i = 0usize; while i < arguments.len() { @@ -54,9 +90,18 @@ impl AstBuilder { out_args.push(subst(&arguments[i], binds)); i += 1; } - ASTNode::MethodCall { object: Box::new(subst(&object, binds)), method, arguments: out_args, span } + ASTNode::MethodCall { + object: Box::new(subst(&object, binds)), + method, + arguments: out_args, + span, + } } - ASTNode::FunctionCall { name, arguments, span } => { + ASTNode::FunctionCall { + name, + arguments, + span, + } => { let mut out_args: Vec = Vec::new(); let mut i = 0usize; while i < arguments.len() { @@ -72,7 +117,11 @@ impl AstBuilder { out_args.push(subst(&arguments[i], binds)); i += 1; } - ASTNode::FunctionCall { name, arguments: out_args, span } + ASTNode::FunctionCall { + name, + arguments: out_args, + span, + } } ASTNode::ArrayLiteral { elements, span } => { // Splice variadic placeholder inside arrays @@ -91,21 +140,55 @@ impl AstBuilder { out_elems.push(subst(&elements[i], binds)); i += 1; } - ASTNode::ArrayLiteral { elements: out_elems, span } + ASTNode::ArrayLiteral { + elements: out_elems, + span, + } } - ASTNode::MapLiteral { entries, span } => { - ASTNode::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, subst(&v, binds))).collect(), span } - } - ASTNode::FieldAccess { object, field, span } => ASTNode::FieldAccess { object: Box::new(subst(&object, binds)), field, span }, - ASTNode::Assignment { target, value, span } => ASTNode::Assignment { target: Box::new(subst(&target, binds)), value: Box::new(subst(&value, binds)), span }, - ASTNode::Return { value, span } => ASTNode::Return { value: value.as_ref().map(|v| Box::new(subst(v, binds))), span }, - ASTNode::If { condition, then_body, else_body, span } => ASTNode::If { + ASTNode::MapLiteral { entries, span } => ASTNode::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, subst(&v, binds))) + .collect(), + span, + }, + ASTNode::FieldAccess { + object, + field, + span, + } => ASTNode::FieldAccess { + object: Box::new(subst(&object, binds)), + field, + span, + }, + ASTNode::Assignment { + target, + value, + span, + } => ASTNode::Assignment { + target: Box::new(subst(&target, binds)), + value: Box::new(subst(&value, binds)), + span, + }, + ASTNode::Return { value, span } => ASTNode::Return { + value: value.as_ref().map(|v| Box::new(subst(v, binds))), + span, + }, + ASTNode::If { + condition, + then_body, + else_body, + span, + } => ASTNode::If { condition: Box::new(subst(&condition, binds)), then_body: then_body.into_iter().map(|n| subst(&n, binds)).collect(), else_body: else_body.map(|v| v.into_iter().map(|n| subst(&n, binds)).collect()), span, }, - ASTNode::Program { statements, span } => ASTNode::Program { statements: statements.into_iter().map(|n| subst(&n, binds)).collect(), span }, + ASTNode::Program { statements, span } => ASTNode::Program { + statements: statements.into_iter().map(|n| subst(&n, binds)).collect(), + span, + }, other => other, } } @@ -114,115 +197,326 @@ impl AstBuilder { } /// Simple template-based pattern that uses Variables named "$name" as bind points. -pub struct TemplatePattern { pub template: ASTNode } +pub struct TemplatePattern { + pub template: ASTNode, +} -impl TemplatePattern { pub fn new(template: ASTNode) -> Self { Self { template } } } +impl TemplatePattern { + pub fn new(template: ASTNode) -> Self { + Self { template } + } +} impl MacroPattern for TemplatePattern { fn match_ast(&self, node: &ASTNode) -> Option> { fn is_placeholder(name: &str) -> Option<&str> { - if name.starts_with('$') && name.len() > 1 { Some(&name[1..]) } else { None } + if name.starts_with('$') && name.len() > 1 { + Some(&name[1..]) + } else { + None + } } fn is_variadic(name: &str) -> Option<&str> { - if name.starts_with("$...") && name.len() > 4 { Some(&name[4..]) } else { None } + if name.starts_with("$...") && name.len() > 4 { + Some(&name[4..]) + } else { + None + } } fn go(tpl: &ASTNode, tgt: &ASTNode, out: &mut HashMap) -> bool { match (tpl, tgt) { (ASTNode::Variable { name, .. }, v) => { - if let Some(k) = is_placeholder(name) { out.insert(k.to_string(), v.clone()); true } else { tpl == tgt } + if let Some(k) = is_placeholder(name) { + out.insert(k.to_string(), v.clone()); + true + } else { + tpl == tgt + } } - (ASTNode::Literal { .. }, _) | (ASTNode::Me { .. }, _) | (ASTNode::This { .. }, _) => tpl == tgt, - (ASTNode::BinaryOp { operator: op1, left: l1, right: r1, .. }, ASTNode::BinaryOp { operator: op2, left: l2, right: r2, .. }) => { - op1 == op2 && go(l1, l2, out) && go(r1, r2, out) - } - (ASTNode::UnaryOp { operator: o1, operand: a1, .. }, ASTNode::UnaryOp { operator: o2, operand: a2, .. }) => { - o1 == o2 && go(a1, a2, out) - } - (ASTNode::MethodCall { object: o1, method: m1, arguments: a1, .. }, ASTNode::MethodCall { object: o2, method: m2, arguments: a2, .. }) => { - if m1 != m2 { return false; } - if !go(o1, o2, out) { return false; } + (ASTNode::Literal { .. }, _) + | (ASTNode::Me { .. }, _) + | (ASTNode::This { .. }, _) => tpl == tgt, + ( + ASTNode::BinaryOp { + operator: op1, + left: l1, + right: r1, + .. + }, + ASTNode::BinaryOp { + operator: op2, + left: l2, + right: r2, + .. + }, + ) => op1 == op2 && go(l1, l2, out) && go(r1, r2, out), + ( + ASTNode::UnaryOp { + operator: o1, + operand: a1, + .. + }, + ASTNode::UnaryOp { + operator: o2, + operand: a2, + .. + }, + ) => o1 == o2 && go(a1, a2, out), + ( + ASTNode::MethodCall { + object: o1, + method: m1, + arguments: a1, + .. + }, + ASTNode::MethodCall { + object: o2, + method: m2, + arguments: a2, + .. + }, + ) => { + if m1 != m2 { + return false; + } + if !go(o1, o2, out) { + return false; + } // Support variadic anywhere in a1 let mut varpos: Option<(usize, String)> = None; for (i, arg) in a1.iter().enumerate() { - if let ASTNode::Variable { name, .. } = arg { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } } + if let ASTNode::Variable { name, .. } = arg { + if let Some(vn) = is_variadic(name) { + varpos = Some((i, vn.to_string())); + break; + } + } } if let Some((k, vn)) = varpos { let suffix_len = a1.len() - k - 1; - if a2.len() < k + suffix_len { return false; } - for (x, y) in a1[..k].iter().zip(a2.iter()) { if !go(x, y, out) { return false; } } - for (i, x) in a1[a1.len()-suffix_len..].iter().enumerate() { - let y = &a2[a2.len()-suffix_len + i]; - if !go(x, y, out) { return false; } + if a2.len() < k + suffix_len { + return false; } - let tail: Vec = a2[k..a2.len()-suffix_len].to_vec(); - out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() }); + for (x, y) in a1[..k].iter().zip(a2.iter()) { + if !go(x, y, out) { + return false; + } + } + for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() { + let y = &a2[a2.len() - suffix_len + i]; + if !go(x, y, out) { + return false; + } + } + let tail: Vec = a2[k..a2.len() - suffix_len].to_vec(); + out.insert( + vn, + ASTNode::Program { + statements: tail, + span: nyash_rust::ast::Span::unknown(), + }, + ); return true; } - if a1.len() != a2.len() { return false; } - for (x, y) in a1.iter().zip(a2.iter()) { if !go(x, y, out) { return false; } } + if a1.len() != a2.len() { + return false; + } + for (x, y) in a1.iter().zip(a2.iter()) { + if !go(x, y, out) { + return false; + } + } true } - (ASTNode::FunctionCall { name: n1, arguments: a1, .. }, ASTNode::FunctionCall { name: n2, arguments: a2, .. }) => { - if n1 != n2 { return false; } + ( + ASTNode::FunctionCall { + name: n1, + arguments: a1, + .. + }, + ASTNode::FunctionCall { + name: n2, + arguments: a2, + .. + }, + ) => { + if n1 != n2 { + return false; + } let mut varpos: Option<(usize, String)> = None; for (i, arg) in a1.iter().enumerate() { - if let ASTNode::Variable { name, .. } = arg { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } } + if let ASTNode::Variable { name, .. } = arg { + if let Some(vn) = is_variadic(name) { + varpos = Some((i, vn.to_string())); + break; + } + } } if let Some((k, vn)) = varpos { let suffix_len = a1.len() - k - 1; - if a2.len() < k + suffix_len { return false; } - for (x, y) in a1[..k].iter().zip(a2.iter()) { if !go(x, y, out) { return false; } } - for (i, x) in a1[a1.len()-suffix_len..].iter().enumerate() { - let y = &a2[a2.len()-suffix_len + i]; - if !go(x, y, out) { return false; } + if a2.len() < k + suffix_len { + return false; } - let tail: Vec = a2[k..a2.len()-suffix_len].to_vec(); - out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() }); + for (x, y) in a1[..k].iter().zip(a2.iter()) { + if !go(x, y, out) { + return false; + } + } + for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() { + let y = &a2[a2.len() - suffix_len + i]; + if !go(x, y, out) { + return false; + } + } + let tail: Vec = a2[k..a2.len() - suffix_len].to_vec(); + out.insert( + vn, + ASTNode::Program { + statements: tail, + span: nyash_rust::ast::Span::unknown(), + }, + ); return true; } - if a1.len() != a2.len() { return false; } - for (x, y) in a1.iter().zip(a2.iter()) { if !go(x, y, out) { return false; } } + if a1.len() != a2.len() { + return false; + } + for (x, y) in a1.iter().zip(a2.iter()) { + if !go(x, y, out) { + return false; + } + } true } - (ASTNode::ArrayLiteral { elements: e1, .. }, ASTNode::ArrayLiteral { elements: e2, .. }) => { + ( + ASTNode::ArrayLiteral { elements: e1, .. }, + ASTNode::ArrayLiteral { elements: e2, .. }, + ) => { let mut varpos: Option<(usize, String)> = None; for (i, el) in e1.iter().enumerate() { - if let ASTNode::Variable { name, .. } = el { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } } + if let ASTNode::Variable { name, .. } = el { + if let Some(vn) = is_variadic(name) { + varpos = Some((i, vn.to_string())); + break; + } + } } if let Some((k, vn)) = varpos { let suffix_len = e1.len() - k - 1; - if e2.len() < k + suffix_len { return false; } - for (x, y) in e1[..k].iter().zip(e2.iter()) { if !go(x, y, out) { return false; } } - for (i, x) in e1[e1.len()-suffix_len..].iter().enumerate() { - let y = &e2[e2.len()-suffix_len + i]; - if !go(x, y, out) { return false; } + if e2.len() < k + suffix_len { + return false; } - let tail: Vec = e2[k..e2.len()-suffix_len].to_vec(); - out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() }); + for (x, y) in e1[..k].iter().zip(e2.iter()) { + if !go(x, y, out) { + return false; + } + } + for (i, x) in e1[e1.len() - suffix_len..].iter().enumerate() { + let y = &e2[e2.len() - suffix_len + i]; + if !go(x, y, out) { + return false; + } + } + let tail: Vec = e2[k..e2.len() - suffix_len].to_vec(); + out.insert( + vn, + ASTNode::Program { + statements: tail, + span: nyash_rust::ast::Span::unknown(), + }, + ); return true; } - if e1.len() != e2.len() { return false; } - for (x, y) in e1.iter().zip(e2.iter()) { if !go(x, y, out) { return false; } } - true - } - (ASTNode::MapLiteral { entries: m1, .. }, ASTNode::MapLiteral { entries: m2, .. }) => { - if m1.len() != m2.len() { return false; } - for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) { - if k1 != k2 { return false; } - if !go(v1, v2, out) { return false; } + if e1.len() != e2.len() { + return false; + } + for (x, y) in e1.iter().zip(e2.iter()) { + if !go(x, y, out) { + return false; + } } true } - (ASTNode::FieldAccess { object: o1, field: f1, .. }, ASTNode::FieldAccess { object: o2, field: f2, .. }) => f1 == f2 && go(o1, o2, out), - (ASTNode::Assignment { target: t1, value: v1, .. }, ASTNode::Assignment { target: t2, value: v2, .. }) => go(t1, t2, out) && go(v1, v2, out), - (ASTNode::Return { value: v1, .. }, ASTNode::Return { value: v2, .. }) => match (v1, v2) { (Some(a), Some(b)) => go(a, b, out), (None, None) => true, _ => false }, - (ASTNode::If { condition: c1, then_body: t1, else_body: e1, .. }, ASTNode::If { condition: c2, then_body: t2, else_body: e2, .. }) => { - if !go(c1, c2, out) || t1.len() != t2.len() { return false; } - for (x, y) in t1.iter().zip(t2.iter()) { if !go(x, y, out) { return false; } } + ( + ASTNode::MapLiteral { entries: m1, .. }, + ASTNode::MapLiteral { entries: m2, .. }, + ) => { + if m1.len() != m2.len() { + return false; + } + for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) { + if k1 != k2 { + return false; + } + if !go(v1, v2, out) { + return false; + } + } + true + } + ( + ASTNode::FieldAccess { + object: o1, + field: f1, + .. + }, + ASTNode::FieldAccess { + object: o2, + field: f2, + .. + }, + ) => f1 == f2 && go(o1, o2, out), + ( + ASTNode::Assignment { + target: t1, + value: v1, + .. + }, + ASTNode::Assignment { + target: t2, + value: v2, + .. + }, + ) => go(t1, t2, out) && go(v1, v2, out), + (ASTNode::Return { value: v1, .. }, ASTNode::Return { value: v2, .. }) => { + match (v1, v2) { + (Some(a), Some(b)) => go(a, b, out), + (None, None) => true, + _ => false, + } + } + ( + ASTNode::If { + condition: c1, + then_body: t1, + else_body: e1, + .. + }, + ASTNode::If { + condition: c2, + then_body: t2, + else_body: e2, + .. + }, + ) => { + if !go(c1, c2, out) || t1.len() != t2.len() { + return false; + } + for (x, y) in t1.iter().zip(t2.iter()) { + if !go(x, y, out) { + return false; + } + } match (e1, e2) { (Some(a), Some(b)) => { - if a.len() != b.len() { return false; } - for (x, y) in a.iter().zip(b.iter()) { if !go(x, y, out) { return false; } } + if a.len() != b.len() { + return false; + } + for (x, y) in a.iter().zip(b.iter()) { + if !go(x, y, out) { + return false; + } + } true } (None, None) => true, @@ -233,19 +527,31 @@ impl MacroPattern for TemplatePattern { } } let mut out = HashMap::new(); - if go(&self.template, node, &mut out) { Some(out) } else { None } + if go(&self.template, node, &mut out) { + Some(out) + } else { + None + } } } /// ORパターン: いずれかのテンプレートにマッチすれば成功 -pub struct OrPattern { pub alts: Vec } +pub struct OrPattern { + pub alts: Vec, +} -impl OrPattern { pub fn new(alts: Vec) -> Self { Self { alts } } } +impl OrPattern { + pub fn new(alts: Vec) -> Self { + Self { alts } + } +} impl MacroPattern for OrPattern { fn match_ast(&self, node: &ASTNode) -> Option> { for tp in &self.alts { - if let Some(b) = tp.match_ast(node) { return Some(b); } + if let Some(b) = tp.match_ast(node) { + return Some(b); + } } None } diff --git a/src/main.rs b/src/main.rs index 3f65af59..af51af6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,11 +11,7 @@ use nyash_rust::runner::NyashRunner; fn main() { // Optional: enable backtrace on stack overflow for deep debug runs. // Guarded by env to keep default behavior unchanged. - if std::env::var("NYASH_DEBUG_STACK_OVERFLOW") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_DEBUG_STACK_OVERFLOW").ok().as_deref() == Some("1") { unsafe { let _ = backtrace_on_stack_overflow::enable(); } diff --git a/src/mir/aot_plan_import.rs b/src/mir/aot_plan_import.rs index 8178ec9a..4a86bfbf 100644 --- a/src/mir/aot_plan_import.rs +++ b/src/mir/aot_plan_import.rs @@ -1,10 +1,10 @@ //! AOT-Plan v1 → MIR13 importer (Phase 15.1) //! Feature-gated behind `aot-plan-import`. +use crate::mir::function_emission as femit; use crate::mir::{ BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirModule, MirType, }; -use crate::mir::function_emission as femit; #[derive(Debug, serde::Deserialize)] struct PlanV1 { @@ -98,7 +98,10 @@ pub fn import_from_str(plan_json: &str) -> Result { // Fall back to direct const emission inline here to avoid adding a new helper unnecessarily. let d = mf.next_value_id(); if let Some(block) = mf.get_block_mut(bb) { - block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: ConstValue::Float(fl) }); + block.add_instruction(crate::mir::MirInstruction::Const { + dst: d, + value: ConstValue::Float(fl), + }); } d } @@ -107,7 +110,10 @@ pub fn import_from_str(plan_json: &str) -> Result { // Null/Void are not expected in PlanBody::ConstReturn; still handle gracefully. let d = mf.next_value_id(); if let Some(block) = mf.get_block_mut(bb) { - block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: other }); + block.add_instruction(crate::mir::MirInstruction::Const { + dst: d, + value: other, + }); } d } diff --git a/src/mir/basic_block.rs b/src/mir/basic_block.rs index 0142183e..5f1ad29b 100644 --- a/src/mir/basic_block.rs +++ b/src/mir/basic_block.rs @@ -240,7 +240,10 @@ impl BasicBlock { } } } - Err(format!("PHI instruction with dst {:?} not found in block {:?}", phi_dst, self.id)) + Err(format!( + "PHI instruction with dst {:?} not found in block {:?}", + phi_dst, self.id + )) } /// Replace terminator instruction diff --git a/src/mir/builder.rs b/src/mir/builder.rs index ca7fba24..7d9e2d98 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -9,50 +9,50 @@ use super::{ BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator, }; -use crate::mir::region::function_slot_registry::FunctionSlotRegistry; -use crate::mir::region::RegionId; use crate::ast::{ASTNode, LiteralValue}; use crate::mir::builder::builder_calls::CallTarget; +use crate::mir::region::function_slot_registry::FunctionSlotRegistry; +use crate::mir::region::RegionId; use std::collections::HashMap; use std::collections::HashSet; -mod calls; // Call system modules (refactored from builder_calls) mod builder_calls; mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities +mod calls; // Call system modules (refactored from builder_calls) mod context; // BoxCompilationContext - 箱理論による静的Boxコンパイル時のコンテキスト分離 -mod method_call_handlers; // Method call handler separation (Phase 3) mod decls; // declarations lowering split mod exprs; // expression lowering split -mod exprs_call; // call(expr) -// include lowering removed (using is handled in runner) +mod exprs_call; +mod method_call_handlers; // Method call handler separation (Phase 3) // call(expr) + // include lowering removed (using is handled in runner) +mod control_flow; // thin wrappers to centralize control-flow entrypoints mod exprs_lambda; // lambda lowering mod exprs_peek; // peek expression mod exprs_qmark; // ?-propagate mod fields; // field access/assignment lowering split +mod if_form; +mod lifecycle; pub(crate) mod loops; mod ops; mod phi; -mod phi_merge; // Phase 25.1q: Unified PHI merge helper -mod if_form; -mod control_flow; // thin wrappers to centralize control-flow entrypoints -mod lifecycle; // prepare/lower_root/finalize split -// legacy large-match remains inline for now (planned extraction) -mod plugin_sigs; // plugin signature loader -mod stmts; -mod utils; -mod vars; // variables/scope helpers // small loop helpers (header/exit context) -mod origin; // P0: origin inference(me/Known)と PHI 伝播(軽量) -mod observe; // P0: dev-only observability helpers(ssa/resolve) -mod rewrite; // P1: Known rewrite & special consolidation -mod ssa; // LocalSSA helpers (in-block materialization) -mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body) -mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約) -mod metadata; // MetadataPropagationBox(type/originの伝播) -mod emission; // emission::*(Const/Compare/Branch の薄い発行箱) -mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段) -mod router; // RouterPolicyBox(Unified vs BoxCall) +mod phi_merge; // Phase 25.1q: Unified PHI merge helper // prepare/lower_root/finalize split + // legacy large-match remains inline for now (planned extraction) +mod emission; // emission::*(Const/Compare/Branch の薄い発行箱) mod emit_guard; // EmitGuardBox(emit直前の最終関所) +mod metadata; // MetadataPropagationBox(type/originの伝播) mod name_const; // NameConstBox(関数名Const生成) -pub(crate) mod type_registry; // TypeRegistryBox(型情報管理の一元化) +mod observe; // P0: dev-only observability helpers(ssa/resolve) +mod origin; // P0: origin inference(me/Known)と PHI 伝播(軽量) +mod plugin_sigs; // plugin signature loader +mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約) +mod rewrite; // P1: Known rewrite & special consolidation +mod router; // RouterPolicyBox(Unified vs BoxCall) +mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body) +mod ssa; // LocalSSA helpers (in-block materialization) +mod stmts; +pub(crate) mod type_registry; +mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段) +mod utils; +mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox(型情報管理の一元化) // Unified member property kinds for computed/once/birth_once #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -62,7 +62,6 @@ pub(crate) enum PropertyKind { BirthOnce, } - /// MIR builder for converting AST to SSA form pub struct MirBuilder { /// Current module being built @@ -152,7 +151,6 @@ pub struct MirBuilder { pub(super) current_region_stack: Vec, // include guards removed - /// Loop context stacks for lowering break/continue inside nested control flow /// Top of stack corresponds to the innermost active loop pub(super) loop_header_stack: Vec, @@ -288,19 +286,31 @@ impl MirBuilder { } /// Push/pop helpers for If merge context (best-effort; optional usage) - pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { self.if_merge_stack.push(bb); } - pub(super) fn pop_if_merge(&mut self) { let _ = self.if_merge_stack.pop(); } + pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { + self.if_merge_stack.push(bb); + } + pub(super) fn pop_if_merge(&mut self) { + let _ = self.if_merge_stack.pop(); + } /// Suppress entry pin copy for the next start_new_block (used for merge blocks). - pub(super) fn suppress_next_entry_pin_copy(&mut self) { self.suppress_pin_entry_copy_next = true; } + pub(super) fn suppress_next_entry_pin_copy(&mut self) { + self.suppress_pin_entry_copy_next = true; + } // ---- Hint helpers (no-op by default) ---- #[inline] - pub(crate) fn hint_scope_enter(&mut self, id: u32) { self.hint_sink.scope_enter(id); } + pub(crate) fn hint_scope_enter(&mut self, id: u32) { + self.hint_sink.scope_enter(id); + } #[inline] - pub(crate) fn hint_scope_leave(&mut self, id: u32) { self.hint_sink.scope_leave(id); } + pub(crate) fn hint_scope_leave(&mut self, id: u32) { + self.hint_sink.scope_leave(id); + } #[inline] - pub(crate) fn hint_join_result>(&mut self, var: S) { self.hint_sink.join_result(var.into()); } + pub(crate) fn hint_join_result>(&mut self, var: S) { + self.hint_sink.join_result(var.into()); + } // ---------------------- // Debug scope helpers (region_id for DebugHub events) @@ -332,10 +342,7 @@ impl MirBuilder { // ---------------------- #[inline] pub(super) fn compile_trace_enabled() -> bool { - std::env::var("NYASH_MIR_COMPILE_TRACE") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_MIR_COMPILE_TRACE").ok().as_deref() == Some("1") } #[inline] @@ -395,7 +402,6 @@ impl MirBuilder { .unwrap_or_default() } - /// Build a complete MIR module from AST pub fn build_module(&mut self, ast: ASTNode) -> Result { self.prepare_module()?; @@ -403,7 +409,6 @@ impl MirBuilder { self.finalize_module(result_value) } - /// Build an expression and return its value ID pub(super) fn build_expression(&mut self, ast: ASTNode) -> Result { // Delegated to exprs.rs to keep this file lean @@ -412,11 +417,17 @@ impl MirBuilder { self.recursion_depth += 1; if self.recursion_depth > MAX_RECURSION_DEPTH { eprintln!("\n[FATAL] ============================================"); - eprintln!("[FATAL] Recursion depth exceeded {} in build_expression", MAX_RECURSION_DEPTH); + eprintln!( + "[FATAL] Recursion depth exceeded {} in build_expression", + MAX_RECURSION_DEPTH + ); eprintln!("[FATAL] Current depth: {}", self.recursion_depth); eprintln!("[FATAL] AST node type: {:?}", std::mem::discriminant(&ast)); eprintln!("[FATAL] ============================================\n"); - return Err(format!("Recursion depth exceeded: {} (possible infinite loop)", self.recursion_depth)); + return Err(format!( + "Recursion depth exceeded: {} (possible infinite loop)", + self.recursion_depth + )); } let result = self.build_expression_impl(ast); @@ -424,7 +435,6 @@ impl MirBuilder { result } - /// Build a literal value pub(super) fn build_literal(&mut self, literal: LiteralValue) -> Result { // Determine type without moving literal @@ -438,9 +448,13 @@ impl MirBuilder { // Emit via ConstantEmissionBox(仕様不変の統一ルート) let dst = match literal { - LiteralValue::Integer(n) => crate::mir::builder::emission::constant::emit_integer(self, n), + LiteralValue::Integer(n) => { + crate::mir::builder::emission::constant::emit_integer(self, n) + } LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f), - LiteralValue::String(s) => crate::mir::builder::emission::constant::emit_string(self, s), + LiteralValue::String(s) => { + crate::mir::builder::emission::constant::emit_string(self, s) + } LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b), LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self), LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self), @@ -453,7 +467,6 @@ impl MirBuilder { Ok(dst) } - /// Build variable access pub(super) fn build_variable_access(&mut self, name: String) -> Result { // Step 5-5-G: __pin$ variables should NEVER be accessed from variable_map @@ -478,7 +491,8 @@ impl MirBuilder { msg.push_str("\nHint: 'local' is a Stage-3 keyword. Enable NYASH_PARSER_STAGE3=1 (and HAKO_PARSER_STAGE3=1 for Stage-B)."); msg.push_str("\nFor AotPrep verification, use tools/hakorune_emit_mir.sh which sets these automatically."); } else if (name == "flow" || name == "try" || name == "catch" || name == "throw") - && !crate::config::env::parser_stage3() { + && !crate::config::env::parser_stage3() + { msg.push_str(&format!("\nHint: '{}' is a Stage-3 keyword. Enable NYASH_PARSER_STAGE3=1 (and HAKO_PARSER_STAGE3=1 for Stage-B).", name)); } @@ -486,7 +500,9 @@ impl MirBuilder { if !suggest.is_empty() { msg.push_str("\nHint: symbol appears in using module(s): "); msg.push_str(&suggest.join(", ")); - msg.push_str("\nConsider adding 'using [as Alias]' or check nyash.toml [using]."); + msg.push_str( + "\nConsider adding 'using [as Alias]' or check nyash.toml [using].", + ); } Err(msg) } @@ -527,8 +543,6 @@ impl MirBuilder { Ok(value_id) } - - /// Emit an instruction to the current basic block pub(super) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> { let block_id = self.current_block.ok_or("No current basic block")?; @@ -551,9 +565,23 @@ impl MirBuilder { // CRITICAL: Final receiver materialization for MethodCall // This ensures the receiver has an in-block definition in the same block as the Call. // Must happen BEFORE function mutable borrow to avoid borrowck conflicts. - if let MirInstruction::Call { callee: Some(callee), dst, args, effects, .. } = &instruction { + if let MirInstruction::Call { + callee: Some(callee), + dst, + args, + effects, + .. + } = &instruction + { use crate::mir::definitions::call_unified::Callee; - if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } = callee.clone() { + if let Callee::Method { + box_name, + method, + receiver: Some(r), + certainty, + box_kind, + } = callee.clone() + { // LocalSSA: ensure receiver has a Copy in current_block let r_local = crate::mir::builder::ssa::local::recv(self, r); @@ -579,7 +607,9 @@ impl MirBuilder { // Pre-capture branch/jump targets for predecessor update after we finish // mutably borrowing the current block. let (then_t, else_t, jump_t) = match &instruction { - MirInstruction::Branch { then_bb, else_bb, .. } => (Some(*then_bb), Some(*else_bb), None), + MirInstruction::Branch { + then_bb, else_bb, .. + } => (Some(*then_bb), Some(*else_bb), None), MirInstruction::Jump { target } => (None, None, Some(*target)), _ => (None, None, None), }; @@ -607,7 +637,13 @@ impl MirBuilder { return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into()); } else if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { use crate::mir::definitions::call_unified::Callee; - if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee { + if let Some(Callee::Method { + box_name, + method, + receiver: Some(r), + .. + }) = callee + { eprintln!( "[emit-inst] fn={} bb={:?} Call {}.{} recv=%{}", current_fn_name, @@ -617,9 +653,16 @@ impl MirBuilder { r.0 ); } - } else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") { + } else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") + { use crate::mir::definitions::call_unified::Callee; - if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee { + if let Some(Callee::Method { + box_name, + method, + receiver: Some(r), + .. + }) = callee + { let names: Vec = self .variable_map .iter() @@ -688,18 +731,24 @@ impl MirBuilder { } block.add_instruction(instruction); // Drop the mutable borrow of `block` before updating other blocks - } + } // Update predecessor sets for branch/jump immediately so that // debug_verify_phi_inputs can observe a consistent CFG without // requiring a full function.update_cfg() pass. if let Some(t) = then_t { - if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } + if let Some(succ) = function.get_block_mut(t) { + succ.add_predecessor(block_id); + } } if let Some(t) = else_t { - if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } + if let Some(succ) = function.get_block_mut(t) { + succ.add_predecessor(block_id); + } } if let Some(t) = jump_t { - if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); } + if let Some(succ) = function.get_block_mut(t) { + succ.add_predecessor(block_id); + } } Ok(()) } else { @@ -726,7 +775,10 @@ impl MirBuilder { } } } - Err(format!("PHI instruction {} not found in block {}", phi_id, block)) + Err(format!( + "PHI instruction {} not found in block {}", + phi_id, block + )) } else { Err(format!("Block {} not found", block)) } @@ -739,7 +791,6 @@ impl MirBuilder { // フェーズM: insert_edge_copy()メソッド削除(no_phi_mode撤廃により不要) - /// Build new expression: new ClassName(arguments) pub(super) fn build_new_expression( &mut self, @@ -825,14 +876,15 @@ impl MirBuilder { // 代替: 既存互換として BoxCall("birth")(プラグイン/ビルトインの初期化に対応) if class != "StringBox" { let arity = arg_values.len(); - let lowered = crate::mir::builder::calls::function_lowering::generate_method_function_name( - &class, - "birth", - arity, - ); + let lowered = + crate::mir::builder::calls::function_lowering::generate_method_function_name( + &class, "birth", arity, + ); let use_lowered = if let Some(ref module) = self.current_module { module.functions.contains_key(&lowered) - } else { false }; + } else { + false + }; if use_lowered { // Call Global("Class.birth/Arity") with argv = [me, args...] let mut argv: Vec = Vec::with_capacity(1 + arity); @@ -847,7 +899,10 @@ impl MirBuilder { let is_user_box = self.user_defined_boxes.contains(&class); // Dev safety: allow disabling birth() injection for builtins to avoid // unified-call method dispatch issues while migrating. Off by default unless explicitly enabled. - let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS").ok().as_deref() == Some("1"); + let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS") + .ok() + .as_deref() + == Some("1"); if !is_user_box && allow_builtin_birth { let birt_mid = resolve_slot_by_type_name(&class, "birth"); self.emit_box_or_plugin_call( @@ -865,7 +920,6 @@ impl MirBuilder { Ok(dst) } - /// Check if the current basic block is terminated fn is_current_block_terminated(&self) -> bool { if let (Some(block_id), Some(ref function)) = (self.current_block, &self.current_function) { @@ -919,7 +973,6 @@ impl MirBuilder { } } - impl Default for MirBuilder { fn default() -> Self { Self::new() diff --git a/src/mir/builder/call_resolution.rs b/src/mir/builder/call_resolution.rs index 2451c70a..6551e03b 100644 --- a/src/mir/builder/call_resolution.rs +++ b/src/mir/builder/call_resolution.rs @@ -39,7 +39,10 @@ pub fn suggest_resolution(name: &str) -> String { "Consider using ArrayBox methods or array.* functions".to_string() } _ => { - format!("Function '{}' not found. Check spelling or add explicit scope qualifier", name) + format!( + "Function '{}' not found. Check spelling or add explicit scope qualifier", + name + ) } } } @@ -53,7 +56,7 @@ pub fn is_commonly_shadowed_method(method: &str) -> bool { "print" | "error" | "log" | "panic" | // Console methods "length" | "size" | "count" | // Container methods "toString" | "valueOf" | // Conversion methods - "equals" | "compare" // Comparison methods + "equals" | "compare" // Comparison methods ) } diff --git a/src/mir/builder/calls/annotation.rs b/src/mir/builder/calls/annotation.rs index b595a40e..cd5e11bb 100644 --- a/src/mir/builder/calls/annotation.rs +++ b/src/mir/builder/calls/annotation.rs @@ -36,9 +36,18 @@ pub(in super::super) fn annotate_call_result_from_func_name>( builder.value_types.insert(dst, ret.clone()); if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - let bx = builder.value_origin_newbox.get(&dst).cloned().unwrap_or_default(); - super::super::utils::builder_debug_log(&format!("annotate call dst={} from {} -> Box({})", dst.0, name, bx)); + if super::super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + let bx = builder + .value_origin_newbox + .get(&dst) + .cloned() + .unwrap_or_default(); + super::super::utils::builder_debug_log(&format!( + "annotate call dst={} from {} -> Box({})", + dst.0, name, bx + )); } } return; @@ -48,32 +57,60 @@ pub(in super::super) fn annotate_call_result_from_func_name>( if name == "JsonParser.parse/1" { let ret = MirType::Box("JsonNode".into()); builder.value_types.insert(dst, ret.clone()); - if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); } - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonNode)", dst.0, name)); + if let MirType::Box(bx) = ret { + builder.value_origin_newbox.insert(dst, bx); + } + if super::super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + super::super::utils::builder_debug_log(&format!( + "annotate call (fallback) dst={} from {} -> Box(JsonNode)", + dst.0, name + )); } } else if name == "JsonParser.current_token/0" { let ret = MirType::Box("JsonToken".into()); builder.value_types.insert(dst, ret.clone()); - if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); } - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonToken)", dst.0, name)); + if let MirType::Box(bx) = ret { + builder.value_origin_newbox.insert(dst, bx); + } + if super::super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + super::super::utils::builder_debug_log(&format!( + "annotate call (fallback) dst={} from {} -> Box(JsonToken)", + dst.0, name + )); } } else if name == "JsonTokenizer.tokenize/0" { // Tokenize returns an ArrayBox of tokens let ret = MirType::Box("ArrayBox".into()); builder.value_types.insert(dst, ret.clone()); - if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); } - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(ArrayBox)", dst.0, name)); + if let MirType::Box(bx) = ret { + builder.value_origin_newbox.insert(dst, bx); + } + if super::super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + super::super::utils::builder_debug_log(&format!( + "annotate call (fallback) dst={} from {} -> Box(ArrayBox)", + dst.0, name + )); } } else if name == "JsonParserModule.create_parser/0" { // Fallback path for parser factory let ret = MirType::Box("JsonParser".into()); builder.value_types.insert(dst, ret.clone()); - if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); } - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonParser)", dst.0, name)); + if let MirType::Box(bx) = ret { + builder.value_origin_newbox.insert(dst, bx); + } + if super::super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + super::super::utils::builder_debug_log(&format!( + "annotate call (fallback) dst={} from {} -> Box(JsonParser)", + dst.0, name + )); } } else { // Generic tiny whitelist for known primitive-like utilities (spec unchanged) diff --git a/src/mir/builder/calls/build.rs b/src/mir/builder/calls/build.rs index f345c9a7..f636ee85 100644 --- a/src/mir/builder/calls/build.rs +++ b/src/mir/builder/calls/build.rs @@ -5,11 +5,11 @@ //! - build_method_call: メソッド呼び出し構築 //! - build_from_expression: from式構築 -use crate::ast::{ASTNode, LiteralValue}; use super::super::{Effect, EffectMask, MirBuilder, MirInstruction, MirType, ValueId}; -use crate::mir::TypeOpKind; -use super::CallTarget; use super::special_handlers; +use super::CallTarget; +use crate::ast::{ASTNode, LiteralValue}; +use crate::mir::TypeOpKind; impl MirBuilder { // Build function call: name(args) @@ -20,7 +20,11 @@ impl MirBuilder { ) -> Result { // Dev trace if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - let cur_fun = self.current_function.as_ref().map(|f| f.signature.name.clone()).unwrap_or_else(|| "".to_string()); + let cur_fun = self + .current_function + .as_ref() + .map(|f| f.signature.name.clone()) + .unwrap_or_else(|| "".to_string()); eprintln!( "[builder] function-call name={} static_ctx={} in_fn={}", name, @@ -71,10 +75,16 @@ impl MirBuilder { const MAX_METHOD_DEPTH: usize = 100; self.recursion_depth += 1; if self.recursion_depth > MAX_METHOD_DEPTH { - eprintln!("[FATAL] build_method_call recursion depth exceeded {}", MAX_METHOD_DEPTH); + eprintln!( + "[FATAL] build_method_call recursion depth exceeded {}", + MAX_METHOD_DEPTH + ); eprintln!("[FATAL] Current depth: {}", self.recursion_depth); eprintln!("[FATAL] Method: {}", method); - return Err(format!("build_method_call recursion depth exceeded: {}", self.recursion_depth)); + return Err(format!( + "build_method_call recursion depth exceeded: {}", + self.recursion_depth + )); } let result = self.build_method_call_impl(object, method, arguments); @@ -96,7 +106,10 @@ impl MirBuilder { ASTNode::Me { .. } => "Me", _ => "Other", }; - eprintln!("[builder] method-call object kind={} method={}", kind, method); + eprintln!( + "[builder] method-call object kind={} method={}", + kind, method + ); } // 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering @@ -110,7 +123,9 @@ impl MirBuilder { // 1. Static box method call: BoxName.method(args) if let ASTNode::Variable { name: obj_name, .. } = &object { - if let Some(result) = self.try_build_static_method_call(obj_name, &method, &arguments)? { + if let Some(result) = + self.try_build_static_method_call(obj_name, &method, &arguments)? + { return Ok(result); } } @@ -211,40 +226,43 @@ impl MirBuilder { let mut math_args: Vec = Vec::new(); for a in raw_args.into_iter() { match a { - ASTNode::New { class, arguments, .. } if class == "FloatBox" && arguments.len() == 1 => { + ASTNode::New { + class, arguments, .. + } if class == "FloatBox" && arguments.len() == 1 => { match self.build_expression(arguments[0].clone()) { v @ Ok(_) => math_args.push(v.unwrap()), err @ Err(_) => return Some(err), } } - ASTNode::New { class, arguments, .. } if class == "IntegerBox" && arguments.len() == 1 => { + ASTNode::New { + class, arguments, .. + } if class == "IntegerBox" && arguments.len() == 1 => { let iv = match self.build_expression(arguments[0].clone()) { Ok(v) => v, - Err(e) => return Some(Err(e)) + Err(e) => return Some(Err(e)), }; let fv = self.next_value_id(); if let Err(e) = self.emit_instruction(MirInstruction::TypeOp { dst: fv, op: TypeOpKind::Cast, value: iv, - ty: MirType::Float + ty: MirType::Float, }) { return Some(Err(e)); } math_args.push(fv); } - ASTNode::Literal { value: LiteralValue::Float(_), .. } => { - match self.build_expression(a) { - v @ Ok(_) => math_args.push(v.unwrap()), - err @ Err(_) => return Some(err), - } - } - other => { - match self.build_expression(other) { - v @ Ok(_) => math_args.push(v.unwrap()), - err @ Err(_) => return Some(err), - } - } + ASTNode::Literal { + value: LiteralValue::Float(_), + .. + } => match self.build_expression(a) { + v @ Ok(_) => math_args.push(v.unwrap()), + err @ Err(_) => return Some(err), + }, + other => match self.build_expression(other) { + v @ Ok(_) => math_args.push(v.unwrap()), + err @ Err(_) => return Some(err), + }, } } // new MathBox() @@ -252,7 +270,8 @@ impl MirBuilder { if let Err(e) = self.emit_constructor_call(math_recv, "MathBox".to_string(), vec![]) { return Some(Err(e)); } - self.value_origin_newbox.insert(math_recv, "MathBox".to_string()); + self.value_origin_newbox + .insert(math_recv, "MathBox".to_string()); // birth() if let Err(e) = self.emit_method_call(None, math_recv, "birth".to_string(), vec![]) { return Some(Err(e)); @@ -272,29 +291,40 @@ impl MirBuilder { method: &str, arguments: &Vec, ) -> Option> { - let ASTNode::FieldAccess { object: env_obj, field: env_field, .. } = object else { + let ASTNode::FieldAccess { + object: env_obj, + field: env_field, + .. + } = object + else { return None; }; if let ASTNode::Variable { name: env_name, .. } = env_obj.as_ref() { - if env_name != "env" { return None; } + if env_name != "env" { + return None; + } // Build arguments once let mut arg_values = Vec::new(); for arg in arguments { match self.build_expression(arg.clone()) { Ok(v) => arg_values.push(v), - Err(e) => return Some(Err(e)) + Err(e) => return Some(Err(e)), } } let iface = env_field.as_str(); let m = method; - let mut extern_call = |iface_name: &str, method_name: &str, effects: EffectMask, returns: bool| -> Result { + let mut extern_call = |iface_name: &str, + method_name: &str, + effects: EffectMask, + returns: bool| + -> Result { let result_id = self.next_value_id(); self.emit_instruction(MirInstruction::ExternCall { dst: if returns { Some(result_id) } else { None }, iface_name: iface_name.to_string(), method_name: method_name.to_string(), args: arg_values.clone(), - effects + effects, })?; if returns { Ok(result_id) @@ -351,7 +381,8 @@ impl MirBuilder { if let Some(result) = self.try_tail_based_fallback(&name, &arg_values)? { return Ok(result); } - return Err(format!("Unresolved function: '{}'. {}", + return Err(format!( + "Unresolved function: '{}'. {}", name, super::super::call_resolution::suggest_resolution(&name) )); @@ -379,11 +410,7 @@ impl MirBuilder { arg_values: Vec, ) -> Result { let dst = self.next_value_id(); - self.emit_unified_call( - Some(dst), - CallTarget::Global(name), - arg_values, - )?; + self.emit_unified_call(Some(dst), CallTarget::Global(name), arg_values)?; Ok(dst) } @@ -398,11 +425,17 @@ impl MirBuilder { // Debug trace if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { - eprintln!("[DEBUG] try_build_static_method_call: obj_name={}, method={}", obj_name, method); + eprintln!( + "[DEBUG] try_build_static_method_call: obj_name={}, method={}", + obj_name, method + ); eprintln!("[DEBUG] is_local_var={}", is_local_var); if is_local_var { eprintln!("[DEBUG] variable_map contains '{}' - treating as local variable, will use method call", obj_name); - eprintln!("[DEBUG] variable_map keys: {:?}", self.variable_map.keys().collect::>()); + eprintln!( + "[DEBUG] variable_map keys: {:?}", + self.variable_map.keys().collect::>() + ); } else { eprintln!("[DEBUG] '{}' not in variable_map - treating as static box, will use global call", obj_name); } @@ -488,7 +521,7 @@ impl MirBuilder { self.emit_unified_call( Some(dst), CallTarget::Global(func_name), - arg_values.to_vec() + arg_values.to_vec(), )?; return Ok(Some(dst)); } @@ -517,7 +550,7 @@ impl MirBuilder { self.emit_legacy_call( Some(dst), CallTarget::Global(func_name), - arg_values.to_vec() + arg_values.to_vec(), )?; return Ok(Some(dst)); } @@ -530,20 +563,31 @@ impl MirBuilder { fn trace_receiver_if_enabled(&self, object: &ASTNode, object_value: ValueId) { if std::env::var("NYASH_DEBUG_PARAM_RECEIVER").ok().as_deref() == Some("1") { if let ASTNode::Variable { name, .. } = object { - eprintln!("[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})", - name, object_value.0); + eprintln!( + "[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})", + name, object_value.0 + ); if let Some(origin) = self.value_origin_newbox.get(&object_value) { eprintln!("[DEBUG/param-recv] origin: {}", origin); } if let Some(&mapped_id) = self.variable_map.get(name) { - eprintln!("[DEBUG/param-recv] variable_map['{}'] = ValueId({})", name, mapped_id.0); + eprintln!( + "[DEBUG/param-recv] variable_map['{}'] = ValueId({})", + name, mapped_id.0 + ); if mapped_id != object_value { eprintln!("[DEBUG/param-recv] ⚠️ MISMATCH! build_expression returned different ValueId!"); } } else { - eprintln!("[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!", name); + eprintln!( + "[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!", + name + ); } - eprintln!("[DEBUG/param-recv] current_block: {:?}", self.current_block); + eprintln!( + "[DEBUG/param-recv] current_block: {:?}", + self.current_block + ); } } } diff --git a/src/mir/builder/calls/call_target.rs b/src/mir/builder/calls/call_target.rs index 5894d0eb..6bb5bacd 100644 --- a/src/mir/builder/calls/call_target.rs +++ b/src/mir/builder/calls/call_target.rs @@ -16,7 +16,7 @@ pub enum CallTarget { /// Method call (box.method) Method { - box_type: Option, // None = infer from value + box_type: Option, // None = infer from value method: String, receiver: ValueId, }, @@ -55,4 +55,4 @@ impl CallTarget { CallTarget::Closure { .. } => "".to_string(), } } -} \ No newline at end of file +} diff --git a/src/mir/builder/calls/call_unified.rs b/src/mir/builder/calls/call_unified.rs index d222d508..d4e67200 100644 --- a/src/mir/builder/calls/call_unified.rs +++ b/src/mir/builder/calls/call_unified.rs @@ -5,12 +5,16 @@ * Replaces 6 different call instructions with a single unified system */ -use crate::mir::{Callee, EffectMask, ValueId}; use crate::mir::definitions::call_unified::{CallFlags, MirCall}; +use crate::mir::{Callee, EffectMask, ValueId}; /// Check if unified call system is enabled pub fn is_unified_call_enabled() -> bool { - match std::env::var("NYASH_MIR_UNIFIED_CALL").ok().as_deref().map(|s| s.to_ascii_lowercase()) { + match std::env::var("NYASH_MIR_UNIFIED_CALL") + .ok() + .as_deref() + .map(|s| s.to_ascii_lowercase()) + { Some(s) if s == "0" || s == "false" || s == "off" => false, _ => true, // default ON during development; explicit opt-out supported } @@ -74,11 +78,7 @@ pub fn create_call_flags(callee: &Callee) -> CallFlags { } /// Create a MirCall instruction from components -pub fn create_mir_call( - dst: Option, - callee: Callee, - args: Vec, -) -> MirCall { +pub fn create_mir_call(dst: Option, callee: Callee, args: Vec) -> MirCall { let effects = compute_call_effects(&callee); let flags = create_call_flags(&callee); diff --git a/src/mir/builder/calls/effects_analyzer.rs b/src/mir/builder/calls/effects_analyzer.rs index 562f1331..896aaf26 100644 --- a/src/mir/builder/calls/effects_analyzer.rs +++ b/src/mir/builder/calls/effects_analyzer.rs @@ -12,9 +12,9 @@ * - 既知の関数・メソッドのエフェクト知識を集約 */ -use crate::mir::definitions::call_unified::Callee; -use crate::mir::builder::{Effect, EffectMask}; use super::extern_calls; +use crate::mir::builder::{Effect, EffectMask}; +use crate::mir::definitions::call_unified::Callee; /// エフェクト解析専用箱 /// @@ -36,16 +36,16 @@ impl EffectsAnalyzerBox { /// - WriteHeap: ヒープ書き込み pub fn compute_call_effects(callee: &Callee) -> EffectMask { match callee { - Callee::Global(name) => { - match name.as_str() { - "print" | "error" => EffectMask::IO, - "panic" | "exit" => EffectMask::IO.add(Effect::Control), - "gc_collect" => EffectMask::IO.add(Effect::Alloc), - _ => EffectMask::IO, - } + Callee::Global(name) => match name.as_str() { + "print" | "error" => EffectMask::IO, + "panic" | "exit" => EffectMask::IO.add(Effect::Control), + "gc_collect" => EffectMask::IO.add(Effect::Alloc), + _ => EffectMask::IO, }, - Callee::Method { method, box_name, .. } => { + Callee::Method { + method, box_name, .. + } => { match method.as_str() { "birth" => EffectMask::PURE.add(Effect::Alloc), "get" | "length" | "size" => EffectMask::READ, @@ -59,7 +59,7 @@ impl EffectsAnalyzerBox { } } } - }, + } Callee::Constructor { .. } => EffectMask::PURE.add(Effect::Alloc), @@ -68,7 +68,7 @@ impl EffectsAnalyzerBox { Callee::Extern(name) => { let (iface, method) = extern_calls::parse_extern_name(name); extern_calls::compute_extern_effects(&iface, &method) - }, + } Callee::Value(_) => EffectMask::IO, // Conservative for dynamic calls } @@ -100,8 +100,8 @@ impl EffectsAnalyzerBox { #[cfg(test)] mod tests { use super::*; - use crate::mir::ValueId; use crate::mir::definitions::call_unified::{CalleeBoxKind, TypeCertainty}; + use crate::mir::ValueId; #[test] fn test_compute_effects_global() { @@ -143,7 +143,10 @@ mod tests { }; let effects = EffectsAnalyzerBox::compute_call_effects(&callee); // Constructor should have PURE + Alloc - assert_eq!(effects, EffectMask::PURE.add(crate::mir::builder::Effect::Alloc)); + assert_eq!( + effects, + EffectMask::PURE.add(crate::mir::builder::Effect::Alloc) + ); } #[test] diff --git a/src/mir/builder/calls/emit.rs b/src/mir/builder/calls/emit.rs index a5e962c6..552468a8 100644 --- a/src/mir/builder/calls/emit.rs +++ b/src/mir/builder/calls/emit.rs @@ -6,8 +6,8 @@ //! - emit_global_call/emit_method_call/emit_constructor_call: 便利ラッパー use super::super::{EffectMask, MirBuilder, MirInstruction, ValueId}; -use crate::mir::definitions::call_unified::Callee; use super::CallTarget; +use crate::mir::definitions::call_unified::Callee; impl MirBuilder { /// Unified call emission - delegates to UnifiedCallEmitterBox @@ -21,7 +21,6 @@ impl MirBuilder { super::unified_emitter::UnifiedCallEmitterBox::emit_unified_call(self, dst, target, args) } - /// Legacy call fallback - preserves existing behavior pub fn emit_legacy_call( &mut self, @@ -30,7 +29,11 @@ impl MirBuilder { args: Vec, ) -> Result<(), String> { match target { - CallTarget::Method { receiver, method, box_type: _ } => { + CallTarget::Method { + receiver, + method, + box_type: _, + } => { // LEGACY PATH (after unified migration): // Instance→Function rewrite is centralized in unified call path. // Legacy path no longer functionizes; always use Box/Plugin call here. @@ -38,10 +41,11 @@ impl MirBuilder { // Set flag to prevent emit_box_or_plugin_call from calling emit_unified_call let prev_flag = self.in_unified_boxcall_fallback; self.in_unified_boxcall_fallback = true; - let result = self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO); + let result = + self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO); self.in_unified_boxcall_fallback = prev_flag; result - }, + } CallTarget::Constructor(box_type) => { // Use existing NewBox let dst = dst.ok_or("Constructor must have destination")?; @@ -50,7 +54,7 @@ impl MirBuilder { box_type, args, }) - }, + } CallTarget::Extern(name) => { // Use existing ExternCall let mut args = args; @@ -69,14 +73,22 @@ impl MirBuilder { args, effects: EffectMask::IO, }) - }, + } CallTarget::Global(name) => { - super::unified_emitter::UnifiedCallEmitterBox::emit_global_unified(self, dst, name, args) - }, + super::unified_emitter::UnifiedCallEmitterBox::emit_global_unified( + self, dst, name, args, + ) + } CallTarget::Value(func_val) => { - super::unified_emitter::UnifiedCallEmitterBox::emit_value_unified(self, dst, func_val, args) - }, - CallTarget::Closure { params, captures, me_capture } => { + super::unified_emitter::UnifiedCallEmitterBox::emit_value_unified( + self, dst, func_val, args, + ) + } + CallTarget::Closure { + params, + captures, + me_capture, + } => { let dst = dst.ok_or("Closure creation must have destination")?; self.emit_instruction(MirInstruction::NewClosure { dst, @@ -85,7 +97,7 @@ impl MirBuilder { captures, me: me_capture, }) - }, + } } } @@ -127,11 +139,7 @@ impl MirBuilder { box_type: String, args: Vec, ) -> Result<(), String> { - self.emit_unified_call( - Some(dst), - CallTarget::Constructor(box_type), - args, - ) + self.emit_unified_call(Some(dst), CallTarget::Constructor(box_type), args) } // ======================================== @@ -146,7 +154,9 @@ impl MirBuilder { name: &str, args: &[ValueId], ) -> Result, String> { - super::materializer::CallMaterializerBox::try_global_fallback_handlers(self, dst, name, args) + super::materializer::CallMaterializerBox::try_global_fallback_handlers( + self, dst, name, args, + ) } /// Ensure receiver is materialized in Callee::Method (delegates to CallMaterializerBox) diff --git a/src/mir/builder/calls/extern_calls.rs b/src/mir/builder/calls/extern_calls.rs index 6a56f32b..c3e1af16 100644 --- a/src/mir/builder/calls/extern_calls.rs +++ b/src/mir/builder/calls/extern_calls.rs @@ -125,18 +125,8 @@ pub fn get_env_method_spec( )), // Direct env access - ("env", "get") => Some(( - "env".to_string(), - "get".to_string(), - EffectMask::READ, - true, - )), - ("env", "set") => Some(( - "env".to_string(), - "set".to_string(), - EffectMask::IO, - false, - )), + ("env", "get") => Some(("env".to_string(), "get".to_string(), EffectMask::READ, true)), + ("env", "set") => Some(("env".to_string(), "set".to_string(), EffectMask::IO, false)), // Unknown _ => None, @@ -157,9 +147,16 @@ pub fn parse_extern_name(name: &str) -> (String, String) { /// Check if a name refers to an environment interface #[allow(dead_code)] pub fn is_env_interface(name: &str) -> bool { - matches!(name, - "env" | "env.console" | "env.fs" | "env.net" | - "env.canvas" | "env.task" | "env.future" | "env.process" + matches!( + name, + "env" + | "env.console" + | "env.fs" + | "env.net" + | "env.canvas" + | "env.task" + | "env.future" + | "env.process" ) } @@ -167,13 +164,9 @@ pub fn is_env_interface(name: &str) -> bool { pub fn compute_extern_effects(iface: &str, method: &str) -> EffectMask { match (iface, method) { // Pure reads - (_, m) if m.starts_with("get") || m == "argv" || m == "env" => { - EffectMask::READ - } + (_, m) if m.starts_with("get") || m == "argv" || m == "env" => EffectMask::READ, // Control flow changes - (_, "exit") | (_, "panic") | (_, "throw") => { - EffectMask::IO.add(Effect::Control) - } + (_, "exit") | (_, "panic") | (_, "throw") => EffectMask::IO.add(Effect::Control), // Memory allocation (_, m) if m.starts_with("new") || m.starts_with("create") => { EffectMask::IO.add(Effect::Alloc) diff --git a/src/mir/builder/calls/function_lowering.rs b/src/mir/builder/calls/function_lowering.rs index 8bbc9d81..110a362d 100644 --- a/src/mir/builder/calls/function_lowering.rs +++ b/src/mir/builder/calls/function_lowering.rs @@ -7,9 +7,9 @@ * Manages the complex state transitions during function lowering */ +use super::special_handlers::contains_value_return; use crate::ast::ASTNode; use crate::mir::{Effect, EffectMask, FunctionSignature, MirType}; -use super::special_handlers::contains_value_return; /// Prepare function signature for a box method /// Includes 'me' parameter as first parameter @@ -32,7 +32,7 @@ pub fn prepare_method_signature( // Determine return type based on body analysis let returns_value = contains_value_return(body); let ret_ty = if returns_value { - MirType::Unknown // Will be inferred later + MirType::Unknown // Will be inferred later } else { MirType::Void }; @@ -62,7 +62,7 @@ pub fn prepare_static_method_signature( // Determine return type based on body analysis let returns_value = contains_value_return(body); let ret_ty = if returns_value { - MirType::Unknown // Will be inferred later + MirType::Unknown // Will be inferred later } else { MirType::Void }; @@ -77,11 +77,7 @@ pub fn prepare_static_method_signature( /// Generate canonical method name for MIR function /// E.g., "StringBox.upper/0" for StringBox's upper method with 0 args -pub fn generate_method_function_name( - box_name: &str, - method_name: &str, - arity: usize, -) -> String { +pub fn generate_method_function_name(box_name: &str, method_name: &str, arity: usize) -> String { format!("{}.{}/{}", box_name, method_name, arity) } @@ -120,10 +116,9 @@ pub fn create_method_parameter_mapping( } /// Create parameter mapping for static method lowering -pub fn create_static_parameter_mapping( - params: &[String], -) -> Vec<(String, MirType)> { - params.iter() +pub fn create_static_parameter_mapping(params: &[String]) -> Vec<(String, MirType)> { + params + .iter() .map(|p| (p.clone(), MirType::Unknown)) .collect() } @@ -139,14 +134,24 @@ pub fn wrap_in_program(statements: Vec) -> ASTNode { /// Check if method name suggests it returns a value pub fn method_likely_returns_value(method_name: &str) -> bool { // Heuristic: methods that likely return values - method_name.starts_with("get") || - method_name.starts_with("is") || - method_name.starts_with("has") || - method_name.starts_with("to") || - matches!(method_name, - "length" | "size" | "count" | - "upper" | "lower" | "trim" | - "add" | "sub" | "mul" | "div" | - "min" | "max" | "abs" - ) + method_name.starts_with("get") + || method_name.starts_with("is") + || method_name.starts_with("has") + || method_name.starts_with("to") + || matches!( + method_name, + "length" + | "size" + | "count" + | "upper" + | "lower" + | "trim" + | "add" + | "sub" + | "mul" + | "div" + | "min" + | "max" + | "abs" + ) } diff --git a/src/mir/builder/calls/guard.rs b/src/mir/builder/calls/guard.rs index 91f25084..d6f93f19 100644 --- a/src/mir/builder/calls/guard.rs +++ b/src/mir/builder/calls/guard.rs @@ -12,8 +12,8 @@ * - receiver実体化の保証 */ -use crate::mir::{Callee, MirType, ValueId}; use crate::mir::definitions::call_unified::CalleeBoxKind; +use crate::mir::{Callee, MirType, ValueId}; use std::collections::HashMap; /// 構造ガード専用箱 @@ -49,31 +49,43 @@ impl<'a> CalleeGuardBox<'a> { /// - StageBArgsBox.resolve_src内のargs.get(i)がStage1UsingResolverBox.getに /// 化けるのを防ぐ(args型はMapBox/ArrayBox → 正規化) pub fn apply_static_runtime_guard(&self, callee: Callee) -> Result { - if let Callee::Method { ref box_name, ref method, receiver: Some(recv), certainty, box_kind } = callee { + if let Callee::Method { + ref box_name, + ref method, + receiver: Some(recv), + certainty, + box_kind, + } = callee + { // Only apply guard if box_kind is StaticCompiler if box_kind == CalleeBoxKind::StaticCompiler { // Check if receiver has a Box type if let Some(MirType::Box(receiver_box)) = self.value_types.get(&recv) { - let trace_enabled = std::env::var("NYASH_CALLEE_RESOLVE_TRACE") - .ok() - .as_deref() == Some("1"); + let trace_enabled = + std::env::var("NYASH_CALLEE_RESOLVE_TRACE").ok().as_deref() == Some("1"); // If receiver box type matches the static box name, this is a me-call // Let it through for static method lowering (don't normalize) if receiver_box == box_name { if trace_enabled { eprintln!("[static-runtime-guard] ME-CALL detected:"); - eprintln!(" {}.{} with receiver type: {} (same as box_name)", box_name, method, receiver_box); + eprintln!( + " {}.{} with receiver type: {} (same as box_name)", + box_name, method, receiver_box + ); eprintln!(" → Allowing for static method lowering"); } - return Ok(callee); // Pass through unchanged + return Ok(callee); // Pass through unchanged } // Otherwise, this is a true mix-up: runtime box with static box name // Normalize to the runtime box type if trace_enabled { eprintln!("[static-runtime-guard] CORRECTING mix-up:"); - eprintln!(" Original: {}.{} (box_kind=StaticCompiler)", box_name, method); + eprintln!( + " Original: {}.{} (box_kind=StaticCompiler)", + box_name, method + ); eprintln!(" Receiver %{} has runtime type: {}", recv.0, receiver_box); eprintln!(" Normalized: {}.{}", receiver_box, method); } @@ -83,7 +95,7 @@ impl<'a> CalleeGuardBox<'a> { method: method.clone(), receiver: Some(recv), certainty, - box_kind: CalleeBoxKind::RuntimeData, // Switch to runtime + box_kind: CalleeBoxKind::RuntimeData, // Switch to runtime }); } } @@ -188,7 +200,9 @@ mod tests { // Mix-up → should normalize to MapBox let result = guard.apply_static_runtime_guard(callee).unwrap(); match result { - Callee::Method { box_name, box_kind, .. } => { + Callee::Method { + box_name, box_kind, .. + } => { assert_eq!(box_name, "MapBox"); assert_eq!(box_kind, CalleeBoxKind::RuntimeData); } diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index e3e9ba1d..1e90a4be 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -5,11 +5,11 @@ //! - BoxCompilationContext による完全独立化 //! - パラメータ・型情報の適切な管理 -use crate::ast::ASTNode; -use crate::mir::builder::{MirBuilder, MirType, MirInstruction}; -use crate::mir::{MirValueKind, ValueId}; // Phase 26-A-3: ValueId型安全化 -use crate::mir::region::function_slot_registry::FunctionSlotRegistry; use super::function_lowering; +use crate::ast::ASTNode; +use crate::mir::builder::{MirBuilder, MirInstruction, MirType}; +use crate::mir::region::function_slot_registry::FunctionSlotRegistry; +use crate::mir::{MirValueKind, ValueId}; // Phase 26-A-3: ValueId型安全化 use std::collections::HashMap; /// 🎯 箱理論: Lowering Context(準備と復元) @@ -24,10 +24,7 @@ struct LoweringContext { impl MirBuilder { /// 🎯 箱理論: Step 1 - Lowering Context準備 - fn prepare_lowering_context( - &mut self, - func_name: &str, - ) -> LoweringContext { + fn prepare_lowering_context(&mut self, func_name: &str) -> LoweringContext { // Static box context設定 let saved_static_ctx = self.current_static_box.clone(); if let Some(pos) = func_name.find('.') { @@ -77,11 +74,8 @@ impl MirBuilder { body: &[ASTNode], ctx: &mut LoweringContext, ) -> Result<(), String> { - let signature = function_lowering::prepare_static_method_signature( - func_name.clone(), - params, - body, - ); + let signature = + function_lowering::prepare_static_method_signature(func_name.clone(), params, body); let entry = self.block_gen.next(); let function = super::super::MirFunction::new(signature, entry); @@ -89,7 +83,10 @@ impl MirBuilder { ctx.saved_function = self.current_function.take(); ctx.saved_block = self.current_block.take(); - eprintln!("[DEBUG/create_function_skeleton] Creating function: {}", func_name); + eprintln!( + "[DEBUG/create_function_skeleton] Creating function: {}", + func_name + ); eprintln!("[DEBUG/create_function_skeleton] Entry block: {:?}", entry); // 新しい関数に切り替え @@ -116,9 +113,15 @@ impl MirBuilder { if let Some(ref mut f) = self.current_function { // 📦 Hotfix 5: Use pre-populated params from MirFunction::new() // Static methods have implicit receiver at params[0], so actual parameters start at offset - let receiver_offset = if f.params.is_empty() { 0 } else { + let receiver_offset = if f.params.is_empty() { + 0 + } else { // If params already populated (by Hotfix 4+5), use them - if f.params.len() > params.len() { 1 } else { 0 } + if f.params.len() > params.len() { + 1 + } else { + 0 + } }; let param_types = f.signature.params.clone(); @@ -186,8 +189,7 @@ impl MirBuilder { // 型推論 if let Some(ref mut f) = self.current_function { - if returns_value - && matches!(f.signature.return_type, MirType::Void | MirType::Unknown) + if returns_value && matches!(f.signature.return_type, MirType::Void | MirType::Unknown) { let mut inferred: Option = None; 'search: for (_bid, bb) in f.blocks.iter() { @@ -254,12 +256,8 @@ impl MirBuilder { body: &[ASTNode], ctx: &mut LoweringContext, ) -> Result<(), String> { - let signature = function_lowering::prepare_method_signature( - func_name, - box_name, - params, - body, - ); + let signature = + function_lowering::prepare_method_signature(func_name, box_name, params, body); let entry = self.block_gen.next(); let function = super::super::MirFunction::new(signature, entry); @@ -418,8 +416,7 @@ impl MirBuilder { // 型推論(Step 5の一部として) if let Some(ref mut f) = self.current_function { - if returns_value - && matches!(f.signature.return_type, MirType::Void | MirType::Unknown) + if returns_value && matches!(f.signature.return_type, MirType::Void | MirType::Unknown) { let mut inferred: Option = None; 'search: for (_bid, bb) in f.blocks.iter() { diff --git a/src/mir/builder/calls/materializer.rs b/src/mir/builder/calls/materializer.rs index 2f67464f..c4ff86de 100644 --- a/src/mir/builder/calls/materializer.rs +++ b/src/mir/builder/calls/materializer.rs @@ -12,7 +12,7 @@ * - Call発行前の準備処理全般 */ -use crate::mir::builder::{MirBuilder, ValueId, MirInstruction, EffectMask}; +use crate::mir::builder::{EffectMask, MirBuilder, MirInstruction, ValueId}; use crate::mir::definitions::call_unified::Callee; /// Call前処理・準備専用箱 @@ -43,11 +43,17 @@ impl CallMaterializerBox { let one = crate::mir::builder::emission::constant::emit_integer(builder, 1); if dst.is_none() { // If a destination was not provided, copy into the allocated dstv - builder.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; + builder.emit_instruction(MirInstruction::Copy { + dst: dstv, + src: one, + })?; crate::mir::builder::metadata::propagate::propagate(builder, one, dstv); } else { // If caller provided dst, ensure the computed value lands there - builder.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; + builder.emit_instruction(MirInstruction::Copy { + dst: dstv, + src: one, + })?; crate::mir::builder::metadata::propagate::propagate(builder, one, dstv); } return Ok(Some(())); @@ -57,7 +63,8 @@ impl CallMaterializerBox { if let Some(ref module) = builder.current_module { if module.functions.contains_key(name) { let dstv = dst.unwrap_or_else(|| builder.next_value_id()); - let name_const = crate::mir::builder::name_const::make_name_const_result(builder, name)?; + let name_const = + crate::mir::builder::name_const::make_name_const_result(builder, name)?; builder.emit_instruction(MirInstruction::Call { dst: Some(dstv), func: name_const, @@ -82,7 +89,8 @@ impl CallMaterializerBox { let func_name = format!("{}.{}{}", bx, name, format!("/{}", args.len())); // Emit legacy call directly to preserve behavior let dstv = dst.unwrap_or_else(|| builder.next_value_id()); - let name_const = crate::mir::builder::name_const::make_name_const_result(builder, &func_name)?; + let name_const = + crate::mir::builder::name_const::make_name_const_result(builder, &func_name)?; builder.emit_instruction(MirInstruction::Call { dst: Some(dstv), func: name_const, diff --git a/src/mir/builder/calls/method_resolution.rs b/src/mir/builder/calls/method_resolution.rs index 82c382f1..ca1f1a8e 100644 --- a/src/mir/builder/calls/method_resolution.rs +++ b/src/mir/builder/calls/method_resolution.rs @@ -53,12 +53,17 @@ pub fn resolve_call_target( // or report a clear unresolved error. // 6. Resolution failed - prevent runtime string-based resolution - Err(format!("Unresolved function: '{}'. {}", name, suggest_resolution(name))) + Err(format!( + "Unresolved function: '{}'. {}", + name, + suggest_resolution(name) + )) } /// Check if function name is a built-in global function pub fn is_builtin_function(name: &str) -> bool { - matches!(name, + matches!( + name, "print" | "error" | "panic" | "exit" | "now" | "gc_collect" | "gc_stats" | // Math functions (handled specially) @@ -68,9 +73,7 @@ pub fn is_builtin_function(name: &str) -> bool { /// Check if function name is an external/host function pub fn is_extern_function(name: &str) -> bool { - name.starts_with("nyash.") || - name.starts_with("env.") || - name.starts_with("system.") + name.starts_with("nyash.") || name.starts_with("env.") || name.starts_with("system.") } /// Check if method is commonly shadowed (for warning generation) @@ -90,18 +93,12 @@ pub fn generate_self_recursion_warning(box_name: &str, method: &str) -> String { /// Suggest resolution for unresolved function pub fn suggest_resolution(name: &str) -> String { match name { - n if n.starts_with("console") => { - "Did you mean 'env.console.log' or 'print'?".to_string() - } - "log" | "println" => { - "Did you mean 'print' or 'env.console.log'?".to_string() - } + n if n.starts_with("console") => "Did you mean 'env.console.log' or 'print'?".to_string(), + "log" | "println" => "Did you mean 'print' or 'env.console.log'?".to_string(), n if n.contains('.') => { "Qualified names should use 'env.' prefix for external calls.".to_string() } - _ => { - "Check function name or ensure it's in scope.".to_string() - } + _ => "Check function name or ensure it's in scope.".to_string(), } } diff --git a/src/mir/builder/calls/mod.rs b/src/mir/builder/calls/mod.rs index 77c8d5f8..1d52e929 100644 --- a/src/mir/builder/calls/mod.rs +++ b/src/mir/builder/calls/mod.rs @@ -17,24 +17,24 @@ pub mod method_resolution; pub mod special_handlers; // New refactored modules (Box Theory Phase 1 & 2 & 25.1d & Phase 3) -pub mod lowering; -pub mod utils; -pub mod emit; // Phase 2: Call emission -pub mod build; // Phase 2: Call building -pub mod guard; // Phase 25.1d: Structural guard (static/runtime box separation) -pub mod resolver; // Phase 25.1d: Callee resolution (CallTarget → Callee) -pub mod unified_emitter; // Phase 3-A: Unified call emitter (統一Call発行専用箱) +pub mod build; // Phase 2: Call building pub mod effects_analyzer; // Phase 3-B: Effects analyzer (エフェクト解析専用箱) -pub mod materializer; // Phase 3-C: Call materializer (Call前処理・準備専用箱) +pub mod emit; // Phase 2: Call emission +pub mod guard; // Phase 25.1d: Structural guard (static/runtime box separation) +pub mod lowering; +pub mod materializer; +pub mod resolver; // Phase 25.1d: Callee resolution (CallTarget → Callee) +pub mod unified_emitter; // Phase 3-A: Unified call emitter (統一Call発行専用箱) +pub mod utils; // Phase 3-C: Call materializer (Call前処理・準備専用箱) // Re-export public interfaces #[allow(unused_imports)] +pub use build::*; +#[allow(unused_imports)] pub use call_target::CallTarget; #[allow(unused_imports)] +pub use emit::*; +#[allow(unused_imports)] pub use lowering::*; #[allow(unused_imports)] pub use utils::*; -#[allow(unused_imports)] -pub use emit::*; -#[allow(unused_imports)] -pub use build::*; diff --git a/src/mir/builder/calls/resolver.rs b/src/mir/builder/calls/resolver.rs index 260a6813..339c5719 100644 --- a/src/mir/builder/calls/resolver.rs +++ b/src/mir/builder/calls/resolver.rs @@ -12,11 +12,11 @@ * - Call引数検証 */ -use crate::mir::{Callee, MirType, ValueId}; +use super::method_resolution; +use crate::mir::builder::type_registry::TypeRegistry; use crate::mir::builder::CallTarget; use crate::mir::definitions::call_unified::{CalleeBoxKind, TypeCertainty}; -use crate::mir::builder::type_registry::TypeRegistry; -use super::method_resolution; +use crate::mir::{Callee, MirType, ValueId}; use std::collections::HashMap; /// Callee解決専用箱 @@ -56,9 +56,7 @@ impl<'a> CalleeResolverBox<'a> { /// /// 🎯 TypeRegistry対応: NYASH_USE_TYPE_REGISTRY=1 で registry 優先 pub fn resolve(&self, target: CallTarget) -> Result { - let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") - .ok() - .as_deref() == Some("1"); + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY").ok().as_deref() == Some("1"); match target { CallTarget::Global(name) => { @@ -71,27 +69,36 @@ impl<'a> CalleeResolverBox<'a> { // Module-local or static lowered function (e.g., "Box.method/N") Ok(Callee::Global(name)) } - }, + } - CallTarget::Method { box_type, method, receiver } => { + CallTarget::Method { + box_type, + method, + receiver, + } => { // 🔍 Debug: trace box_name resolution - let trace_enabled = std::env::var("NYASH_CALLEE_RESOLVE_TRACE") - .ok() - .as_deref() == Some("1"); + let trace_enabled = + std::env::var("NYASH_CALLEE_RESOLVE_TRACE").ok().as_deref() == Some("1"); if trace_enabled { - eprintln!("[callee-resolve] receiver=%{} method={}", receiver.0, method); + eprintln!( + "[callee-resolve] receiver=%{} method={}", + receiver.0, method + ); eprintln!("[callee-resolve] explicit box_type: {:?}", box_type); eprintln!("[callee-resolve] use_registry: {}", use_registry); } - let inferred_box_type = self.infer_box_type(receiver, box_type, trace_enabled, use_registry); + let inferred_box_type = + self.infer_box_type(receiver, box_type, trace_enabled, use_registry); // Certainty is Known when we have explicit origin or Box型の型情報を持つ場合 - let has_box_type = self.value_types + let has_box_type = self + .value_types .get(&receiver) .map(|t| matches!(t, MirType::Box(_))) .unwrap_or(false); - let certainty = if self.value_origin_newbox.contains_key(&receiver) || has_box_type { + let certainty = if self.value_origin_newbox.contains_key(&receiver) || has_box_type + { TypeCertainty::Known } else { TypeCertainty::Union @@ -101,7 +108,10 @@ impl<'a> CalleeResolverBox<'a> { let box_kind = self.classify_box_kind(&inferred_box_type); if trace_enabled { - eprintln!("[callee-resolve] inferred_box_name: {}", inferred_box_type); + eprintln!( + "[callee-resolve] inferred_box_name: {}", + inferred_box_type + ); eprintln!("[callee-resolve] box_kind: {:?}", box_kind); } @@ -112,23 +122,23 @@ impl<'a> CalleeResolverBox<'a> { certainty, box_kind, }) - }, + } - CallTarget::Constructor(box_type) => { - Ok(Callee::Constructor { box_type }) - }, + CallTarget::Constructor(box_type) => Ok(Callee::Constructor { box_type }), - CallTarget::Extern(name) => { - Ok(Callee::Extern(name)) - }, + CallTarget::Extern(name) => Ok(Callee::Extern(name)), - CallTarget::Value(func_val) => { - Ok(Callee::Value(func_val)) - }, + CallTarget::Value(func_val) => Ok(Callee::Value(func_val)), - CallTarget::Closure { params, captures, me_capture } => { - Ok(Callee::Closure { params, captures, me_capture }) - }, + CallTarget::Closure { + params, + captures, + me_capture, + } => Ok(Callee::Closure { + params, + captures, + me_capture, + }), } } @@ -183,14 +193,18 @@ impl<'a> CalleeResolverBox<'a> { } "exit" => { if args.len() != 1 { - return Err("exit requires exactly one argument (exit code)".to_string()); + return Err( + "exit requires exactly one argument (exit code)".to_string() + ); } } _ => {} // Unknown functions pass through } - }, + } - Callee::Method { box_name, method, .. } => { + Callee::Method { + box_name, method, .. + } => { // Validate known methods match (box_name.as_str(), method.as_str()) { ("ArrayBox", "get") | ("ArrayBox", "set") => { @@ -200,7 +214,7 @@ impl<'a> CalleeResolverBox<'a> { } _ => {} // Unknown methods pass through } - }, + } _ => {} // Other callee types don't have validation yet } @@ -240,12 +254,10 @@ impl<'a> CalleeResolverBox<'a> { } // 従来: HashMap から推論(型情報を優先し、origin は補助とする) - let from_type = self.value_types - .get(&receiver) - .and_then(|t| match t { - MirType::Box(box_name) => Some(box_name.clone()), - _ => None, - }); + let from_type = self.value_types.get(&receiver).and_then(|t| match t { + MirType::Box(box_name) => Some(box_name.clone()), + _ => None, + }); let from_origin = self.value_origin_newbox.get(&receiver).cloned(); if trace_enabled { @@ -254,14 +266,12 @@ impl<'a> CalleeResolverBox<'a> { } // 型情報(MirType)がある場合はそれを優先し、無い場合のみ origin にフォールバックする。 - from_type - .or(from_origin) - .unwrap_or_else(|| { - if trace_enabled { - eprintln!("[callee-resolve] FALLBACK: UnknownBox"); - } - "UnknownBox".to_string() - }) + from_type.or(from_origin).unwrap_or_else(|| { + if trace_enabled { + eprintln!("[callee-resolve] FALLBACK: UnknownBox"); + } + "UnknownBox".to_string() + }) }) } } @@ -277,14 +287,26 @@ mod tests { let resolver = CalleeResolverBox::new(&value_origin, &value_types, None); // Stage-B boxes - assert_eq!(resolver.classify_box_kind("StageBArgsBox"), CalleeBoxKind::StaticCompiler); - assert_eq!(resolver.classify_box_kind("StageBDriverBox"), CalleeBoxKind::StaticCompiler); + assert_eq!( + resolver.classify_box_kind("StageBArgsBox"), + CalleeBoxKind::StaticCompiler + ); + assert_eq!( + resolver.classify_box_kind("StageBDriverBox"), + CalleeBoxKind::StaticCompiler + ); // Stage-1 boxes - assert_eq!(resolver.classify_box_kind("Stage1UsingResolverBox"), CalleeBoxKind::StaticCompiler); + assert_eq!( + resolver.classify_box_kind("Stage1UsingResolverBox"), + CalleeBoxKind::StaticCompiler + ); // Parser boxes - assert_eq!(resolver.classify_box_kind("ParserBox"), CalleeBoxKind::StaticCompiler); + assert_eq!( + resolver.classify_box_kind("ParserBox"), + CalleeBoxKind::StaticCompiler + ); } #[test] @@ -293,10 +315,22 @@ mod tests { let value_types = HashMap::new(); let resolver = CalleeResolverBox::new(&value_origin, &value_types, None); - assert_eq!(resolver.classify_box_kind("MapBox"), CalleeBoxKind::RuntimeData); - assert_eq!(resolver.classify_box_kind("ArrayBox"), CalleeBoxKind::RuntimeData); - assert_eq!(resolver.classify_box_kind("StringBox"), CalleeBoxKind::RuntimeData); - assert_eq!(resolver.classify_box_kind("UnknownBox"), CalleeBoxKind::RuntimeData); + assert_eq!( + resolver.classify_box_kind("MapBox"), + CalleeBoxKind::RuntimeData + ); + assert_eq!( + resolver.classify_box_kind("ArrayBox"), + CalleeBoxKind::RuntimeData + ); + assert_eq!( + resolver.classify_box_kind("StringBox"), + CalleeBoxKind::RuntimeData + ); + assert_eq!( + resolver.classify_box_kind("UnknownBox"), + CalleeBoxKind::RuntimeData + ); } #[test] @@ -305,8 +339,14 @@ mod tests { let value_types = HashMap::new(); let resolver = CalleeResolverBox::new(&value_origin, &value_types, None); - assert_eq!(resolver.classify_box_kind("MyCustomBox"), CalleeBoxKind::UserDefined); - assert_eq!(resolver.classify_box_kind("PersonBox"), CalleeBoxKind::UserDefined); + assert_eq!( + resolver.classify_box_kind("MyCustomBox"), + CalleeBoxKind::UserDefined + ); + assert_eq!( + resolver.classify_box_kind("PersonBox"), + CalleeBoxKind::UserDefined + ); } #[test] diff --git a/src/mir/builder/calls/special_handlers.rs b/src/mir/builder/calls/special_handlers.rs index 8ea9e836..1d29a17e 100644 --- a/src/mir/builder/calls/special_handlers.rs +++ b/src/mir/builder/calls/special_handlers.rs @@ -12,7 +12,10 @@ use crate::mir::MirType; /// Check if a function is a math function pub fn is_math_function(name: &str) -> bool { - matches!(name, "sin" | "cos" | "abs" | "min" | "max" | "sqrt" | "pow" | "floor" | "ceil") + matches!( + name, + "sin" | "cos" | "abs" | "min" | "max" | "sqrt" | "pow" | "floor" | "ceil" + ) } /// Check if a method is a type operation (.is() or .as()) @@ -62,11 +65,17 @@ pub fn parse_type_name_to_mir(name: &str) -> MirType { /// Check if a value is a numeric literal or numeric Box constructor pub fn is_numeric_value(node: &ASTNode) -> bool { match node { - ASTNode::Literal { value: LiteralValue::Integer(_), .. } => true, - ASTNode::Literal { value: LiteralValue::Float(_), .. } => true, - ASTNode::New { class, arguments, .. } => { - (class == "IntegerBox" || class == "FloatBox") && arguments.len() == 1 - } + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } => true, + ASTNode::Literal { + value: LiteralValue::Float(_), + .. + } => true, + ASTNode::New { + class, arguments, .. + } => (class == "IntegerBox" || class == "FloatBox") && arguments.len() == 1, _ => false, } } @@ -74,8 +83,14 @@ pub fn is_numeric_value(node: &ASTNode) -> bool { /// Extract numeric type from AST node pub fn extract_numeric_type(node: &ASTNode) -> Option { match node { - ASTNode::Literal { value: LiteralValue::Integer(_), .. } => Some(MirType::Integer), - ASTNode::Literal { value: LiteralValue::Float(_), .. } => Some(MirType::Float), + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } => Some(MirType::Integer), + ASTNode::Literal { + value: LiteralValue::Float(_), + .. + } => Some(MirType::Float), ASTNode::New { class, .. } if class == "IntegerBox" => Some(MirType::Integer), ASTNode::New { class, .. } if class == "FloatBox" => Some(MirType::Float), _ => None, @@ -87,7 +102,11 @@ pub fn contains_value_return(nodes: &[ASTNode]) -> bool { fn node_has_value_return(node: &ASTNode) -> bool { match node { ASTNode::Return { value: Some(_), .. } => true, - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { contains_value_return(then_body) || else_body .as_ref() @@ -125,9 +144,9 @@ pub fn make_function_name_with_arity(base_name: &str, arity: usize) -> String { /// Check if a name is a reserved/special function pub fn is_reserved_function(name: &str) -> bool { - matches!(name, - "birth" | "me" | "this" | "super" | "from" | - "new" | "delete" | "typeof" | "instanceof" + matches!( + name, + "birth" | "me" | "this" | "super" | "from" | "new" | "delete" | "typeof" | "instanceof" ) } diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index eaee2e5f..5db2e444 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -13,10 +13,10 @@ * - emit_value_unified: 第一級関数呼び出し */ -use crate::mir::builder::{MirBuilder, ValueId, MirInstruction, Effect, EffectMask}; -use crate::mir::definitions::call_unified::Callee; -use super::CallTarget; use super::call_unified; +use super::CallTarget; +use crate::mir::builder::{Effect, EffectMask, MirBuilder, MirInstruction, ValueId}; +use crate::mir::definitions::call_unified::Callee; /// 統一Call発行専用箱 /// @@ -39,10 +39,16 @@ impl UnifiedCallEmitterBox { const MAX_EMIT_DEPTH: usize = 100; builder.recursion_depth += 1; if builder.recursion_depth > MAX_EMIT_DEPTH { - eprintln!("[FATAL] emit_unified_call recursion depth exceeded {}", MAX_EMIT_DEPTH); + eprintln!( + "[FATAL] emit_unified_call recursion depth exceeded {}", + MAX_EMIT_DEPTH + ); eprintln!("[FATAL] Current depth: {}", builder.recursion_depth); eprintln!("[FATAL] Target: {:?}", target); - return Err(format!("emit_unified_call recursion depth exceeded: {}", builder.recursion_depth)); + return Err(format!( + "emit_unified_call recursion depth exceeded: {}", + builder.recursion_depth + )); } // Check environment variable for unified call usage @@ -62,11 +68,16 @@ impl UnifiedCallEmitterBox { target: CallTarget, args: Vec, ) -> Result<(), String> { - // Emit resolve.try for method targets (dev-only; default OFF) let arity_for_try = args.len(); - if let CallTarget::Method { ref box_type, ref method, receiver } = target { - let recv_cls = box_type.clone() + if let CallTarget::Method { + ref box_type, + ref method, + receiver, + } = target + { + let recv_cls = box_type + .clone() .or_else(|| builder.value_origin_newbox.get(&receiver).cloned()) .unwrap_or_default(); // Use indexed candidate lookup (tail → names) @@ -81,22 +92,60 @@ impl UnifiedCallEmitterBox { } // Centralized user-box rewrite for method targets (toString/stringify, equals/1, Known→unique) - if let CallTarget::Method { ref box_type, ref method, receiver } = target { - let class_name_opt = box_type.clone() + if let CallTarget::Method { + ref box_type, + ref method, + receiver, + } = target + { + let class_name_opt = box_type + .clone() .or_else(|| builder.value_origin_newbox.get(&receiver).cloned()) - .or_else(|| builder.value_types.get(&receiver).and_then(|t| if let crate::mir::MirType::Box(b) = t { Some(b.clone()) } else { None })); + .or_else(|| { + builder.value_types.get(&receiver).and_then(|t| { + if let crate::mir::MirType::Box(b) = t { + Some(b.clone()) + } else { + None + } + }) + }); // Early str-like if let Some(res) = crate::mir::builder::rewrite::special::try_early_str_like_to_dst( - builder, dst, receiver, &class_name_opt, method, args.len(), - ) { res?; return Ok(()); } + builder, + dst, + receiver, + &class_name_opt, + method, + args.len(), + ) { + res?; + return Ok(()); + } // equals/1 if let Some(res) = crate::mir::builder::rewrite::special::try_special_equals_to_dst( - builder, dst, receiver, &class_name_opt, method, args.clone(), - ) { res?; return Ok(()); } + builder, + dst, + receiver, + &class_name_opt, + method, + args.clone(), + ) { + res?; + return Ok(()); + } // Known or unique if let Some(res) = crate::mir::builder::rewrite::known::try_known_or_unique_to_dst( - builder, dst, receiver, &class_name_opt, method, args.clone(), - ) { res?; return Ok(()); } + builder, + dst, + receiver, + &class_name_opt, + method, + args.clone(), + ) { + res?; + return Ok(()); + } } // Convert CallTarget to Callee using CalleeResolverBox @@ -105,14 +154,18 @@ impl UnifiedCallEmitterBox { let resolver = super::resolver::CalleeResolverBox::new( &builder.value_origin_newbox, &builder.value_types, - Some(&builder.type_registry), // 🎯 TypeRegistry を渡す + Some(&builder.type_registry), // 🎯 TypeRegistry を渡す ); let mut callee = match resolver.resolve(target.clone()) { Ok(c) => c, Err(e) => { if let CallTarget::Global(ref name) = target { // Try fallback handlers (via CallMaterializerBox) - if let Some(result) = super::materializer::CallMaterializerBox::try_global_fallback_handlers(builder, dst, name, &args)? { + if let Some(result) = + super::materializer::CallMaterializerBox::try_global_fallback_handlers( + builder, dst, name, &args, + )? + { return Ok(result); } } @@ -121,7 +174,9 @@ impl UnifiedCallEmitterBox { }; // Safety: ensure receiver is materialized even after callee conversion (via CallMaterializerBox) - callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee(builder, callee)?; + callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee( + builder, callee, + )?; // Structural guard: prevent static compiler boxes from being called with runtime receivers // 箱理論: CalleeGuardBox による構造的分離 @@ -129,7 +184,13 @@ impl UnifiedCallEmitterBox { callee = guard.apply_static_runtime_guard(callee)?; // Emit resolve.choose for method callee (dev-only; default OFF) - if let Callee::Method { box_name, method, certainty, .. } = &callee { + if let Callee::Method { + box_name, + method, + certainty, + .. + } = &callee + { let chosen = format!("{}.{}{}", box_name, method, format!("/{}", arity_for_try)); let meta = serde_json::json!({ "recv_cls": box_name, @@ -152,11 +213,28 @@ impl UnifiedCallEmitterBox { resolver.validate_args(&callee, &args)?; // Stability guard: decide route via RouterPolicyBox (behavior-preserving rules) - if let Callee::Method { box_name, method, receiver: Some(r), certainty, .. } = &callee { - let route = crate::mir::builder::router::policy::choose_route(box_name, method, *certainty, arity_for_try); + if let Callee::Method { + box_name, + method, + receiver: Some(r), + certainty, + .. + } = &callee + { + let route = crate::mir::builder::router::policy::choose_route( + box_name, + method, + *certainty, + arity_for_try, + ); if let crate::mir::builder::router::policy::Route::BoxCall = route { - if crate::mir::builder::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - eprintln!("[router-guard] {}.{} → BoxCall fallback (recv=%{})", box_name, method, r.0); + if crate::mir::builder::utils::builder_debug_enabled() + || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") + { + eprintln!( + "[router-guard] {}.{} → BoxCall fallback (recv=%{})", + box_name, method, r.0 + ); } let effects = EffectMask::READ.add(Effect::ReadHeap); // Prevent BoxCall helper from bouncing back into emit_unified_call @@ -166,7 +244,8 @@ impl UnifiedCallEmitterBox { // would otherwise choose Unified. let prev_flag = builder.in_unified_boxcall_fallback; builder.in_unified_boxcall_fallback = true; - let res = builder.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); + let res = + builder.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); builder.in_unified_boxcall_fallback = prev_flag; return res; } @@ -175,13 +254,21 @@ impl UnifiedCallEmitterBox { // Finalize operands in current block (EmitGuardBox wrapper) let mut callee = callee; let mut args_local: Vec = args; - crate::mir::builder::emit_guard::finalize_call_operands(builder, &mut callee, &mut args_local); + crate::mir::builder::emit_guard::finalize_call_operands( + builder, + &mut callee, + &mut args_local, + ); // 📦 Hotfix 7: Include receiver in args for Callee::Method // VM's exec_function_inner expects receiver as the first parameter (ValueId(0)) // but finalize_call_operands keeps receiver in Callee, not in args. // We must add it to args_local here so VM can bind it correctly. - if let Callee::Method { receiver: Some(recv), .. } = &callee { + if let Callee::Method { + receiver: Some(recv), + .. + } = &callee + { args_local.insert(0, *recv); } @@ -189,11 +276,21 @@ impl UnifiedCallEmitterBox { let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone()); // Dev trace: show final callee/recv right before emission (guarded) - if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || crate::mir::builder::utils::builder_debug_enabled() { - if let Callee::Method { method, receiver, box_name, .. } = &callee { + if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") + || crate::mir::builder::utils::builder_debug_enabled() + { + if let Callee::Method { + method, + receiver, + box_name, + .. + } = &callee + { if let Some(r) = receiver { - eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}", - builder.current_block, method, r.0, box_name); + eprintln!( + "[vm-call-final] bb={:?} method={} recv=%{} class={}", + builder.current_block, method, r.0, box_name + ); } } } @@ -223,7 +320,11 @@ impl UnifiedCallEmitterBox { // Create a string constant for the function name via NameConstBox let name_const = crate::mir::builder::name_const::make_name_const_result(builder, &name)?; // Allocate a destination if not provided so we can annotate it - let actual_dst = if let Some(d) = dst { d } else { builder.next_value_id() }; + let actual_dst = if let Some(d) = dst { + d + } else { + builder.next_value_id() + }; let mut args = args; crate::mir::builder::ssa::local::finalize_args(builder, &mut args); builder.emit_instruction(MirInstruction::Call { diff --git a/src/mir/builder/context.rs b/src/mir/builder/context.rs index 22bc8d10..20ae59c8 100644 --- a/src/mir/builder/context.rs +++ b/src/mir/builder/context.rs @@ -88,7 +88,8 @@ mod tests { fn test_size_info() { let mut ctx = BoxCompilationContext::new(); ctx.variable_map.insert("a".to_string(), ValueId::new(1)); - ctx.value_origin_newbox.insert(ValueId::new(2), "StringBox".to_string()); + ctx.value_origin_newbox + .insert(ValueId::new(2), "StringBox".to_string()); ctx.value_types.insert(ValueId::new(3), MirType::Integer); let (vars, origins, types) = ctx.size_info(); diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index 3bb385aa..05276fd8 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -148,7 +148,9 @@ impl super::MirBuilder { // Exit block self.start_new_block(exit_block)?; let result = if self.return_deferred_emitted && !cleanup_terminated { - self.emit_instruction(MirInstruction::Return { value: Some(ret_slot) })?; + self.emit_instruction(MirInstruction::Return { + value: Some(ret_slot), + })?; crate::mir::builder::emission::constant::emit_void(self) } else { crate::mir::builder::emission::constant::emit_void(self) diff --git a/src/mir/builder/decls.rs b/src/mir/builder/decls.rs index db615b4b..258f596e 100644 --- a/src/mir/builder/decls.rs +++ b/src/mir/builder/decls.rs @@ -28,26 +28,34 @@ impl super::MirBuilder { self.current_static_box = Some(box_name.clone()); // Look for the main() method let out = if let Some(main_method) = methods.get("main") { - if let ASTNode::FunctionDeclaration { params, body, .. } = main_method { - // Optional: materialize a callable function entry "BoxName.main/N" for harness/PyVM. - // This static entryは通常の VM 実行では使用されず、過去の Hotfix 4 絡みの loop/control-flow - // バグの温床になっていたため、Phase 25.1m では明示トグルが立っている場合だけ生成する。 - if std::env::var("NYASH_BUILD_STATIC_MAIN_ENTRY") - .ok() - .as_deref() - == Some("1") - { - let func_name = format!("{}.{}", box_name, "main"); - eprintln!("[DEBUG] build_static_main_box: Before lower_static_method_as_function"); - eprintln!("[DEBUG] params.len() = {}", params.len()); - eprintln!("[DEBUG] body.len() = {}", body.len()); - eprintln!("[DEBUG] variable_map = {:?}", self.variable_map); - // Note: Metadata clearing is now handled by BoxCompilationContext (箱理論) - // See lifecycle.rs and builder_calls.rs for context swap implementation - let _ = self.lower_static_method_as_function(func_name, params.clone(), body.clone()); - eprintln!("[DEBUG] build_static_main_box: After lower_static_method_as_function"); - eprintln!("[DEBUG] variable_map = {:?}", self.variable_map); - } + if let ASTNode::FunctionDeclaration { params, body, .. } = main_method { + // Optional: materialize a callable function entry "BoxName.main/N" for harness/PyVM. + // This static entryは通常の VM 実行では使用されず、過去の Hotfix 4 絡みの loop/control-flow + // バグの温床になっていたため、Phase 25.1m では明示トグルが立っている場合だけ生成する。 + if std::env::var("NYASH_BUILD_STATIC_MAIN_ENTRY") + .ok() + .as_deref() + == Some("1") + { + let func_name = format!("{}.{}", box_name, "main"); + eprintln!( + "[DEBUG] build_static_main_box: Before lower_static_method_as_function" + ); + eprintln!("[DEBUG] params.len() = {}", params.len()); + eprintln!("[DEBUG] body.len() = {}", body.len()); + eprintln!("[DEBUG] variable_map = {:?}", self.variable_map); + // Note: Metadata clearing is now handled by BoxCompilationContext (箱理論) + // See lifecycle.rs and builder_calls.rs for context swap implementation + let _ = self.lower_static_method_as_function( + func_name, + params.clone(), + body.clone(), + ); + eprintln!( + "[DEBUG] build_static_main_box: After lower_static_method_as_function" + ); + eprintln!("[DEBUG] variable_map = {:?}", self.variable_map); + } // Initialize local variables for Main.main() parameters // Note: These are local variables in the wrapper main() function, NOT parameters let saved_var_map = std::mem::take(&mut self.variable_map); @@ -63,10 +71,8 @@ impl super::MirBuilder { box_type: "ArrayBox".to_string(), args: vec![], })?; - self.value_origin_newbox - .insert(pid, "ArrayBox".to_string()); - self - .value_types + self.value_origin_newbox.insert(pid, "ArrayBox".to_string()); + self.value_types .insert(pid, super::MirType::Box("ArrayBox".to_string())); // Explicitly call birth() to initialize internal state self.emit_instruction(MirInstruction::BoxCall { @@ -79,7 +85,10 @@ impl super::MirBuilder { })?; if let Some(args) = script_args.as_ref() { for arg in args { - let val = crate::mir::builder::emission::constant::emit_string(self, arg.clone()); + let val = crate::mir::builder::emission::constant::emit_string( + self, + arg.clone(), + ); self.emit_instruction(MirInstruction::BoxCall { dst: None, box_val: pid, @@ -131,7 +140,10 @@ impl super::MirBuilder { // Emit field metadata markers for field in fields { - let _field_id = crate::mir::builder::emission::constant::emit_string(self, format!("__field_{}_{}", name, field)); + let _field_id = crate::mir::builder::emission::constant::emit_string( + self, + format!("__field_{}_{}", name, field), + ); } // Record weak fields for this box @@ -161,17 +173,21 @@ impl super::MirBuilder { // Emit markers for declared methods (kept as metadata hints) for (method_name, method_ast) in methods { if let ASTNode::FunctionDeclaration { .. } = method_ast { - let _method_id = crate::mir::builder::emission::constant::emit_string(self, format!("__method_{}_{}", name, method_name)); + let _method_id = crate::mir::builder::emission::constant::emit_string( + self, + format!("__method_{}_{}", name, method_name), + ); // Track unified member getters: __get_ | __get_once_ | __get_birth_ - let kind_and_prop: Option<(super::PropertyKind, String)> = if let Some(rest) = method_name.strip_prefix("__get_once_") { - Some((super::PropertyKind::Once, rest.to_string())) - } else if let Some(rest) = method_name.strip_prefix("__get_birth_") { - Some((super::PropertyKind::BirthOnce, rest.to_string())) - } else if let Some(rest) = method_name.strip_prefix("__get_") { - Some((super::PropertyKind::Computed, rest.to_string())) - } else { - None - }; + let kind_and_prop: Option<(super::PropertyKind, String)> = + if let Some(rest) = method_name.strip_prefix("__get_once_") { + Some((super::PropertyKind::Once, rest.to_string())) + } else if let Some(rest) = method_name.strip_prefix("__get_birth_") { + Some((super::PropertyKind::BirthOnce, rest.to_string())) + } else if let Some(rest) = method_name.strip_prefix("__get_") { + Some((super::PropertyKind::Computed, rest.to_string())) + } else { + None + }; if let Some((k, prop)) = kind_and_prop { use std::collections::HashMap; let entry: &mut HashMap = self diff --git a/src/mir/builder/emission/branch.rs b/src/mir/builder/emission/branch.rs index 9b2b02be..5948653b 100644 --- a/src/mir/builder/emission/branch.rs +++ b/src/mir/builder/emission/branch.rs @@ -1,15 +1,24 @@ //! BranchEmissionBox — 分岐/ジャンプ命令発行の薄いヘルパ(仕様不変) -use crate::mir::{BasicBlockId, MirInstruction}; use crate::mir::builder::MirBuilder; +use crate::mir::{BasicBlockId, MirInstruction}; #[inline] -pub fn emit_conditional(b: &mut MirBuilder, cond: crate::mir::ValueId, then_bb: BasicBlockId, else_bb: BasicBlockId) -> Result<(), String> { +pub fn emit_conditional( + b: &mut MirBuilder, + cond: crate::mir::ValueId, + then_bb: BasicBlockId, + else_bb: BasicBlockId, +) -> Result<(), String> { if let (Some(func), Some(cur_bb)) = (b.current_function.as_mut(), b.current_block) { crate::mir::ssot::cf_common::set_branch(func, cur_bb, cond, then_bb, else_bb); Ok(()) } else { - b.emit_instruction(MirInstruction::Branch { condition: cond, then_bb, else_bb }) + b.emit_instruction(MirInstruction::Branch { + condition: cond, + then_bb, + else_bb, + }) } } diff --git a/src/mir/builder/emission/compare.rs b/src/mir/builder/emission/compare.rs index 0ef363ce..133470e0 100644 --- a/src/mir/builder/emission/compare.rs +++ b/src/mir/builder/emission/compare.rs @@ -1,10 +1,16 @@ //! CompareEmissionBox — 比較命令発行の薄いヘルパ(仕様不変) -use crate::mir::{CompareOp, MirInstruction, MirType, ValueId}; use crate::mir::builder::MirBuilder; +use crate::mir::{CompareOp, MirInstruction, MirType, ValueId}; #[inline] -pub fn emit_to(b: &mut MirBuilder, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) -> Result<(), String> { +pub fn emit_to( + b: &mut MirBuilder, + dst: ValueId, + op: CompareOp, + lhs: ValueId, + rhs: ValueId, +) -> Result<(), String> { if let (Some(func), Some(cur_bb)) = (b.current_function.as_mut(), b.current_block) { crate::mir::ssot::cf_common::emit_compare_func(func, cur_bb, dst, op, lhs, rhs); } else { @@ -18,12 +24,22 @@ pub fn emit_to(b: &mut MirBuilder, dst: ValueId, op: CompareOp, lhs: ValueId, rh // Convenience wrappers (明示関数名が読みやすい箇所用) #[inline] #[allow(dead_code)] -pub fn emit_eq_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> { +pub fn emit_eq_to( + b: &mut MirBuilder, + dst: ValueId, + lhs: ValueId, + rhs: ValueId, +) -> Result<(), String> { emit_to(b, dst, CompareOp::Eq, lhs, rhs) } #[inline] #[allow(dead_code)] -pub fn emit_ne_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> { +pub fn emit_ne_to( + b: &mut MirBuilder, + dst: ValueId, + lhs: ValueId, + rhs: ValueId, +) -> Result<(), String> { emit_to(b, dst, CompareOp::Ne, lhs, rhs) } diff --git a/src/mir/builder/emission/constant.rs b/src/mir/builder/emission/constant.rs index 3947d915..73441110 100644 --- a/src/mir/builder/emission/constant.rs +++ b/src/mir/builder/emission/constant.rs @@ -3,47 +3,65 @@ //! ✅ Phase 25.1b Fix: All constant emission now uses function-local ID generator //! when inside a function context to ensure proper SSA verification. -use crate::mir::{ConstValue, MirInstruction, ValueId}; use crate::mir::builder::MirBuilder; +use crate::mir::{ConstValue, MirInstruction, ValueId}; #[inline] pub fn emit_integer(b: &mut MirBuilder, val: i64) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(val) }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::Integer(val), + }); dst } #[inline] pub fn emit_bool(b: &mut MirBuilder, val: bool) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(val) }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::Bool(val), + }); dst } #[inline] pub fn emit_float(b: &mut MirBuilder, val: f64) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Float(val) }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::Float(val), + }); dst } #[inline] pub fn emit_string>(b: &mut MirBuilder, s: S) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::String(s.into()) }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::String(s.into()), + }); dst } #[inline] pub fn emit_null(b: &mut MirBuilder) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Null }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::Null, + }); dst } #[inline] pub fn emit_void(b: &mut MirBuilder) -> ValueId { let dst = b.next_value_id(); - let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Void }); + let _ = b.emit_instruction(MirInstruction::Const { + dst, + value: ConstValue::Void, + }); dst } diff --git a/src/mir/builder/emission/mod.rs b/src/mir/builder/emission/mod.rs index 601b3423..b27f9edc 100644 --- a/src/mir/builder/emission/mod.rs +++ b/src/mir/builder/emission/mod.rs @@ -3,6 +3,6 @@ //! - compare.rs: Compare命令の薄い発行 //! - branch.rs: Branch/Jump 発行の薄い関数 -pub mod constant; -pub mod compare; pub mod branch; +pub mod compare; +pub mod constant; diff --git a/src/mir/builder/emit_guard/mod.rs b/src/mir/builder/emit_guard/mod.rs index 5ed3a33e..154d0c77 100644 --- a/src/mir/builder/emit_guard/mod.rs +++ b/src/mir/builder/emit_guard/mod.rs @@ -3,7 +3,11 @@ use crate::mir::definitions::call_unified::Callee; use crate::mir::ValueId; /// Finalize call operands (receiver/args) using LocalSSA; thin wrapper to centralize usage. -pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec) { +pub fn finalize_call_operands( + builder: &mut MirBuilder, + callee: &mut Callee, + args: &mut Vec, +) { // Step 1: Receiver materialization (pin slot + LocalSSA) in a dedicated box crate::mir::builder::receiver::finalize_method_receiver(builder, callee); diff --git a/src/mir/builder/exprs.rs b/src/mir/builder/exprs.rs index 7b4c906a..6e3fa490 100644 --- a/src/mir/builder/exprs.rs +++ b/src/mir/builder/exprs.rs @@ -1,6 +1,8 @@ // Expression lowering split from builder.rs to keep files lean use super::{MirInstruction, ValueId}; -use crate::ast::{ASTNode, AssignStmt, ReturnStmt, BinaryExpr, CallExpr, MethodCallExpr, FieldAccessExpr}; +use crate::ast::{ + ASTNode, AssignStmt, BinaryExpr, CallExpr, FieldAccessExpr, MethodCallExpr, ReturnStmt, +}; impl super::MirBuilder { // Main expression dispatcher @@ -16,9 +18,7 @@ impl super::MirBuilder { // Sequentially lower statements and return last value (or Void) self.cf_block(statements) } - ASTNode::Print { expression, .. } => { - self.build_print_statement(*expression) - } + ASTNode::Print { expression, .. } => self.build_print_statement(*expression), ASTNode::If { condition, then_body, @@ -36,13 +36,17 @@ impl super::MirBuilder { }); self.cf_if(*condition, then_node, else_node) } - ASTNode::Loop { condition, body, .. } => { + ASTNode::Loop { + condition, body, .. + } => { if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!("[exprs.rs:35] FIRST Loop pattern matched"); } self.cf_loop(*condition, body) } - ASTNode::While { condition, body, .. } => { + ASTNode::While { + condition, body, .. + } => { // Desugar Stage-3 while into legacy loop(condition) { body } self.cf_loop(*condition, body) } @@ -85,8 +89,17 @@ impl super::MirBuilder { let obj_val = self.build_expression_impl(*m.object.clone())?; let ty = Self::parse_type_name_to_mir(&type_name); let dst = self.next_value_id(); - let op = if m.method == "is" { crate::mir::TypeOpKind::Check } else { crate::mir::TypeOpKind::Cast }; - self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty })?; + let op = if m.method == "is" { + crate::mir::TypeOpKind::Check + } else { + crate::mir::TypeOpKind::Cast + }; + self.emit_instruction(MirInstruction::TypeOp { + dst, + op, + value: obj_val, + ty, + })?; return Ok(dst); } } @@ -106,7 +119,11 @@ impl super::MirBuilder { if let ASTNode::FieldAccess { object, field, .. } = stmt.target.as_ref() { self.build_field_assignment(*object.clone(), field.clone(), *stmt.value.clone()) } else if let ASTNode::Index { target, index, .. } = stmt.target.as_ref() { - self.build_index_assignment(*target.clone(), *index.clone(), *stmt.value.clone()) + self.build_index_assignment( + *target.clone(), + *index.clone(), + *stmt.value.clone(), + ) } else if let ASTNode::Variable { name, .. } = stmt.target.as_ref() { self.build_assignment(name.clone(), *stmt.value.clone()) } else { @@ -280,8 +297,7 @@ impl super::MirBuilder { })?; self.value_origin_newbox .insert(arr_id, "ArrayBox".to_string()); - self - .value_types + self.value_types .insert(arr_id, super::MirType::Box("ArrayBox".to_string())); for e in elements { let v = self.build_expression_impl(e)?; @@ -312,11 +328,9 @@ impl super::MirBuilder { args: vec![], effects: super::EffectMask::MUT, })?; - self - .value_origin_newbox + self.value_origin_newbox .insert(map_id, "MapBox".to_string()); - self - .value_types + self.value_types .insert(map_id, super::MirType::Box("MapBox".to_string())); for (k, expr) in entries { // const string key diff --git a/src/mir/builder/exprs_call.rs b/src/mir/builder/exprs_call.rs index 68e020ec..38f2a82b 100644 --- a/src/mir/builder/exprs_call.rs +++ b/src/mir/builder/exprs_call.rs @@ -32,7 +32,9 @@ impl super::MirBuilder { self.emit_instruction(super::MirInstruction::Call { dst: Some(dst), func: callee_id, - callee: Some(crate::mir::definitions::call_unified::Callee::Value(callee_id)), + callee: Some(crate::mir::definitions::call_unified::Callee::Value( + callee_id, + )), args: arg_ids, effects: super::EffectMask::PURE, })?; diff --git a/src/mir/builder/exprs_peek.rs b/src/mir/builder/exprs_peek.rs index 26f45fe1..9bf654e0 100644 --- a/src/mir/builder/exprs_peek.rs +++ b/src/mir/builder/exprs_peek.rs @@ -69,16 +69,35 @@ impl super::MirBuilder { // In current dispatch block, compare and branch self.start_new_block(cur_dispatch)?; let lit_id = match label { - LiteralValue::String(s) => crate::mir::builder::emission::constant::emit_string(self, s), - LiteralValue::Integer(i) => crate::mir::builder::emission::constant::emit_integer(self, i), - LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b), - LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f), + LiteralValue::String(s) => { + crate::mir::builder::emission::constant::emit_string(self, s) + } + LiteralValue::Integer(i) => { + crate::mir::builder::emission::constant::emit_integer(self, i) + } + LiteralValue::Bool(b) => { + crate::mir::builder::emission::constant::emit_bool(self, b) + } + LiteralValue::Float(f) => { + crate::mir::builder::emission::constant::emit_float(self, f) + } LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self), LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self), }; let cond_id = self.next_value_id(); - crate::mir::builder::emission::compare::emit_to(self, cond_id, super::CompareOp::Eq, scr_val, lit_id)?; - crate::mir::builder::emission::branch::emit_conditional(self, cond_id, then_block, else_target)?; + crate::mir::builder::emission::compare::emit_to( + self, + cond_id, + super::CompareOp::Eq, + scr_val, + lit_id, + )?; + crate::mir::builder::emission::branch::emit_conditional( + self, + cond_id, + then_block, + else_target, + )?; // then arm self.start_new_block(then_block)?; @@ -102,7 +121,10 @@ impl super::MirBuilder { if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { crate::mir::ssot::cf_common::insert_phi_at_head(func, cur_bb, result_val, phi_inputs); } else { - self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?; + self.emit_instruction(super::MirInstruction::Phi { + dst: result_val, + inputs: phi_inputs, + })?; } Ok(result_val) } diff --git a/src/mir/builder/exprs_qmark.rs b/src/mir/builder/exprs_qmark.rs index 250eeaab..03b79c45 100644 --- a/src/mir/builder/exprs_qmark.rs +++ b/src/mir/builder/exprs_qmark.rs @@ -21,7 +21,9 @@ impl super::MirBuilder { let then_block = self.block_gen.next(); let else_block = self.block_gen.next(); let ok_local = self.local_ssa_ensure(ok_id, 4); - crate::mir::builder::emission::branch::emit_conditional(self, ok_local, then_block, else_block)?; + crate::mir::builder::emission::branch::emit_conditional( + self, ok_local, then_block, else_block, + )?; self.start_new_block(then_block)?; self.emit_instruction(super::MirInstruction::Return { value: Some(res_local), diff --git a/src/mir/builder/fields.rs b/src/mir/builder/fields.rs index 4a7b214c..46ca6046 100644 --- a/src/mir/builder/fields.rs +++ b/src/mir/builder/fields.rs @@ -30,11 +30,16 @@ impl super::MirBuilder { } // Emit: field name const (boxed) - let field_name_id = crate::mir::builder::emission::constant::emit_string(self, field.clone()); + let field_name_id = + crate::mir::builder::emission::constant::emit_string(self, field.clone()); // Finalize operands (base + args) in current block let mut base = object_value; let mut args_vec = vec![field_name_id]; - crate::mir::builder::ssa::local::finalize_field_base_and_args(self, &mut base, &mut args_vec); + crate::mir::builder::ssa::local::finalize_field_base_and_args( + self, + &mut base, + &mut args_vec, + ); // BoxCall: getField(name) let field_val = self.next_value_id(); self.emit_instruction(MirInstruction::BoxCall { @@ -60,8 +65,13 @@ impl super::MirBuilder { .get(&(base_cls.clone(), field.clone())) .cloned() { - if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - super::utils::builder_debug_log(&format!("field-origin hit by box-level map: base={} .{} -> {}", base_cls, field, fcls)); + if super::utils::builder_debug_enabled() + || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") + { + super::utils::builder_debug_log(&format!( + "field-origin hit by box-level map: base={} .{} -> {}", + base_cls, field, fcls + )); } self.value_origin_newbox.insert(field_val, fcls); } @@ -127,11 +137,16 @@ impl super::MirBuilder { } // Emit: field name const - let field_name_id = crate::mir::builder::emission::constant::emit_string(self, field.clone()); + let field_name_id = + crate::mir::builder::emission::constant::emit_string(self, field.clone()); // Finalize operands (base + args) in current block let mut base = object_value; let mut args_vec = vec![field_name_id, value_result]; - crate::mir::builder::ssa::local::finalize_field_base_and_args(self, &mut base, &mut args_vec); + crate::mir::builder::ssa::local::finalize_field_base_and_args( + self, + &mut base, + &mut args_vec, + ); // Set the field via BoxCall: setField(name, value) self.emit_instruction(MirInstruction::BoxCall { dst: None, diff --git a/src/mir/builder/if_form.rs b/src/mir/builder/if_form.rs index 86f63495..6a6f7b2a 100644 --- a/src/mir/builder/if_form.rs +++ b/src/mir/builder/if_form.rs @@ -1,6 +1,6 @@ use super::{MirBuilder, ValueId}; -use crate::mir::loop_api::LoopBuilderApi; // for current_block() use crate::ast::{ASTNode, BinaryOperator}; +use crate::mir::loop_api::LoopBuilderApi; // for current_block() impl MirBuilder { /// Lower an if/else using a structured IfForm (header→then/else→merge). @@ -17,25 +17,31 @@ impl MirBuilder { // so that subsequent branches can safely reuse these values across blocks. // This leverages existing variable_map merges (PHI) at the merge block. if crate::config::env::mir_pre_pin_compare_operands() { - if let ASTNode::BinaryOp { operator, left, right, .. } = &condition { - match operator { - BinaryOperator::Equal - | BinaryOperator::NotEqual - | BinaryOperator::Less - | BinaryOperator::LessEqual - | BinaryOperator::Greater - | BinaryOperator::GreaterEqual => { - if let Ok(lhs_v) = self.build_expression((**left).clone()) { - let _ = self.pin_to_slot(lhs_v, "@if_lhs"); - } - if let Ok(rhs_v) = self.build_expression((**right).clone()) { - let _ = self.pin_to_slot(rhs_v, "@if_rhs"); + if let ASTNode::BinaryOp { + operator, + left, + right, + .. + } = &condition + { + match operator { + BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::Less + | BinaryOperator::LessEqual + | BinaryOperator::Greater + | BinaryOperator::GreaterEqual => { + if let Ok(lhs_v) = self.build_expression((**left).clone()) { + let _ = self.pin_to_slot(lhs_v, "@if_lhs"); + } + if let Ok(rhs_v) = self.build_expression((**right).clone()) { + let _ = self.pin_to_slot(rhs_v, "@if_rhs"); + } } + _ => {} } - _ => {} } } - } let condition_val = self.build_expression(condition)?; let condition_val = self.local_cond(condition_val); @@ -49,7 +55,12 @@ impl MirBuilder { let pre_branch_bb = self.current_block()?; let mut condition_val = condition_val; crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut condition_val); - crate::mir::builder::emission::branch::emit_conditional(self, condition_val, then_block, else_block)?; + crate::mir::builder::emission::branch::emit_conditional( + self, + condition_val, + then_block, + else_block, + )?; // Snapshot variables before entering branches let pre_if_var_map = self.variable_map.clone(); @@ -93,40 +104,41 @@ impl MirBuilder { self.debug_push_region(format!("join#{}", join_id) + "/else"); // Scope enter for else-branch self.hint_scope_enter(0); - let (else_value_raw, else_ast_for_analysis, else_var_map_end_opt) = if let Some(else_ast) = else_branch { - // Reset variable_map BEFORE materializing PHI nodes (same pattern as then-branch) - self.variable_map = pre_if_var_map.clone(); - // Materialize all variables at block entry via single-pred Phi (correctness-first) - for (name, &pre_v) in pre_if_var_map.iter() { - let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?; - self.variable_map.insert(name.clone(), phi_val); - if trace_if { - eprintln!( - "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", - name, pre_v, phi_val - ); + let (else_value_raw, else_ast_for_analysis, else_var_map_end_opt) = + if let Some(else_ast) = else_branch { + // Reset variable_map BEFORE materializing PHI nodes (same pattern as then-branch) + self.variable_map = pre_if_var_map.clone(); + // Materialize all variables at block entry via single-pred Phi (correctness-first) + for (name, &pre_v) in pre_if_var_map.iter() { + let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?; + self.variable_map.insert(name.clone(), phi_val); + if trace_if { + eprintln!( + "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", + name, pre_v, phi_val + ); + } } - } - let val = self.build_expression(else_ast.clone())?; - (val, Some(else_ast), Some(self.variable_map.clone())) - } else { - // No else branch: materialize PHI nodes for the empty else block - self.variable_map = pre_if_var_map.clone(); - for (name, &pre_v) in pre_if_var_map.iter() { - let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?; - self.variable_map.insert(name.clone(), phi_val); - if trace_if { - eprintln!( - "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", - name, pre_v, phi_val - ); + let val = self.build_expression(else_ast.clone())?; + (val, Some(else_ast), Some(self.variable_map.clone())) + } else { + // No else branch: materialize PHI nodes for the empty else block + self.variable_map = pre_if_var_map.clone(); + for (name, &pre_v) in pre_if_var_map.iter() { + let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?; + self.variable_map.insert(name.clone(), phi_val); + if trace_if { + eprintln!( + "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", + name, pre_v, phi_val + ); + } } - } - let void_val = crate::mir::builder::emission::constant::emit_void(self); - // Phase 25.1c/k: Pass PHI-renamed variable_map for empty else branch - // This ensures merge_modified_vars uses correct ValueIds after PHI renaming - (void_val, None, Some(self.variable_map.clone())) - }; + let void_val = crate::mir::builder::emission::constant::emit_void(self); + // Phase 25.1c/k: Pass PHI-renamed variable_map for empty else branch + // This ensures merge_modified_vars uses correct ValueIds after PHI renaming + (void_val, None, Some(self.variable_map.clone())) + }; let else_exit_block = self.current_block()?; let else_reaches_merge = !self.is_current_block_terminated(); if else_reaches_merge { @@ -146,7 +158,8 @@ impl MirBuilder { self.push_if_merge(merge_block); // Pre-analysis: identify then/else assigned var for skip and hints - let assigned_then_pre = crate::mir::phi_core::if_phi::extract_assigned_var(&then_ast_for_analysis); + let assigned_then_pre = + crate::mir::phi_core::if_phi::extract_assigned_var(&then_ast_for_analysis); let assigned_else_pre = else_ast_for_analysis .as_ref() .and_then(|e| crate::mir::phi_core::if_phi::extract_assigned_var(e)); @@ -157,8 +170,16 @@ impl MirBuilder { let result_val = self.normalize_if_else_phi( then_block, else_block, - if then_reaches_merge { Some(then_exit_block) } else { None }, - if else_reaches_merge { Some(else_exit_block) } else { None }, + if then_reaches_merge { + Some(then_exit_block) + } else { + None + }, + if else_reaches_merge { + Some(else_exit_block) + } else { + None + }, then_value_raw, else_value_raw, &pre_if_var_map, @@ -172,12 +193,16 @@ impl MirBuilder { // Hint: join result variable(s) // 1) Primary: if both branches assign to the same variable name, emit a hint for that name if let (Some(tn), Some(en)) = (assigned_then_pre.as_deref(), assigned_else_pre.as_deref()) { - if tn == en { self.hint_join_result(tn); } + if tn == en { + self.hint_join_result(tn); + } } // 2) Secondary: if both branches assign multiple variables, hint全件(制限なし) if let Some(ref else_map_end) = else_var_map_end_opt { for name in then_var_map_end.keys() { - if Some(name.as_str()) == assigned_then_pre.as_deref() { continue; } + if Some(name.as_str()) == assigned_then_pre.as_deref() { + continue; + } if else_map_end.contains_key(name) { self.hint_join_result(name.as_str()); } @@ -189,8 +214,16 @@ impl MirBuilder { self.merge_modified_vars( then_block, else_block, - if then_reaches_merge { Some(then_exit_block) } else { None }, - if else_reaches_merge { Some(else_exit_block) } else { None }, + if then_reaches_merge { + Some(then_exit_block) + } else { + None + }, + if else_reaches_merge { + Some(else_exit_block) + } else { + None + }, &pre_if_var_map, &then_var_map_end, &else_var_map_end_opt, diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index fe3ca58c..095ad2b6 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -1,4 +1,7 @@ -use super::{EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, BasicBlockId}; +use super::{ + BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, + ValueId, +}; use crate::ast::ASTNode; // Lifecycle routines extracted from builder.rs @@ -6,7 +9,13 @@ fn has_main_static(ast: &ASTNode) -> bool { use crate::ast::ASTNode as N; if let N::Program { statements, .. } = ast { for st in statements { - if let N::BoxDeclaration { name, methods, is_static, .. } = st { + if let N::BoxDeclaration { + name, + methods, + is_static, + .. + } = st + { if *is_static && name == "Main" { if let Some(m) = methods.get("main") { if let N::FunctionDeclaration { .. } = m { @@ -31,7 +40,12 @@ impl super::MirBuilder { self.index_declarations(st); } } - ASTNode::BoxDeclaration { name, methods, is_static, .. } => { + ASTNode::BoxDeclaration { + name, + methods, + is_static, + .. + } => { if !*is_static { self.user_defined_boxes.insert(name.clone()); } else { @@ -106,7 +120,8 @@ impl super::MirBuilder { ASTNode::Program { statements, .. } => { use crate::ast::ASTNode as N; // First pass: lower declarations (static boxes except Main, and instance boxes) - let mut main_static: Option<(String, std::collections::HashMap)> = None; + let mut main_static: Option<(String, std::collections::HashMap)> = + None; for st in &statements { if let N::BoxDeclaration { name, @@ -135,9 +150,20 @@ impl super::MirBuilder { // Lower all static methods into standalone functions: BoxName.method/Arity for (mname, mast) in methods.iter() { - if let N::FunctionDeclaration { params, body, .. } = mast { - let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len())); - self.lower_static_method_as_function(func_name, params.clone(), body.clone())?; + if let N::FunctionDeclaration { params, body, .. } = + mast + { + let func_name = format!( + "{}.{}{}", + name, + mname, + format!("/{}", params.len()) + ); + self.lower_static_method_as_function( + func_name, + params.clone(), + body.clone(), + )?; self.static_method_index .entry(mname.clone()) .or_insert_with(Vec::new) @@ -174,9 +200,20 @@ impl super::MirBuilder { } } for (mname, mast) in methods.iter() { - if let N::FunctionDeclaration { params, body, is_static, .. } = mast { + if let N::FunctionDeclaration { + params, + body, + is_static, + .. + } = mast + { if !*is_static { - let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len())); + let func_name = format!( + "{}.{}{}", + name, + mname, + format!("/{}", params.len()) + ); self.lower_method_as_function( func_name, name.clone(), @@ -208,10 +245,7 @@ impl super::MirBuilder { } } - pub(super) fn finalize_module( - &mut self, - result_value: ValueId, - ) -> Result { + pub(super) fn finalize_module(&mut self, result_value: ValueId) -> Result { // Hint: scope leave at function end (id=0 for main) self.hint_scope_leave(0); if let Some(block_id) = self.current_block { @@ -280,7 +314,12 @@ impl super::MirBuilder { let insns = &bb.instructions; let mut idx = 0usize; while idx < insns.len() { - if let MirInstruction::NewBox { dst, box_type, args } = &insns[idx] { + if let MirInstruction::NewBox { + dst, + box_type, + args, + } = &insns[idx] + { // Skip StringBox (literal optimization path) if box_type != "StringBox" { let expect_tail = format!("{}.birth/{}", box_type, args.len()); @@ -290,16 +329,26 @@ impl super::MirBuilder { let mut last_const_name: Option = None; while j < insns.len() && j <= idx + 3 { match &insns[j] { - MirInstruction::BoxCall { box_val, method, .. } => { - if method == "birth" && box_val == dst { ok = true; break; } + MirInstruction::BoxCall { + box_val, method, .. + } => { + if method == "birth" && box_val == dst { + ok = true; + break; + } } MirInstruction::Const { value, .. } => { - if let super::ConstValue::String(s) = value { last_const_name = Some(s.clone()); } + if let super::ConstValue::String(s) = value { + last_const_name = Some(s.clone()); + } } MirInstruction::Call { func: _, .. } => { // If immediately preceded by matching Const String, accept if let Some(prev) = last_const_name.as_ref() { - if prev == &expect_tail { ok = true; break; } + if prev == &expect_tail { + ok = true; + break; + } } // Heuristic: in some forms, builder may reuse a shared const; best-effort only } @@ -317,7 +366,10 @@ impl super::MirBuilder { } } if warn_count > 0 { - eprintln!("[warn] dev verify: NewBox→birth invariant warnings: {}", warn_count); + eprintln!( + "[warn] dev verify: NewBox→birth invariant warnings: {}", + warn_count + ); } } diff --git a/src/mir/builder/loops.rs b/src/mir/builder/loops.rs index fa3de464..bd6f42dd 100644 --- a/src/mir/builder/loops.rs +++ b/src/mir/builder/loops.rs @@ -62,7 +62,10 @@ pub(crate) fn add_predecessor( bb.add_predecessor(pred); return Ok(()); } - return Err(format!("Block {} not found (impossible after auto-create)", block)); + return Err(format!( + "Block {} not found (impossible after auto-create)", + block + )); } Err("No current function".to_string()) } diff --git a/src/mir/builder/metadata/mod.rs b/src/mir/builder/metadata/mod.rs index 1c8f999a..c2b8b136 100644 --- a/src/mir/builder/metadata/mod.rs +++ b/src/mir/builder/metadata/mod.rs @@ -2,4 +2,3 @@ //! - value_types と value_origin_newbox の伝播を一箇所に集約する。 pub mod propagate; - diff --git a/src/mir/builder/metadata/propagate.rs b/src/mir/builder/metadata/propagate.rs index be136824..d6b66128 100644 --- a/src/mir/builder/metadata/propagate.rs +++ b/src/mir/builder/metadata/propagate.rs @@ -4,16 +4,14 @@ //! 🎯 箱理論: TypeRegistryBox 統合対応 //! NYASH_USE_TYPE_REGISTRY=1 で TypeRegistry 経由に切り替え(段階的移行) -use crate::mir::{MirType, ValueId}; use crate::mir::builder::MirBuilder; +use crate::mir::{MirType, ValueId}; /// src から dst へ builder 内メタデータ(value_types / value_origin_newbox)を伝播する。 /// 🎯 TypeRegistry 経由モード対応(NYASH_USE_TYPE_REGISTRY=1) #[inline] pub fn propagate(builder: &mut MirBuilder, src: ValueId, dst: ValueId) { - let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") - .ok() - .as_deref() == Some("1"); + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY").ok().as_deref() == Some("1"); if use_registry { // 🎯 新: TypeRegistry 経由(トレース可能) @@ -34,9 +32,7 @@ pub fn propagate(builder: &mut MirBuilder, src: ValueId, dst: ValueId) { #[inline] #[allow(dead_code)] pub fn propagate_with_override(builder: &mut MirBuilder, dst: ValueId, ty: MirType) { - let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY") - .ok() - .as_deref() == Some("1"); + let use_registry = std::env::var("NYASH_USE_TYPE_REGISTRY").ok().as_deref() == Some("1"); if use_registry { // 🎯 新: TypeRegistry 経由 diff --git a/src/mir/builder/method_call_handlers.rs b/src/mir/builder/method_call_handlers.rs index 97082604..829a1083 100644 --- a/src/mir/builder/method_call_handlers.rs +++ b/src/mir/builder/method_call_handlers.rs @@ -4,10 +4,10 @@ //! following the Single Responsibility Principle. use crate::ast::ASTNode; -use crate::mir::builder::{MirBuilder, ValueId}; use crate::mir::builder::builder_calls::CallTarget; -use crate::mir::{MirInstruction, TypeOpKind}; use crate::mir::builder::calls::function_lowering; +use crate::mir::builder::{MirBuilder, ValueId}; +use crate::mir::{MirInstruction, TypeOpKind}; /// Me-call 専用のポリシー箱。 /// @@ -34,8 +34,7 @@ impl MeCallPolicyBox { arg_values.push(builder.build_expression(a.clone())?); } let arity = arg_values.len(); - let fname = - function_lowering::generate_method_function_name(cls, method, arity); + let fname = function_lowering::generate_method_function_name(cls, method, arity); let exists = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { @@ -63,8 +62,7 @@ impl MeCallPolicyBox { // static box 文脈では実体インスタンスが存在しないため UndefinedValue を生みやすい。 // - ここでは receiver を持たない Global call に揃え、FuncScannerBox などの // static ヘルパー呼び出しを安全に扱う。 - let static_dst = - builder.handle_static_method_call(cls, method, arguments)?; + let static_dst = builder.handle_static_method_call(cls, method, arguments)?; return Ok(Some(static_dst)); } @@ -161,7 +159,11 @@ impl MirBuilder { let dst = self.next_value_id(); self.emit_unified_call( Some(dst), - CallTarget::Method { box_type: None, method, receiver: object_value }, + CallTarget::Method { + box_type: None, + method, + receiver: object_value, + }, arg_values, )?; Ok(dst) diff --git a/src/mir/builder/name_const.rs b/src/mir/builder/name_const.rs index adf313fb..0815bcc0 100644 --- a/src/mir/builder/name_const.rs +++ b/src/mir/builder/name_const.rs @@ -1,5 +1,5 @@ -use crate::mir::ValueId; use crate::mir::builder::MirBuilder; +use crate::mir::ValueId; /// Emit a string Const (function name const) and return its ValueId. /// Behavior-preserving wrapper around Const emission with String value. diff --git a/src/mir/builder/observe/mod.rs b/src/mir/builder/observe/mod.rs index ddd77f18..01e68b10 100644 --- a/src/mir/builder/observe/mod.rs +++ b/src/mir/builder/observe/mod.rs @@ -3,6 +3,5 @@ //! - ssa: PHI/SSA related debug emissions //! - resolve: method resolution try/choose(既存呼び出しの置換は段階的に) -pub mod ssa; pub mod resolve; - +pub mod ssa; diff --git a/src/mir/builder/observe/resolve.rs b/src/mir/builder/observe/resolve.rs index a27febbd..fb4550e0 100644 --- a/src/mir/builder/observe/resolve.rs +++ b/src/mir/builder/observe/resolve.rs @@ -23,14 +23,20 @@ fn sample_every() -> usize { /// Dev‑only: emit a resolve.try event(candidates inspection)。 pub(crate) fn emit_try(builder: &MirBuilder, meta: serde_json::Value) { - let fn_name = builder.current_function.as_ref().map(|f| f.signature.name.as_str()); + let fn_name = builder + .current_function + .as_ref() + .map(|f| f.signature.name.as_str()); let region = builder.debug_current_region_id(); crate::debug::hub::emit("resolve", "try", fn_name, region.as_deref(), meta); } /// Dev‑only: emit a resolve.choose event(decision)。 pub(crate) fn emit_choose(builder: &MirBuilder, meta: serde_json::Value) { - let fn_name = builder.current_function.as_ref().map(|f| f.signature.name.as_str()); + let fn_name = builder + .current_function + .as_ref() + .map(|f| f.signature.name.as_str()); let region = builder.debug_current_region_id(); // KPI (dev-only) record_kpi(&meta); @@ -40,7 +46,9 @@ pub(crate) fn emit_choose(builder: &MirBuilder, meta: serde_json::Value) { /// Internal: Call from emit_choose wrapper to record KPI if enabled. #[allow(dead_code)] fn record_kpi(meta: &serde_json::Value) { - if !kpi_enabled() { return; } + if !kpi_enabled() { + return; + } let total = TOTAL_CHOOSE.fetch_add(1, Ordering::Relaxed) + 1; let certainty = meta.get("certainty").and_then(|v| v.as_str()).unwrap_or(""); if certainty == "Known" { @@ -49,7 +57,14 @@ fn record_kpi(meta: &serde_json::Value) { let n = sample_every(); if n > 0 && total % n == 0 { let known = KNOWN_CHOOSE.load(Ordering::Relaxed); - let rate = if total > 0 { (known as f64) * 100.0 / (total as f64) } else { 0.0 }; - eprintln!("[NYASH-KPI] resolve.choose Known={} Total={} ({:.1}%)", known, total, rate); + let rate = if total > 0 { + (known as f64) * 100.0 / (total as f64) + } else { + 0.0 + }; + eprintln!( + "[NYASH-KPI] resolve.choose Known={} Total={} ({:.1}%)", + known, total, rate + ); } } diff --git a/src/mir/builder/ops.rs b/src/mir/builder/ops.rs index 6e4c0b96..4c1ba73a 100644 --- a/src/mir/builder/ops.rs +++ b/src/mir/builder/ops.rs @@ -1,6 +1,6 @@ use super::{MirInstruction, MirType, ValueId}; -use crate::mir::loop_api::LoopBuilderApi; // for current_block() use crate::ast::{ASTNode, BinaryOperator}; +use crate::mir::loop_api::LoopBuilderApi; // for current_block() use crate::mir::{BinaryOp, CompareOp, TypeOpKind, UnaryOp}; // Internal classification for binary operations @@ -38,7 +38,10 @@ impl super::MirBuilder { let mir_op = self.convert_binary_operator(operator)?; - let all_call = std::env::var("NYASH_BUILDER_OPERATOR_BOX_ALL_CALL").ok().as_deref() == Some("1"); + let all_call = std::env::var("NYASH_BUILDER_OPERATOR_BOX_ALL_CALL") + .ok() + .as_deref() + == Some("1"); match mir_op { // Arithmetic operations @@ -51,11 +54,19 @@ impl super::MirBuilder { .unwrap_or(false); if matches!(op, crate::mir::BinaryOp::Add) && !in_add_op - && (all_call || std::env::var("NYASH_BUILDER_OPERATOR_BOX_ADD_CALL").ok().as_deref() == Some("1")) + && (all_call + || std::env::var("NYASH_BUILDER_OPERATOR_BOX_ADD_CALL") + .ok() + .as_deref() + == Some("1")) { // AddOperator.apply/2(lhs, rhs) let name = "AddOperator.apply/2".to_string(); - self.emit_legacy_call(Some(dst), super::builder_calls::CallTarget::Global(name), vec![lhs, rhs])?; + self.emit_legacy_call( + Some(dst), + super::builder_calls::CallTarget::Global(name), + vec![lhs, rhs], + )?; // 型注釈(従来と同等) let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, @@ -81,21 +92,39 @@ impl super::MirBuilder { crate::mir::BinaryOp::Mod => ("ModOperator.apply/2", "ModOperator.apply/"), crate::mir::BinaryOp::Shl => ("ShlOperator.apply/2", "ShlOperator.apply/"), crate::mir::BinaryOp::Shr => ("ShrOperator.apply/2", "ShrOperator.apply/"), - crate::mir::BinaryOp::BitAnd => ("BitAndOperator.apply/2", "BitAndOperator.apply/"), - crate::mir::BinaryOp::BitOr => ("BitOrOperator.apply/2", "BitOrOperator.apply/"), - crate::mir::BinaryOp::BitXor => ("BitXorOperator.apply/2", "BitXorOperator.apply/"), + crate::mir::BinaryOp::BitAnd => { + ("BitAndOperator.apply/2", "BitAndOperator.apply/") + } + crate::mir::BinaryOp::BitOr => { + ("BitOrOperator.apply/2", "BitOrOperator.apply/") + } + crate::mir::BinaryOp::BitXor => { + ("BitXorOperator.apply/2", "BitXorOperator.apply/") + } _ => ("", ""), }; if !name.is_empty() { - let in_guard = self.current_function.as_ref().map(|f| f.signature.name.starts_with(guard_prefix)).unwrap_or(false); + let in_guard = self + .current_function + .as_ref() + .map(|f| f.signature.name.starts_with(guard_prefix)) + .unwrap_or(false); if !in_guard { - self.emit_legacy_call(Some(dst), super::builder_calls::CallTarget::Global(name.to_string()), vec![lhs, rhs])?; + self.emit_legacy_call( + Some(dst), + super::builder_calls::CallTarget::Global(name.to_string()), + vec![lhs, rhs], + )?; // 型注釈: 算術はおおむね整数(Addは上で注釈済み) self.value_types.insert(dst, MirType::Integer); } else { // guard中は従来のBinOp - if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { - crate::mir::ssot::binop_lower::emit_binop_to_dst(func, cur_bb, dst, op, lhs, rhs); + if let (Some(func), Some(cur_bb)) = + (self.current_function.as_mut(), self.current_block) + { + crate::mir::ssot::binop_lower::emit_binop_to_dst( + func, cur_bb, dst, op, lhs, rhs, + ); } else { self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?; } @@ -103,8 +132,12 @@ impl super::MirBuilder { } } else { // 既存の算術経路 - if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { - crate::mir::ssot::binop_lower::emit_binop_to_dst(func, cur_bb, dst, op, lhs, rhs); + if let (Some(func), Some(cur_bb)) = + (self.current_function.as_mut(), self.current_block) + { + crate::mir::ssot::binop_lower::emit_binop_to_dst( + func, cur_bb, dst, op, lhs, rhs, + ); } else { self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?; } @@ -130,8 +163,12 @@ impl super::MirBuilder { } } else { // 既存の算術経路 - if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { - crate::mir::ssot::binop_lower::emit_binop_to_dst(func, cur_bb, dst, op, lhs, rhs); + if let (Some(func), Some(cur_bb)) = + (self.current_function.as_mut(), self.current_block) + { + crate::mir::ssot::binop_lower::emit_binop_to_dst( + func, cur_bb, dst, op, lhs, rhs, + ); } else { self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?; } @@ -165,7 +202,12 @@ impl super::MirBuilder { .map(|f| f.signature.name.starts_with("CompareOperator.apply/")) .unwrap_or(false); if !in_cmp_op - && (all_call || std::env::var("NYASH_BUILDER_OPERATOR_BOX_COMPARE_CALL").ok().as_deref() == Some("1")) { + && (all_call + || std::env::var("NYASH_BUILDER_OPERATOR_BOX_COMPARE_CALL") + .ok() + .as_deref() + == Some("1")) + { // op名の文字列化 let opname = match op { CompareOp::Eq => "Eq", @@ -175,10 +217,15 @@ impl super::MirBuilder { CompareOp::Gt => "Gt", CompareOp::Ge => "Ge", }; - let op_const = crate::mir::builder::emission::constant::emit_string(self, opname); + let op_const = + crate::mir::builder::emission::constant::emit_string(self, opname); // そのまま値を渡す(型変換/slot化は演算子内orVMで行う) let name = "CompareOperator.apply/3".to_string(); - self.emit_legacy_call(Some(dst), super::builder_calls::CallTarget::Global(name), vec![op_const, lhs, rhs])?; + self.emit_legacy_call( + Some(dst), + super::builder_calls::CallTarget::Global(name), + vec![op_const, lhs, rhs], + )?; self.value_types.insert(dst, MirType::Bool); } else { // 既存の比較経路(安全のための型注釈/slot化含む) @@ -245,7 +292,9 @@ impl super::MirBuilder { // Branch on LHS truthiness (runtime to_bool semantics in interpreter/LLVM) let mut lhs_cond = self.local_cond(lhs_val); crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut lhs_cond); - crate::mir::builder::emission::branch::emit_conditional(self, lhs_cond, then_block, else_block)?; + crate::mir::builder::emission::branch::emit_conditional( + self, lhs_cond, then_block, else_block, + )?; // Record predecessor block for branch (for single-pred PHI materialization) let pre_branch_bb = self.current_block()?; @@ -273,7 +322,9 @@ impl super::MirBuilder { let rhs_val = self.build_expression(right.clone())?; let mut rhs_cond = self.local_cond(rhs_val); crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut rhs_cond); - crate::mir::builder::emission::branch::emit_conditional(self, rhs_cond, rhs_true, rhs_false)?; + crate::mir::builder::emission::branch::emit_conditional( + self, rhs_cond, rhs_true, rhs_false, + )?; // true path self.start_new_block(rhs_true)?; let t_id = crate::mir::builder::emission::constant::emit_bool(self, true); @@ -286,7 +337,9 @@ impl super::MirBuilder { let rhs_false_exit = self.current_block()?; // join rhs result into a single bool self.start_new_block(rhs_join)?; - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } let rhs_bool = self.insert_phi_binary(rhs_true_exit, t_id, rhs_false_exit, f_id)?; self.value_types.insert(rhs_bool, MirType::Bool); rhs_bool @@ -323,7 +376,9 @@ impl super::MirBuilder { let rhs_val = self.build_expression(right)?; let mut rhs_cond = self.local_cond(rhs_val); crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut rhs_cond); - crate::mir::builder::emission::branch::emit_conditional(self, rhs_cond, rhs_true, rhs_false)?; + crate::mir::builder::emission::branch::emit_conditional( + self, rhs_cond, rhs_true, rhs_false, + )?; // true path self.start_new_block(rhs_true)?; let t_id = crate::mir::builder::emission::constant::emit_bool(self, true); @@ -336,7 +391,9 @@ impl super::MirBuilder { let rhs_false_exit = self.current_block()?; // join rhs result into a single bool self.start_new_block(rhs_join)?; - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } let rhs_bool = self.insert_phi_binary(rhs_true_exit, t_id, rhs_false_exit, f_id)?; self.value_types.insert(rhs_bool, MirType::Bool); rhs_bool @@ -357,10 +414,16 @@ impl super::MirBuilder { // Result PHI (bool) — consider only reachable predecessors let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new(); - if then_reaches_merge { inputs.push((then_exit_block, then_value_raw)); } - if else_reaches_merge { inputs.push((else_exit_block, else_value_raw)); } + if then_reaches_merge { + inputs.push((then_exit_block, then_value_raw)); + } + if else_reaches_merge { + inputs.push((else_exit_block, else_value_raw)); + } let result_val = if inputs.len() >= 2 { - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } let dst = self.insert_phi(inputs)?; self.value_types.insert(dst, MirType::Bool); dst @@ -375,8 +438,16 @@ impl super::MirBuilder { self.merge_modified_vars( then_block, else_block, - if then_reaches_merge { Some(then_exit_block) } else { None }, - if else_reaches_merge { Some(else_exit_block) } else { None }, + if then_reaches_merge { + Some(then_exit_block) + } else { + None + }, + if else_reaches_merge { + Some(else_exit_block) + } else { + None + }, &pre_if_var_map, &then_var_map_end, &Some(else_var_map_end), @@ -394,19 +465,38 @@ impl super::MirBuilder { operand: ASTNode, ) -> Result { let operand_val = self.build_expression(operand)?; - let all_call = std::env::var("NYASH_BUILDER_OPERATOR_BOX_ALL_CALL").ok().as_deref() == Some("1"); + let all_call = std::env::var("NYASH_BUILDER_OPERATOR_BOX_ALL_CALL") + .ok() + .as_deref() + == Some("1"); if all_call { let (name, guard_prefix, rett) = match operator.as_str() { - "-" => ("NegOperator.apply/1", "NegOperator.apply/", MirType::Integer), + "-" => ( + "NegOperator.apply/1", + "NegOperator.apply/", + MirType::Integer, + ), "!" | "not" => ("NotOperator.apply/1", "NotOperator.apply/", MirType::Bool), - "~" => ("BitNotOperator.apply/1", "BitNotOperator.apply/", MirType::Integer), + "~" => ( + "BitNotOperator.apply/1", + "BitNotOperator.apply/", + MirType::Integer, + ), _ => ("", "", MirType::Integer), }; if !name.is_empty() { - let in_guard = self.current_function.as_ref().map(|f| f.signature.name.starts_with(guard_prefix)).unwrap_or(false); + let in_guard = self + .current_function + .as_ref() + .map(|f| f.signature.name.starts_with(guard_prefix)) + .unwrap_or(false); let dst = self.next_value_id(); if !in_guard { - self.emit_legacy_call(Some(dst), super::builder_calls::CallTarget::Global(name.to_string()), vec![operand_val])?; + self.emit_legacy_call( + Some(dst), + super::builder_calls::CallTarget::Global(name.to_string()), + vec![operand_val], + )?; self.value_types.insert(dst, rett); return Ok(dst); } diff --git a/src/mir/builder/origin/infer.rs b/src/mir/builder/origin/infer.rs index 95677e28..27b7f0b9 100644 --- a/src/mir/builder/origin/infer.rs +++ b/src/mir/builder/origin/infer.rs @@ -7,13 +7,17 @@ use super::super::{MirBuilder, MirType, ValueId}; pub(crate) fn annotate_me_origin(builder: &mut MirBuilder, me_id: ValueId) { let mut cls: Option = None; if let Some(c) = builder.current_static_box.clone() { - if !c.is_empty() { cls = Some(c); } + if !c.is_empty() { + cls = Some(c); + } } if cls.is_none() { if let Some(ref fun) = builder.current_function { if let Some(dot) = fun.signature.name.find('.') { let c = fun.signature.name[..dot].to_string(); - if !c.is_empty() { cls = Some(c); } + if !c.is_empty() { + cls = Some(c); + } } } } diff --git a/src/mir/builder/origin/mod.rs b/src/mir/builder/origin/mod.rs index 864daa18..8ca89d39 100644 --- a/src/mir/builder/origin/mod.rs +++ b/src/mir/builder/origin/mod.rs @@ -10,4 +10,3 @@ pub mod infer; pub mod phi; - diff --git a/src/mir/builder/origin/phi.rs b/src/mir/builder/origin/phi.rs index 0e6b644d..96537de5 100644 --- a/src/mir/builder/origin/phi.rs +++ b/src/mir/builder/origin/phi.rs @@ -1,4 +1,4 @@ -use super::super::{MirBuilder, MirType, ValueId, BasicBlockId}; +use super::super::{BasicBlockId, MirBuilder, MirType, ValueId}; /// Lightweight propagation at PHI when all inputs agree(型/起源)。 /// 仕様は不変: 一致時のみ dst にコピーする(不一致/未知は何もしない)。 @@ -15,13 +15,21 @@ pub(crate) fn propagate_phi_meta( match &common_ty { None => common_ty = Some(t), Some(ct) => { - if ct != &t { ty_agree = false; break; } + if ct != &t { + ty_agree = false; + break; + } } } - } else { ty_agree = false; break; } + } else { + ty_agree = false; + break; + } } if ty_agree { - if let Some(ct) = common_ty { builder.value_types.insert(dst, ct); } + if let Some(ct) = common_ty { + builder.value_types.insert(dst, ct); + } } // Origin一致のときだけコピー let mut common_cls: Option = None; @@ -30,9 +38,21 @@ pub(crate) fn propagate_phi_meta( if let Some(c) = builder.value_origin_newbox.get(v).cloned() { match &common_cls { None => common_cls = Some(c), - Some(cc) => { if cc != &c { cls_agree = false; break; } } + Some(cc) => { + if cc != &c { + cls_agree = false; + break; + } + } } - } else { cls_agree = false; break; } + } else { + cls_agree = false; + break; + } + } + if cls_agree { + if let Some(cc) = common_cls { + builder.value_origin_newbox.insert(dst, cc); + } } - if cls_agree { if let Some(cc) = common_cls { builder.value_origin_newbox.insert(dst, cc); } } } diff --git a/src/mir/builder/phi.rs b/src/mir/builder/phi.rs index 3ec9e4fc..397631cd 100644 --- a/src/mir/builder/phi.rs +++ b/src/mir/builder/phi.rs @@ -31,28 +31,38 @@ impl MirBuilder { let changed_set: HashSet = conservative.changed_vars.iter().cloned().collect(); // Use PhiMergeHelper for unified variable merging - let mut helper = super::phi_merge::PhiMergeHelper::new( - self, - then_exit_block_opt, - else_exit_block_opt, - ); + let mut helper = + super::phi_merge::PhiMergeHelper::new(self, then_exit_block_opt, else_exit_block_opt); helper.merge_all_vars(pre_if_snapshot, then_map_end, else_map_end_opt, skip_var)?; // Ensure pinned synthetic slots ("__pin$...") have a block-local definition at the merge, // even if their values did not change across branches. This avoids undefined uses when // subsequent blocks re-use pinned values without modifications. for (pin_name, pre_val) in pre_if_snapshot.iter() { - if !pin_name.starts_with("__pin$") { continue; } - if skip_var.map(|s| s == pin_name.as_str()).unwrap_or(false) { continue; } - if changed_set.contains(pin_name) { continue; } - let then_v = then_map_end.get(pin_name.as_str()).copied().unwrap_or(*pre_val); + if !pin_name.starts_with("__pin$") { + continue; + } + if skip_var.map(|s| s == pin_name.as_str()).unwrap_or(false) { + continue; + } + if changed_set.contains(pin_name) { + continue; + } + let then_v = then_map_end + .get(pin_name.as_str()) + .copied() + .unwrap_or(*pre_val); let else_v = else_map_end_opt .as_ref() .and_then(|m| m.get(pin_name.as_str()).copied()) .unwrap_or(*pre_val); let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new(); - if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); } - if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); } + if let Some(tp) = then_exit_block_opt { + inputs.push((tp, then_v)); + } + if let Some(ep) = else_exit_block_opt { + inputs.push((ep, else_v)); + } match inputs.len() { 0 => {} 1 => { @@ -60,15 +70,27 @@ impl MirBuilder { self.variable_map.insert(pin_name.clone(), v); } _ => { - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } - if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { - crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } + if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) + { + crate::mir::phi_core::common::debug_verify_phi_inputs( + func, cur_bb, &inputs, + ); } let merged = self.next_value_id(); - if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { - crate::mir::ssot::cf_common::insert_phi_at_head(func, cur_bb, merged, inputs); + if let (Some(func), Some(cur_bb)) = + (self.current_function.as_mut(), self.current_block) + { + crate::mir::ssot::cf_common::insert_phi_at_head( + func, cur_bb, merged, inputs, + ); } else { - self.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?; + self.emit_instruction(MirInstruction::Phi { + dst: merged, + inputs, + })?; } self.variable_map.insert(pin_name.clone(), merged); } @@ -95,7 +117,8 @@ impl MirBuilder { ) -> Result { // If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else // does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value). - let assigned_var_then = crate::mir::phi_core::if_phi::extract_assigned_var(then_ast_for_analysis); + let assigned_var_then = + crate::mir::phi_core::if_phi::extract_assigned_var(then_ast_for_analysis); let assigned_var_else = else_ast_for_analysis .as_ref() .and_then(|a| crate::mir::phi_core::if_phi::extract_assigned_var(a)); @@ -131,8 +154,12 @@ impl MirBuilder { }; // Build inputs from reachable predecessors only let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_for_var)); } - if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_for_var)); } + if let Some(tp) = then_exit_block_opt { + inputs.push((tp, then_value_for_var)); + } + if let Some(ep) = else_exit_block_opt { + inputs.push((ep, else_value_for_var)); + } match inputs.len() { 0 => {} 1 => { @@ -142,9 +169,14 @@ impl MirBuilder { return Ok(inputs[0].1); } _ => { - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } - if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { - crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } + if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) + { + crate::mir::phi_core::common::debug_verify_phi_inputs( + func, cur_bb, &inputs, + ); } self.insert_phi_with_dst(result_val, inputs)?; } @@ -154,10 +186,15 @@ impl MirBuilder { } else { // No variable assignment pattern detected – just emit Phi for expression result let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_raw)); } - if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_raw)); } + if let Some(tp) = then_exit_block_opt { + inputs.push((tp, then_value_raw)); + } + if let Some(ep) = else_exit_block_opt { + inputs.push((ep, else_value_raw)); + } match inputs.len() { - 0 => { /* leave result_val as fresh, but unused; synthesize void */ + 0 => { + /* leave result_val as fresh, but unused; synthesize void */ let v = crate::mir::builder::emission::constant::emit_void(self); return Ok(v); } @@ -165,9 +202,14 @@ impl MirBuilder { return Ok(inputs[0].1); } _ => { - if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } - if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { - crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); + if let Some(func) = self.current_function.as_mut() { + func.update_cfg(); + } + if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) + { + crate::mir::phi_core::common::debug_verify_phi_inputs( + func, cur_bb, &inputs, + ); } self.insert_phi_with_dst(result_val, inputs)?; } diff --git a/src/mir/builder/receiver.rs b/src/mir/builder/receiver.rs index 58d7cff7..3f4d95d8 100644 --- a/src/mir/builder/receiver.rs +++ b/src/mir/builder/receiver.rs @@ -9,7 +9,14 @@ use crate::mir::definitions::call_unified::Callee; /// - Ensure the receiver has an in-block definition via LocalSSA (Copy in the current block). /// - Args の LocalSSA は別レイヤ(ssa::local)で扱う。 pub fn finalize_method_receiver(builder: &mut MirBuilder, callee: &mut Callee) { - if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } = callee.clone() { + if let Callee::Method { + box_name, + method, + receiver: Some(r), + certainty, + box_kind, + } = callee.clone() + { // Pin to a named slot so start_new_block や LoopBuilder が slot 経由で追跡できる let r_pinned = builder.pin_to_slot(r, "@recv").unwrap_or(r); @@ -40,7 +47,12 @@ pub fn finalize_method_receiver(builder: &mut MirBuilder, callee: &mut Callee) { // LocalSSA: ensure an in-block definition in the current block let r_local = crate::mir::builder::ssa::local::recv(builder, r_pinned); - *callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty, box_kind }; + *callee = Callee::Method { + box_name, + method, + receiver: Some(r_local), + certainty, + box_kind, + }; } } - diff --git a/src/mir/builder/rewrite/known.rs b/src/mir/builder/rewrite/known.rs index cb67ae30..ded036cb 100644 --- a/src/mir/builder/rewrite/known.rs +++ b/src/mir/builder/rewrite/known.rs @@ -5,12 +5,20 @@ fn rewrite_enabled() -> bool { // New primary flag (P4): NYASH_REWRITE_KNOWN_DEFAULT (default ON; allow explicit OFF) if let Ok(v) = std::env::var("NYASH_REWRITE_KNOWN_DEFAULT") { let s = v.to_ascii_lowercase(); - if s == "0" || s == "false" || s == "off" { return false; } - if s == "1" || s == "true" || s == "on" { return true; } + if s == "0" || s == "false" || s == "off" { + return false; + } + if s == "1" || s == "true" || s == "on" { + return true; + } // fallthrough to legacy if malformed } // Legacy flag (kept for compatibility): NYASH_BUILDER_REWRITE_INSTANCE (default ON) - match std::env::var("NYASH_BUILDER_REWRITE_INSTANCE").ok().as_deref().map(|v| v.to_ascii_lowercase()) { + match std::env::var("NYASH_BUILDER_REWRITE_INSTANCE") + .ok() + .as_deref() + .map(|v| v.to_ascii_lowercase()) + { Some(ref s) if s == "0" || s == "false" || s == "off" => false, Some(ref s) if s == "1" || s == "true" || s == "on" => true, _ => true, // default ON (spec unchanged; can opt out by setting ...=0) @@ -40,13 +48,23 @@ pub(crate) fn try_known_rewrite( return None; } // Policy gates(従来互換) - let allow_userbox_rewrite = std::env::var("NYASH_DEV_REWRITE_USERBOX").ok().as_deref() == Some("1"); - let allow_new_origin = std::env::var("NYASH_DEV_REWRITE_NEW_ORIGIN").ok().as_deref() == Some("1"); + let allow_userbox_rewrite = + std::env::var("NYASH_DEV_REWRITE_USERBOX").ok().as_deref() == Some("1"); + let allow_new_origin = std::env::var("NYASH_DEV_REWRITE_NEW_ORIGIN") + .ok() + .as_deref() + == Some("1"); let from_new_origin = builder.value_origin_newbox.get(&object_value).is_some(); let arity = arg_values.len(); - let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, method, arity); - let module_has = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { false }; - if !( (module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin) ) { + let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name( + cls, method, arity, + ); + let module_has = if let Some(ref module) = builder.current_module { + module.functions.contains_key(&fname) + } else { + false + }; + if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { return None; } // Materialize function call: pass 'me' first, then args (unified call) @@ -59,7 +77,9 @@ pub(crate) fn try_known_rewrite( Some(dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } // Annotate and emit choose let chosen = fname.clone(); builder.annotate_call_result_from_func_name(dst, &chosen); @@ -84,16 +104,34 @@ pub(crate) fn try_known_rewrite_to_dst( method: &str, mut arg_values: Vec, ) -> Option> { - if !rewrite_enabled() { return None; } - if builder.value_origin_newbox.get(&object_value).is_none() { return None; } - if !builder.user_defined_boxes.contains(cls) { return None; } - let allow_userbox_rewrite = std::env::var("NYASH_DEV_REWRITE_USERBOX").ok().as_deref() == Some("1"); - let allow_new_origin = std::env::var("NYASH_DEV_REWRITE_NEW_ORIGIN").ok().as_deref() == Some("1"); + if !rewrite_enabled() { + return None; + } + if builder.value_origin_newbox.get(&object_value).is_none() { + return None; + } + if !builder.user_defined_boxes.contains(cls) { + return None; + } + let allow_userbox_rewrite = + std::env::var("NYASH_DEV_REWRITE_USERBOX").ok().as_deref() == Some("1"); + let allow_new_origin = std::env::var("NYASH_DEV_REWRITE_NEW_ORIGIN") + .ok() + .as_deref() + == Some("1"); let from_new_origin = builder.value_origin_newbox.get(&object_value).is_some(); let arity = arg_values.len(); - let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, method, arity); - let module_has = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { false }; - if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { return None; } + let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name( + cls, method, arity, + ); + let module_has = if let Some(ref module) = builder.current_module { + module.functions.contains_key(&fname) + } else { + false + }; + if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { + return None; + } // unified global function call (module-local) let mut call_args = Vec::with_capacity(arity + 1); call_args.push(object_value); @@ -104,7 +142,9 @@ pub(crate) fn try_known_rewrite_to_dst( Some(actual_dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(actual_dst, &fname); let meta = serde_json::json!({ "recv_cls": cls, @@ -157,7 +197,9 @@ pub(crate) fn try_unique_suffix_rewrite( Some(dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(dst, &fname); let meta = serde_json::json!({ "recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(), @@ -179,13 +221,26 @@ pub(crate) fn try_unique_suffix_rewrite_to_dst( method: &str, mut arg_values: Vec, ) -> Option> { - if !rewrite_enabled() { return None; } - if builder.value_origin_newbox.get(&object_value).is_none() { return None; } + if !rewrite_enabled() { + return None; + } + if builder.value_origin_newbox.get(&object_value).is_none() { + return None; + } let mut cands: Vec = builder.method_candidates(method, arg_values.len()); - if cands.len() != 1 { return None; } + if cands.len() != 1 { + return None; + } let fname = cands.remove(0); - if let Some((bx, _)) = fname.split_once('.') { if !builder.user_defined_boxes.contains(bx) { return None; } } else { return None; } - let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) { + if let Some((bx, _)) = fname.split_once('.') { + if !builder.user_defined_boxes.contains(bx) { + return None; + } + } else { + return None; + } + let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) + { Ok(v) => v, Err(e) => return Some(Err(e)), }; @@ -199,7 +254,9 @@ pub(crate) fn try_unique_suffix_rewrite_to_dst( Some(actual_dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(actual_dst, &fname); let meta = serde_json::json!({ "recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(), @@ -223,7 +280,8 @@ pub(crate) fn try_known_or_unique( arg_values: Vec, ) -> Option> { if let Some(cls) = class_name_opt.as_ref() { - if let Some(res) = try_known_rewrite(builder, object_value, cls, method, arg_values.clone()) { + if let Some(res) = try_known_rewrite(builder, object_value, cls, method, arg_values.clone()) + { return Some(res); } } @@ -240,7 +298,14 @@ pub(crate) fn try_known_or_unique_to_dst( arg_values: Vec, ) -> Option> { if let Some(cls) = class_name_opt.as_ref() { - if let Some(res) = try_known_rewrite_to_dst(builder, want_dst, object_value, cls, method, arg_values.clone()) { + if let Some(res) = try_known_rewrite_to_dst( + builder, + want_dst, + object_value, + cls, + method, + arg_values.clone(), + ) { return Some(res); } } diff --git a/src/mir/builder/rewrite/mod.rs b/src/mir/builder/rewrite/mod.rs index 02041f6b..abde0e38 100644 --- a/src/mir/builder/rewrite/mod.rs +++ b/src/mir/builder/rewrite/mod.rs @@ -7,4 +7,3 @@ pub mod known; pub mod special; - diff --git a/src/mir/builder/rewrite/special.rs b/src/mir/builder/rewrite/special.rs index 435d0dea..c8d474b8 100644 --- a/src/mir/builder/rewrite/special.rs +++ b/src/mir/builder/rewrite/special.rs @@ -13,11 +13,21 @@ pub(crate) fn try_early_str_like( if !(method == "toString" || method == "stringify") || arity != 0 { return None; } - let module = match &builder.current_module { Some(m) => m, None => return None }; + let module = match &builder.current_module { + Some(m) => m, + None => return None, + }; // Prefer class-qualified str if we can infer class; fallback to stringify for互換 if let Some(cls) = class_name_opt.clone() { - let str_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "str", 0); - let compat_stringify = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "stringify", 0); + let str_name = crate::mir::builder::calls::function_lowering::generate_method_function_name( + &cls, "str", 0, + ); + let compat_stringify = + crate::mir::builder::calls::function_lowering::generate_method_function_name( + &cls, + "stringify", + 0, + ); let have_str = module.functions.contains_key(&str_name); let have_compat = module.functions.contains_key(&compat_stringify); if have_str || (!have_str && have_compat) { @@ -32,7 +42,7 @@ pub(crate) fn try_early_str_like( "certainty": "Known", }); super::super::observe::resolve::emit_choose(builder, meta); - // unified + // unified let mut call_args = Vec::with_capacity(1); call_args.push(object_value); crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args); @@ -41,7 +51,9 @@ pub(crate) fn try_early_str_like( Some(dst), crate::mir::builder::builder_calls::CallTarget::Global(chosen.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(dst, &chosen); return Some(Ok(dst)); } @@ -71,7 +83,9 @@ pub(crate) fn try_early_str_like( Some(dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(dst, &fname); return Some(Ok(dst)); } else if cands.len() > 1 { @@ -95,7 +109,9 @@ pub(crate) fn try_early_str_like( Some(dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(dst, &fname); return Some(Ok(dst)); } @@ -113,10 +129,14 @@ pub(crate) fn try_special_equals( method: &str, args: Vec, ) -> Option> { - if method != "equals" || args.len() != 1 { return None; } + if method != "equals" || args.len() != 1 { + return None; + } // First, Known rewrite if possible if let Some(cls) = class_name_opt.as_ref() { - if let Some(res) = super::known::try_known_rewrite(builder, object_value, cls, method, args.clone()) { + if let Some(res) = + super::known::try_known_rewrite(builder, object_value, cls, method, args.clone()) + { return Some(res); } } @@ -136,10 +156,20 @@ pub(crate) fn try_early_str_like_to_dst( if !(method == "toString" || method == "stringify") || arity != 0 { return None; } - let module = match &builder.current_module { Some(m) => m, None => return None }; + let module = match &builder.current_module { + Some(m) => m, + None => return None, + }; if let Some(cls) = class_name_opt.clone() { - let str_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "str", 0); - let compat_stringify = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "stringify", 0); + let str_name = crate::mir::builder::calls::function_lowering::generate_method_function_name( + &cls, "str", 0, + ); + let compat_stringify = + crate::mir::builder::calls::function_lowering::generate_method_function_name( + &cls, + "stringify", + 0, + ); let have_str = module.functions.contains_key(&str_name); let have_compat = module.functions.contains_key(&compat_stringify); if have_str || (!have_str && have_compat) { @@ -153,10 +183,11 @@ pub(crate) fn try_early_str_like_to_dst( "certainty": "Known", }); super::super::observe::resolve::emit_choose(builder, meta); - let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; + let _name_const = + match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; let mut call_args = Vec::with_capacity(1); call_args.push(object_value); crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args); @@ -165,7 +196,9 @@ pub(crate) fn try_early_str_like_to_dst( Some(actual_dst), crate::mir::builder::builder_calls::CallTarget::Global(chosen.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(actual_dst, &chosen); return Some(Ok(actual_dst)); } @@ -184,10 +217,11 @@ pub(crate) fn try_early_str_like_to_dst( "certainty": "Heuristic", }); super::super::observe::resolve::emit_choose(builder, meta); - let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; + let _name_const = + match crate::mir::builder::name_const::make_name_const_result(builder, &fname) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; let mut call_args = Vec::with_capacity(1); call_args.push(object_value); crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args); @@ -196,7 +230,9 @@ pub(crate) fn try_early_str_like_to_dst( Some(actual_dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(actual_dst, &fname); return Some(Ok(actual_dst)); } else if cands.len() > 1 { @@ -211,19 +247,22 @@ pub(crate) fn try_early_str_like_to_dst( "certainty": "Heuristic", }); super::super::observe::resolve::emit_choose(builder, meta); - let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; - let mut call_args = Vec::with_capacity(1); - call_args.push(object_value); - crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args); + let _name_const = + match crate::mir::builder::name_const::make_name_const_result(builder, &fname) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + let mut call_args = Vec::with_capacity(1); + call_args.push(object_value); + crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args); let actual_dst = want_dst.unwrap_or_else(|| builder.next_value_id()); if let Err(e) = builder.emit_unified_call( Some(actual_dst), crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()), call_args, - ) { return Some(Err(e)); } + ) { + return Some(Err(e)); + } builder.annotate_call_result_from_func_name(actual_dst, &fname); return Some(Ok(actual_dst)); } @@ -240,9 +279,18 @@ pub(crate) fn try_special_equals_to_dst( method: &str, args: Vec, ) -> Option> { - if method != "equals" || args.len() != 1 { return None; } + if method != "equals" || args.len() != 1 { + return None; + } if let Some(cls) = class_name_opt.as_ref() { - if let Some(res) = super::known::try_known_rewrite_to_dst(builder, want_dst, object_value, cls, method, args.clone()) { + if let Some(res) = super::known::try_known_rewrite_to_dst( + builder, + want_dst, + object_value, + cls, + method, + args.clone(), + ) { return Some(res); } } diff --git a/src/mir/builder/router/mod.rs b/src/mir/builder/router/mod.rs index ab875bab..9ee4dbcb 100644 --- a/src/mir/builder/router/mod.rs +++ b/src/mir/builder/router/mod.rs @@ -1,3 +1,2 @@ //! Router policy module pub mod policy; - diff --git a/src/mir/builder/schedule/block.rs b/src/mir/builder/schedule/block.rs index 7c77e9bc..71f87855 100644 --- a/src/mir/builder/schedule/block.rs +++ b/src/mir/builder/schedule/block.rs @@ -8,11 +8,17 @@ pub struct BlockScheduleBox; impl BlockScheduleBox { /// Insert a Copy immediately after PHI nodes. Returns the local value id. #[allow(dead_code)] - pub fn ensure_after_phis_copy(builder: &mut MirBuilder, src: ValueId) -> Result { + pub fn ensure_after_phis_copy( + builder: &mut MirBuilder, + src: ValueId, + ) -> Result { if let Some(bb) = builder.current_block { if let Some(&cached) = builder.schedule_mat_map.get(&(bb, src)) { if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") { - eprintln!("[schedule/after-phis] bb={:?} src=%{} cached dst=%{}", bb, src.0, cached.0); + eprintln!( + "[schedule/after-phis] bb={:?} src=%{} cached dst=%{}", + bb, src.0, cached.0 + ); } return Ok(cached); } @@ -31,13 +37,18 @@ impl BlockScheduleBox { /// Emit a Copy right before the next emitted instruction (best-effort): /// place it at the tail of the current block. Returns the local value id. #[allow(dead_code)] - pub fn emit_before_call_copy(builder: &mut MirBuilder, src: ValueId) -> Result { + pub fn emit_before_call_copy( + builder: &mut MirBuilder, + src: ValueId, + ) -> Result { // Prefer to reuse the after-phis materialized id for this src in this block let base = Self::ensure_after_phis_copy(builder, src)?; let dst = builder.next_value_id(); if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") { - eprintln!("[schedule/before-call] bb={:?} src=%{} base=%{} dst=%{} (emitting Copy)", - builder.current_block, src.0, base.0, dst.0); + eprintln!( + "[schedule/before-call] bb={:?} src=%{} base=%{} dst=%{} (emitting Copy)", + builder.current_block, src.0, base.0, dst.0 + ); } builder.emit_instruction(MirInstruction::Copy { dst, src: base })?; // Propagate metadata to keep dst consistent with base @@ -54,8 +65,12 @@ impl BlockScheduleBox { return; } let (f_opt, bb_opt) = (builder.current_function.as_ref(), builder.current_block); - let (Some(fun), Some(bb_id)) = (f_opt, bb_opt) else { return; }; - let Some(bb) = fun.get_block(bb_id) else { return; }; + let (Some(fun), Some(bb_id)) = (f_opt, bb_opt) else { + return; + }; + let Some(bb) = fun.get_block(bb_id) else { + return; + }; // 1) PHI group must be at head let mut seen_non_phi = false; @@ -66,27 +81,27 @@ impl BlockScheduleBox { eprintln!("[block-schedule][verify] WARN: PHI found after non-PHI at bb={:?} idx={}", bb_id, idx); } } - _ => { seen_non_phi = true; } + _ => { + seen_non_phi = true; + } } } // 2) If a Copy is immediately before a Call-like, prefer it to be derived from after-PHIs copy let is_call_like = |mi: &MirInstruction| -> bool { - matches!(mi, - MirInstruction::Call { .. } | - MirInstruction::BoxCall { .. } | - MirInstruction::PluginInvoke { .. } | - MirInstruction::ExternCall { .. } + matches!( + mi, + MirInstruction::Call { .. } + | MirInstruction::BoxCall { .. } + | MirInstruction::PluginInvoke { .. } + | MirInstruction::ExternCall { .. } ) }; for w in bb.instructions.windows(2) { if let [MirInstruction::Copy { dst: _, src }, call] = w { if is_call_like(call) { // best-effort: src should be one of the after-PHIs materialized ids for this bb - let derived_ok = builder - .schedule_mat_map - .values() - .any(|&v| v == *src); + let derived_ok = builder.schedule_mat_map.values().any(|&v| v == *src); if !derived_ok { eprintln!( "[block-schedule][verify] WARN: tail Copy src=%{} is not from after-PHIs in bb={:?}", diff --git a/src/mir/builder/schedule/mod.rs b/src/mir/builder/schedule/mod.rs index 914ea9a8..a863eaad 100644 --- a/src/mir/builder/schedule/mod.rs +++ b/src/mir/builder/schedule/mod.rs @@ -1,2 +1 @@ pub mod block; - diff --git a/src/mir/builder/ssa/local.rs b/src/mir/builder/ssa/local.rs index aff7c2a4..4770feb0 100644 --- a/src/mir/builder/ssa/local.rs +++ b/src/mir/builder/ssa/local.rs @@ -45,8 +45,10 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId // CRITICAL: Check for errors - if block creation fails, return original value. if let Err(e) = builder.ensure_block_exists(bb) { if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - eprintln!("[local-ssa] ensure_block_exists FAILED bb={:?} kind={:?} v=%{} err={}", - bb, kind, v.0, e); + eprintln!( + "[local-ssa] ensure_block_exists FAILED bb={:?} kind={:?} v=%{} err={}", + bb, kind, v.0, e + ); } return v; } @@ -60,7 +62,9 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId // and then look up the current ValueId for that slot. let mut slot_name_opt: Option = None; - let names_for_v: Vec = builder.variable_map.iter() + let names_for_v: Vec = builder + .variable_map + .iter() .filter(|(k, &vid)| vid == v && k.starts_with("__pin$")) .map(|(k, _)| k.clone()) .collect(); @@ -77,8 +81,10 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId // The slot has been updated (likely by a PHI or header rewrite). // Use the updated value instead of the stale pinned ValueId. if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - eprintln!("[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}", - bb, kind, slot_name, v.0, current_val.0); + eprintln!( + "[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}", + bb, kind, slot_name, v.0, current_val.0 + ); } builder.local_ssa_map.insert(key, current_val); return current_val; @@ -89,7 +95,9 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId let loc = builder.next_value_id(); // CRITICAL: Check emit_instruction result - if Copy fails, return original value // to avoid returning undefined ValueId. - if let Err(e) = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v }) { + if let Err(e) = + builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v }) + { if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { eprintln!("[local-ssa] emit_instruction Copy FAILED bb={:?} kind={:?} v=%{} dst=%{} err={}", bb, kind, v.0, loc.0, e); @@ -98,7 +106,10 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId return v; } if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - eprintln!("[local-ssa] copy bb={:?} kind={:?} %{} -> %{}", bb, kind, v.0, loc.0); + eprintln!( + "[local-ssa] copy bb={:?} kind={:?} %{} -> %{}", + bb, kind, v.0, loc.0 + ); } // Success: register metadata and cache if let Some(t) = builder.value_types.get(&v).cloned() { @@ -115,19 +126,29 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId } #[inline] -pub fn recv(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Recv) } +pub fn recv(builder: &mut MirBuilder, v: ValueId) -> ValueId { + ensure(builder, v, LocalKind::Recv) +} #[inline] -pub fn arg(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Arg) } +pub fn arg(builder: &mut MirBuilder, v: ValueId) -> ValueId { + ensure(builder, v, LocalKind::Arg) +} #[inline] -pub fn cond(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Cond) } +pub fn cond(builder: &mut MirBuilder, v: ValueId) -> ValueId { + ensure(builder, v, LocalKind::Cond) +} #[inline] -pub fn field_base(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::FieldBase) } +pub fn field_base(builder: &mut MirBuilder, v: ValueId) -> ValueId { + ensure(builder, v, LocalKind::FieldBase) +} #[inline] -pub fn cmp_operand(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::CompareOperand) } +pub fn cmp_operand(builder: &mut MirBuilder, v: ValueId) -> ValueId { + ensure(builder, v, LocalKind::CompareOperand) +} /// Finalize only the args (legacy Call paths) pub fn finalize_args(builder: &mut MirBuilder, args: &mut Vec) { @@ -141,7 +162,12 @@ pub fn finalize_args(builder: &mut MirBuilder, args: &mut Vec) { pub fn finalize_branch_cond(builder: &mut MirBuilder, condition_v: &mut ValueId) { *condition_v = cond(builder, *condition_v); if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-branch bb={:?} cond=%{}", bb, condition_v.0); } + if let Some(bb) = builder.current_block { + eprintln!( + "[local-ssa] finalize-branch bb={:?} cond=%{}", + bb, condition_v.0 + ); + } } } @@ -151,15 +177,33 @@ pub fn finalize_compare(builder: &mut MirBuilder, lhs: &mut ValueId, rhs: &mut V *lhs = cmp_operand(builder, *lhs); *rhs = cmp_operand(builder, *rhs); if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-compare bb={:?} lhs=%{} rhs=%{}", bb, lhs.0, rhs.0); } + if let Some(bb) = builder.current_block { + eprintln!( + "[local-ssa] finalize-compare bb={:?} lhs=%{} rhs=%{}", + bb, lhs.0, rhs.0 + ); + } } } /// Finalize field use sites: ensure base and all args are in the current block. -pub fn finalize_field_base_and_args(builder: &mut MirBuilder, base: &mut ValueId, args: &mut Vec) { +pub fn finalize_field_base_and_args( + builder: &mut MirBuilder, + base: &mut ValueId, + args: &mut Vec, +) { *base = field_base(builder, *base); - for a in args.iter_mut() { *a = arg(builder, *a); } + for a in args.iter_mut() { + *a = arg(builder, *a); + } if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-field bb={:?} base=%{} argc={}", bb, base.0, args.len()); } + if let Some(bb) = builder.current_block { + eprintln!( + "[local-ssa] finalize-field bb={:?} base=%{} argc={}", + bb, + base.0, + args.len() + ); + } } } diff --git a/src/mir/builder/ssa/mod.rs b/src/mir/builder/ssa/mod.rs index 34c8f4e8..27099624 100644 --- a/src/mir/builder/ssa/mod.rs +++ b/src/mir/builder/ssa/mod.rs @@ -1,2 +1 @@ pub mod local; - diff --git a/src/mir/builder/stmts.rs b/src/mir/builder/stmts.rs index 484cbe34..11255141 100644 --- a/src/mir/builder/stmts.rs +++ b/src/mir/builder/stmts.rs @@ -1,7 +1,7 @@ use super::{Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::{ASTNode, CallExpr}; -use crate::mir::TypeOpKind; use crate::mir::utils::is_current_block_terminated; +use crate::mir::TypeOpKind; impl super::MirBuilder { // Print statement: env.console.log(value) with early TypeOp handling @@ -10,15 +10,34 @@ impl super::MirBuilder { // Prefer wrapper for simple function-call pattern (non-breaking refactor) if let Ok(call) = CallExpr::try_from(expression.clone()) { if (call.name == "isType" || call.name == "asType") && call.arguments.len() == 2 { - super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType) [via wrapper]"); - if let Some(type_name) = super::MirBuilder::extract_string_literal(&call.arguments[1]) { - super::utils::builder_debug_log(&format!("extract_string_literal OK: {}", type_name)); + super::utils::builder_debug_log( + "pattern: print(FunctionCall isType|asType) [via wrapper]", + ); + if let Some(type_name) = + super::MirBuilder::extract_string_literal(&call.arguments[1]) + { + super::utils::builder_debug_log(&format!( + "extract_string_literal OK: {}", + type_name + )); let val = self.build_expression(call.arguments[0].clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.next_value_id(); - let op = if call.name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast }; - super::utils::builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst)); - self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?; + let op = if call.name == "isType" { + TypeOpKind::Check + } else { + TypeOpKind::Cast + }; + super::utils::builder_debug_log(&format!( + "emit TypeOp {:?} value={} dst= {}", + op, val, dst + )); + self.emit_instruction(MirInstruction::TypeOp { + dst, + op, + value: val, + ty, + })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), @@ -129,7 +148,7 @@ impl super::MirBuilder { if use_unified { // New unified path - treat print as global function self.emit_unified_call( - None, // print returns nothing + None, // print returns nothing super::builder_calls::CallTarget::Global("print".to_string()), vec![value], )?; @@ -155,14 +174,24 @@ impl super::MirBuilder { let total = statements.len(); eprintln!("[DEBUG/build_block] Processing {} statements", total); for (idx, statement) in statements.into_iter().enumerate() { - eprintln!("[DEBUG/build_block] Statement {}/{} current_block={:?} current_function={}", - idx+1, total, self.current_block, - self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("none")); + eprintln!( + "[DEBUG/build_block] Statement {}/{} current_block={:?} current_function={}", + idx + 1, + total, + self.current_block, + self.current_function + .as_ref() + .map(|f| f.signature.name.as_str()) + .unwrap_or("none") + ); last_value = Some(self.build_statement(statement)?); // If the current block was terminated by this statement (e.g., return/throw), // do not emit any further instructions for this block. if is_current_block_terminated(self)? { - eprintln!("[DEBUG/build_block] Block terminated after statement {}", idx+1); + eprintln!( + "[DEBUG/build_block] Block terminated after statement {}", + idx + 1 + ); break; } } @@ -190,7 +219,6 @@ impl super::MirBuilder { } } - // Local declarations with optional initializers pub(super) fn build_local_statement( &mut self, @@ -198,7 +226,11 @@ impl super::MirBuilder { initial_values: Vec>>, ) -> Result { if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_local_statement] ENTRY: variables={:?}, initial_values.len()={}", variables, initial_values.len()); + eprintln!( + "[build_local_statement] ENTRY: variables={:?}, initial_values.len()={}", + variables, + initial_values.len() + ); } let mut last_value = None; for (i, var_name) in variables.iter().enumerate() { @@ -212,12 +244,15 @@ impl super::MirBuilder { let var_id = self.next_value_id(); if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_local_statement] '{}': init_val={:?}, allocated var_id={:?}", var_name, init_val, var_id); + eprintln!( + "[build_local_statement] '{}': init_val={:?}, allocated var_id={:?}", + var_name, init_val, var_id + ); } self.emit_instruction(crate::mir::MirInstruction::Copy { dst: var_id, - src: init_val + src: init_val, })?; // Propagate metadata (type/origin) from initializer to variable @@ -228,13 +263,19 @@ impl super::MirBuilder { // Create a concrete register for uninitialized locals (Void) let void_id = crate::mir::builder::emission::constant::emit_void(self); if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_local_statement] '{}': uninitialized, void_id={:?}", var_name, void_id); + eprintln!( + "[build_local_statement] '{}': uninitialized, void_id={:?}", + var_name, void_id + ); } void_id }; if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_local_statement] Inserting '{}' -> {:?} into variable_map", var_name, var_id); + eprintln!( + "[build_local_statement] Inserting '{}' -> {:?} into variable_map", + var_name, var_id + ); } self.variable_map.insert(var_name.clone(), var_id); // SlotRegistry にもローカル変数スロットを登録しておくよ(観測専用) @@ -267,7 +308,10 @@ impl super::MirBuilder { // Defer: copy into slot and jump to target if let (Some(slot), Some(target)) = (self.return_defer_slot, self.return_defer_target) { self.return_deferred_emitted = true; - self.emit_instruction(MirInstruction::Copy { dst: slot, src: return_value })?; + self.emit_instruction(MirInstruction::Copy { + dst: slot, + src: return_value, + })?; crate::mir::builder::metadata::propagate::propagate(self, return_value, slot); if !self.is_current_block_terminated() { crate::mir::builder::emission::branch::emit_jump(self, target)?; @@ -275,12 +319,16 @@ impl super::MirBuilder { Ok(return_value) } else { // Fallback: no configured slot/target; emit a real return - self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?; + self.emit_instruction(MirInstruction::Return { + value: Some(return_value), + })?; Ok(return_value) } } else { // Normal return - self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?; + self.emit_instruction(MirInstruction::Return { + value: Some(return_value), + })?; Ok(return_value) } } @@ -299,7 +347,8 @@ impl super::MirBuilder { } = expression.clone() { let recv_val = self.build_expression(*object)?; - let mname_id = crate::mir::builder::emission::constant::emit_string(self, method.clone()); + let mname_id = + crate::mir::builder::emission::constant::emit_string(self, method.clone()); let mut arg_vals: Vec = Vec::with_capacity(2 + arguments.len()); arg_vals.push(recv_val); arg_vals.push(mname_id); diff --git a/src/mir/builder/type_registry.rs b/src/mir/builder/type_registry.rs index 44e8a30e..52154074 100644 --- a/src/mir/builder/type_registry.rs +++ b/src/mir/builder/type_registry.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; #[derive(Debug, Clone)] pub struct TraceEntry { pub vid: ValueId, - pub source: String, // "newbox:MapBox", "param:args", "propagate:from_%123" + pub source: String, // "newbox:MapBox", "param:args", "propagate:from_%123" #[allow(dead_code)] pub timestamp: usize, } @@ -36,9 +36,7 @@ pub struct TypeRegistry { impl TypeRegistry { /// 新しいレジストリを作成 pub fn new() -> Self { - let trace_enabled = std::env::var("NYASH_TYPE_REGISTRY_TRACE") - .ok() - .as_deref() == Some("1"); + let trace_enabled = std::env::var("NYASH_TYPE_REGISTRY_TRACE").ok().as_deref() == Some("1"); Self { origins: HashMap::new(), @@ -194,7 +192,10 @@ impl TypeRegistry { // フォールバック: コンテキスト名(警告付き) if let Some(ctx) = fallback_context { if self.trace_enabled { - eprintln!("[type-registry] WARNING: fallback to context '{}' for %{}", ctx, vid.0); + eprintln!( + "[type-registry] WARNING: fallback to context '{}' for %{}", + ctx, vid.0 + ); } return ctx.to_string(); } @@ -222,9 +223,15 @@ impl TypeRegistry { /// 全トレースログを表示 #[allow(dead_code)] pub fn dump_trace(&self) { - eprintln!("[type-registry] === Trace Log ({} entries) ===", self.trace_log.len()); + eprintln!( + "[type-registry] === Trace Log ({} entries) ===", + self.trace_log.len() + ); for entry in &self.trace_log { - eprintln!("[type-registry] #{:04} %{} ← {}", entry.timestamp, entry.vid.0, entry.source); + eprintln!( + "[type-registry] #{:04} %{} ← {}", + entry.timestamp, entry.vid.0, entry.source + ); } } @@ -234,7 +241,10 @@ impl TypeRegistry { eprintln!("[type-registry] === Statistics ==="); eprintln!("[type-registry] Origins: {} entries", self.origins.len()); eprintln!("[type-registry] Types: {} entries", self.types.len()); - eprintln!("[type-registry] Trace log: {} entries", self.trace_log.len()); + eprintln!( + "[type-registry] Trace log: {} entries", + self.trace_log.len() + ); } // ============================================================ diff --git a/src/mir/builder/types/annotation.rs b/src/mir/builder/types/annotation.rs index 937b3d4f..558247ff 100644 --- a/src/mir/builder/types/annotation.rs +++ b/src/mir/builder/types/annotation.rs @@ -1,7 +1,7 @@ //! TypeAnnotationBox — MIR 値への型注釈(仕様不変の最小) -use crate::mir::{MirType, ValueId}; use crate::mir::builder::MirBuilder; +use crate::mir::{MirType, ValueId}; /// 直接的に MirType を設定する(仕様不変)。 #[inline] @@ -22,18 +22,42 @@ pub fn annotate_from_function(builder: &mut MirBuilder, dst: ValueId, func_name: fn infer_return_type(func_name: &str) -> Option { // Very small whitelist; 仕様不変(既知の戻り型のみ) // Normalize forms like "JsonNode.str/0" or "StringBox.length/0" if needed - if func_name.ends_with(".str/0") { return Some(MirType::String); } - if func_name.ends_with(".length/0") { return Some(MirType::Integer); } - if func_name.ends_with(".size/0") { return Some(MirType::Integer); } - if func_name.ends_with(".len/0") { return Some(MirType::Integer); } - if func_name.ends_with(".substring/2") { return Some(MirType::String); } - if func_name.ends_with(".esc_json/0") { return Some(MirType::String); } - if func_name.ends_with(".indexOf/1") { return Some(MirType::Integer); } - if func_name.ends_with(".lastIndexOf/1") { return Some(MirType::Integer); } - if func_name.ends_with(".is_digit_char/1") { return Some(MirType::Bool); } - if func_name.ends_with(".is_hex_digit_char/1") { return Some(MirType::Bool); } - if func_name.ends_with(".is_alpha_char/1") { return Some(MirType::Bool); } - if func_name.ends_with("MapBox.has/1") { return Some(MirType::Bool); } + if func_name.ends_with(".str/0") { + return Some(MirType::String); + } + if func_name.ends_with(".length/0") { + return Some(MirType::Integer); + } + if func_name.ends_with(".size/0") { + return Some(MirType::Integer); + } + if func_name.ends_with(".len/0") { + return Some(MirType::Integer); + } + if func_name.ends_with(".substring/2") { + return Some(MirType::String); + } + if func_name.ends_with(".esc_json/0") { + return Some(MirType::String); + } + if func_name.ends_with(".indexOf/1") { + return Some(MirType::Integer); + } + if func_name.ends_with(".lastIndexOf/1") { + return Some(MirType::Integer); + } + if func_name.ends_with(".is_digit_char/1") { + return Some(MirType::Bool); + } + if func_name.ends_with(".is_hex_digit_char/1") { + return Some(MirType::Bool); + } + if func_name.ends_with(".is_alpha_char/1") { + return Some(MirType::Bool); + } + if func_name.ends_with("MapBox.has/1") { + return Some(MirType::Bool); + } // Fallback: none (変更なし) None } diff --git a/src/mir/builder/types/mod.rs b/src/mir/builder/types/mod.rs index 2f12bbc7..95ddfd83 100644 --- a/src/mir/builder/types/mod.rs +++ b/src/mir/builder/types/mod.rs @@ -3,4 +3,3 @@ //! - inference.rs(後段、挙動不変の観測強化と最小推論)。 pub mod annotation; - diff --git a/src/mir/builder/utils.rs b/src/mir/builder/utils.rs index 28cdab04..37e67944 100644 --- a/src/mir/builder/utils.rs +++ b/src/mir/builder/utils.rs @@ -17,7 +17,9 @@ pub(super) fn builder_debug_log(msg: &str) { if let Ok(cap_s) = std::env::var("NYASH_BUILDER_DEBUG_LIMIT") { if let Ok(cap) = cap_s.parse::() { let n = BUILDER_DEBUG_COUNT.fetch_add(1, Ordering::Relaxed); - if n >= cap { return; } + if n >= cap { + return; + } } } eprintln!("[BUILDER] {}", msg); @@ -32,28 +34,38 @@ impl super::MirBuilder { #[inline] pub(crate) fn next_value_id(&mut self) -> super::ValueId { if let Some(ref mut f) = self.current_function { - f.next_value_id() // Function context + f.next_value_id() // Function context } else { - self.value_gen.next() // Module context + self.value_gen.next() // Module context } } // ---- LocalSSA convenience (readability helpers) ---- #[allow(dead_code)] #[inline] - pub(crate) fn local_recv(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::recv(self, v) } + pub(crate) fn local_recv(&mut self, v: super::ValueId) -> super::ValueId { + super::ssa::local::recv(self, v) + } #[allow(dead_code)] #[inline] - pub(crate) fn local_arg(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::arg(self, v) } + pub(crate) fn local_arg(&mut self, v: super::ValueId) -> super::ValueId { + super::ssa::local::arg(self, v) + } #[allow(dead_code)] #[inline] - pub(crate) fn local_cmp_operand(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::cmp_operand(self, v) } + pub(crate) fn local_cmp_operand(&mut self, v: super::ValueId) -> super::ValueId { + super::ssa::local::cmp_operand(self, v) + } #[allow(dead_code)] #[inline] - pub(crate) fn local_field_base(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::field_base(self, v) } + pub(crate) fn local_field_base(&mut self, v: super::ValueId) -> super::ValueId { + super::ssa::local::field_base(self, v) + } #[allow(dead_code)] #[inline] - pub(crate) fn local_cond(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::cond(self, v) } + pub(crate) fn local_cond(&mut self, v: super::ValueId) -> super::ValueId { + super::ssa::local::cond(self, v) + } /// Ensure a basic block exists in the current function pub(crate) fn ensure_block_exists(&mut self, block_id: BasicBlockId) -> Result<(), String> { if let Some(ref mut function) = self.current_function { @@ -93,12 +105,15 @@ impl super::MirBuilder { // and LoopBuilder/IfBuilder already manage PHIs for ALL variables in variable_map, // including pinned slots. } - if false && !self.suppress_pin_entry_copy_next { // Keep old code for reference + if false && !self.suppress_pin_entry_copy_next { + // Keep old code for reference // First pass: copy all pin slots and remember old->new mapping let names: Vec = self.variable_map.keys().cloned().collect(); let mut pin_renames: Vec<(super::ValueId, super::ValueId)> = Vec::new(); for name in names.iter() { - if !name.starts_with("__pin$") { continue; } + if !name.starts_with("__pin$") { + continue; + } if let Some(&src) = self.variable_map.get(name) { let dst = self.next_value_id(); self.emit_instruction(super::MirInstruction::Copy { dst, src })?; @@ -169,8 +184,13 @@ impl super::MirBuilder { crate::mir::definitions::call_unified::TypeCertainty::Union, args.len(), ); - if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - if matches!(method.as_str(), "parse" | "substring" | "has_errors" | "length") { + if super::utils::builder_debug_enabled() + || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") + { + if matches!( + method.as_str(), + "parse" | "substring" | "has_errors" | "length" + ) { eprintln!( "[boxcall-decision] method={} bb={:?} recv=%{} class_hint={:?} prefer_legacy={}", method, @@ -314,18 +334,27 @@ impl super::MirBuilder { /// Pin a block-crossing ephemeral value into a pseudo local slot and register it in variable_map /// so it participates in PHI merges across branches/blocks. Safe default for correctness-first. - pub(crate) fn pin_to_slot(&mut self, v: super::ValueId, hint: &str) -> Result { + pub(crate) fn pin_to_slot( + &mut self, + v: super::ValueId, + hint: &str, + ) -> Result { self.temp_slot_counter = self.temp_slot_counter.wrapping_add(1); let slot_name = format!("__pin${}${}", self.temp_slot_counter, hint); // Phase 25.1b: Use function-local ID allocator to avoid SSA verification failures let dst = if let Some(ref mut f) = self.current_function { - f.next_value_id() // Function context: use local ID + f.next_value_id() // Function context: use local ID } else { - self.value_gen.next() // Module context: use global ID + self.value_gen.next() // Module context: use global ID }; self.emit_instruction(super::MirInstruction::Copy { dst, src: v })?; - if super::utils::builder_debug_enabled() || std::env::var("NYASH_PIN_TRACE").ok().as_deref() == Some("1") { - super::utils::builder_debug_log(&format!("pin slot={} src={} dst={}", slot_name, v.0, dst.0)); + if super::utils::builder_debug_enabled() + || std::env::var("NYASH_PIN_TRACE").ok().as_deref() == Some("1") + { + super::utils::builder_debug_log(&format!( + "pin slot={} src={} dst={}", + slot_name, v.0, dst.0 + )); } // Propagate lightweight metadata so downstream resolution/type inference remains stable crate::mir::builder::metadata::propagate::propagate(self, v, dst); @@ -339,12 +368,15 @@ impl super::MirBuilder { /// Ensure a value has a local definition in the current block by inserting a Copy. #[allow(dead_code)] - pub(crate) fn materialize_local(&mut self, v: super::ValueId) -> Result { + pub(crate) fn materialize_local( + &mut self, + v: super::ValueId, + ) -> Result { // Phase 25.1b: Use function-local ID allocator to avoid SSA verification failures let dst = if let Some(ref mut f) = self.current_function { - f.next_value_id() // Function context: use local ID + f.next_value_id() // Function context: use local ID } else { - self.value_gen.next() // Module context: use global ID + self.value_gen.next() // Module context: use global ID }; self.emit_instruction(super::MirInstruction::Copy { dst, src: v })?; // Propagate metadata (type/origin) from source to the new local copy @@ -354,11 +386,18 @@ impl super::MirBuilder { /// Insert a Copy immediately after PHI nodes in the current block (position-stable). #[allow(dead_code)] - pub(crate) fn insert_copy_after_phis(&mut self, dst: super::ValueId, src: super::ValueId) -> Result<(), String> { - if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) { + pub(crate) fn insert_copy_after_phis( + &mut self, + dst: super::ValueId, + src: super::ValueId, + ) -> Result<(), String> { + if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) + { if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") { - eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} attempting...", - bb, dst.0, src.0); + eprintln!( + "[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} attempting...", + bb, dst.0, src.0 + ); } if let Some(block) = function.get_block_mut(bb) { if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") { @@ -382,7 +421,11 @@ impl super::MirBuilder { /// Ensure a value is safe to use in the current block by slotifying (pinning) it. /// Currently correctness-first: always pin to get a block-local def and PHI participation. - pub(crate) fn ensure_slotified_for_use(&mut self, v: super::ValueId, hint: &str) -> Result { + pub(crate) fn ensure_slotified_for_use( + &mut self, + v: super::ValueId, + hint: &str, + ) -> Result { self.pin_to_slot(v, hint) } diff --git a/src/mir/definitions/call_unified.rs b/src/mir/definitions/call_unified.rs index 1232690a..e0224fa0 100644 --- a/src/mir/definitions/call_unified.rs +++ b/src/mir/definitions/call_unified.rs @@ -41,18 +41,18 @@ pub enum Callee { /// Box method call with explicit receiver /// Enables static resolution of box.method() patterns Method { - box_name: String, // "StringBox", "ConsoleStd", etc. - method: String, // "upper", "print", etc. - receiver: Option, // Some(obj) for instance, None for static/constructor - certainty: TypeCertainty, // Phase 3: known vs union - box_kind: CalleeBoxKind, // Structural guard: prevent static/runtime mixing + box_name: String, // "StringBox", "ConsoleStd", etc. + method: String, // "upper", "print", etc. + receiver: Option, // Some(obj) for instance, None for static/constructor + certainty: TypeCertainty, // Phase 3: known vs union + box_kind: CalleeBoxKind, // Structural guard: prevent static/runtime mixing }, /// Constructor call (NewBox equivalent) /// Creates new Box instances with birth() method Constructor { - box_type: String, // "StringBox", "ArrayBox", etc. - // Constructor doesn't have a receiver + box_type: String, // "StringBox", "ArrayBox", etc. + // Constructor doesn't have a receiver }, /// Closure creation (NewClosure equivalent) @@ -60,7 +60,7 @@ pub enum Callee { Closure { params: Vec, captures: Vec<(String, ValueId)>, - me_capture: Option, // Optional 'me' weak capture + me_capture: Option, // Optional 'me' weak capture }, /// Dynamic function value call @@ -207,7 +207,7 @@ impl MirCall { method, receiver: Some(receiver), certainty: TypeCertainty::Known, - box_kind: CalleeBoxKind::RuntimeData, // Default to runtime for helper + box_kind: CalleeBoxKind::RuntimeData, // Default to runtime for helper }, args, ) @@ -216,17 +216,13 @@ impl MirCall { /// Create an external call pub fn external(dst: Option, name: String, args: Vec) -> Self { let mut call = MirCall::new(dst, Callee::Extern(name), args); - call.effects = EffectMask::IO; // External calls have I/O effects + call.effects = EffectMask::IO; // External calls have I/O effects call } /// Create a constructor call (NewBox equivalent) pub fn constructor(dst: ValueId, box_type: String, args: Vec) -> Self { - let mut call = MirCall::new( - Some(dst), - Callee::Constructor { box_type }, - args, - ); + let mut call = MirCall::new(Some(dst), Callee::Constructor { box_type }, args); call.flags = CallFlags::constructor(); call.effects = EffectMask::PURE.add(Effect::Alloc); call @@ -246,7 +242,7 @@ impl MirCall { captures, me_capture, }, - vec![], // Closures don't have regular args at creation + vec![], // Closures don't have regular args at creation ); call.flags = CallFlags::constructor(); call.effects = EffectMask::PURE.add(Effect::Alloc); @@ -323,7 +319,7 @@ pub mod migration { method, receiver: Some(box_val), certainty: TypeCertainty::Union, - box_kind: CalleeBoxKind::RuntimeData, // BoxCall is always runtime + box_kind: CalleeBoxKind::RuntimeData, // BoxCall is always runtime }, args, ); @@ -332,11 +328,7 @@ pub mod migration { } /// Convert NewBox to MirCall - pub fn from_new_box( - dst: ValueId, - box_type: String, - args: Vec, - ) -> MirCall { + pub fn from_new_box(dst: ValueId, box_type: String, args: Vec) -> MirCall { MirCall::constructor(dst, box_type, args) } diff --git a/src/mir/definitions/mod.rs b/src/mir/definitions/mod.rs index 9f91d15f..94209638 100644 --- a/src/mir/definitions/mod.rs +++ b/src/mir/definitions/mod.rs @@ -7,4 +7,4 @@ pub mod call_unified; // Re-export commonly used types -pub use call_unified::{Callee, CallFlags, MirCall}; \ No newline at end of file +pub use call_unified::{CallFlags, Callee, MirCall}; diff --git a/src/mir/function.rs b/src/mir/function.rs index 48bd86a1..1e2fa1e6 100644 --- a/src/mir/function.rs +++ b/src/mir/function.rs @@ -105,7 +105,7 @@ impl MirFunction { let receiver_count = if has_implicit_receiver { 1 } else { 0 }; let total_value_ids = param_count + receiver_count; - let initial_counter = total_value_ids.max(1); // At least 1 to reserve ValueId(0) as sentinel + let initial_counter = total_value_ids.max(1); // At least 1 to reserve ValueId(0) as sentinel // 🔥 Hotfix 5: Pre-populate params vector with reserved ValueIds // Without this, setup_function_params() will allocate NEW ValueIds starting from @@ -132,7 +132,7 @@ impl MirFunction { blocks, entry_block, locals: Vec::new(), - params: pre_params, // ✅ Pre-populate instead of empty Vec + params: pre_params, // ✅ Pre-populate instead of empty Vec next_value_id: initial_counter, metadata: FunctionMetadata::default(), } @@ -168,7 +168,9 @@ impl MirFunction { /// Add a new basic block pub fn add_block(&mut self, block: BasicBlock) -> BasicBlockId { let id = block.id; - if self.blocks.contains_key(&id) && std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { + if self.blocks.contains_key(&id) + && std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") + { eprintln!("[mir-function] replacing existing block {:?}", id); } self.blocks.insert(id, block); diff --git a/src/mir/function_emission.rs b/src/mir/function_emission.rs index de8b1c18..3d8a988f 100644 --- a/src/mir/function_emission.rs +++ b/src/mir/function_emission.rs @@ -9,7 +9,10 @@ use crate::mir::{BasicBlockId, ConstValue, MirFunction, MirInstruction, ValueId} pub fn emit_const_integer(f: &mut MirFunction, bb: BasicBlockId, val: i64) -> ValueId { let dst = f.next_value_id(); if let Some(block) = f.get_block_mut(bb) { - block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(val) }); + block.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Integer(val), + }); } dst } @@ -18,7 +21,10 @@ pub fn emit_const_integer(f: &mut MirFunction, bb: BasicBlockId, val: i64) -> Va pub fn emit_const_bool(f: &mut MirFunction, bb: BasicBlockId, val: bool) -> ValueId { let dst = f.next_value_id(); if let Some(block) = f.get_block_mut(bb) { - block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(val) }); + block.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Bool(val), + }); } dst } @@ -27,7 +33,10 @@ pub fn emit_const_bool(f: &mut MirFunction, bb: BasicBlockId, val: bool) -> Valu pub fn emit_const_string>(f: &mut MirFunction, bb: BasicBlockId, s: S) -> ValueId { let dst = f.next_value_id(); if let Some(block) = f.get_block_mut(bb) { - block.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(s.into()) }); + block.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::String(s.into()), + }); } dst } @@ -36,7 +45,10 @@ pub fn emit_const_string>(f: &mut MirFunction, bb: BasicBlockId, pub fn emit_const_void(f: &mut MirFunction, bb: BasicBlockId) -> ValueId { let dst = f.next_value_id(); if let Some(block) = f.get_block_mut(bb) { - block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Void }); + block.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Void, + }); } dst } @@ -54,4 +66,3 @@ pub fn emit_jump(f: &mut MirFunction, bb: BasicBlockId, target: BasicBlockId) { block.add_instruction(MirInstruction::Jump { target }); } } - diff --git a/src/mir/hints.rs b/src/mir/hints.rs index 819f3a34..219d7310 100644 --- a/src/mir/hints.rs +++ b/src/mir/hints.rs @@ -24,8 +24,13 @@ pub struct HintSink { } impl HintSink { - pub fn new() -> Self { Self { enabled: false } } - pub fn with_enabled(mut self, enabled: bool) -> Self { self.enabled = enabled; self } + pub fn new() -> Self { + Self { enabled: false } + } + pub fn with_enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } fn cfg() -> HintCfg { // New unified env: NYASH_MIR_HINTS="|" @@ -38,58 +43,97 @@ impl HintSink { return HintCfg::parse(&spec); } if std::env::var("NYASH_MIR_TRACE_HINTS").ok().as_deref() == Some("1") { - return HintCfg { sink: HintSinkTarget::Stderr, kinds: HintKinds::All }; + return HintCfg { + sink: HintSinkTarget::Stderr, + kinds: HintKinds::All, + }; + } + HintCfg { + sink: HintSinkTarget::None, + kinds: HintKinds::None, } - HintCfg { sink: HintSinkTarget::None, kinds: HintKinds::None } } #[inline] pub fn record(&mut self, hint: HintKind) { // Resolve config (env-based). Lightweight and robust; acceptable to parse per call. let cfg = Self::cfg(); - if matches!(cfg.sink, HintSinkTarget::None) { return; } + if matches!(cfg.sink, HintSinkTarget::None) { + return; + } // Filter kinds let k = hint_tag(&hint); - if !cfg.kinds.contains(k) { return; } + if !cfg.kinds.contains(k) { + return; + } match cfg.sink { HintSinkTarget::None => {} - HintSinkTarget::Stderr => { - match hint { - HintKind::ScopeEnter(id) => eprintln!("[mir][hint] ScopeEnter({})", id), - HintKind::ScopeLeave(id) => eprintln!("[mir][hint] ScopeLeave({})", id), - HintKind::Defer(calls) => eprintln!("[mir][hint] Defer({})", calls.join(";")), - HintKind::JoinResult(var) => eprintln!("[mir][hint] JoinResult({})", var), - HintKind::LoopCarrier(vars) => eprintln!("[mir][hint] LoopCarrier({})", vars.join(",")), - HintKind::LoopHeader => eprintln!("[mir][hint] LoopHeader"), - HintKind::LoopLatch => eprintln!("[mir][hint] LoopLatch"), - HintKind::NoEmptyPhi => eprintln!("[mir][hint] NoEmptyPhi"), + HintSinkTarget::Stderr => match hint { + HintKind::ScopeEnter(id) => eprintln!("[mir][hint] ScopeEnter({})", id), + HintKind::ScopeLeave(id) => eprintln!("[mir][hint] ScopeLeave({})", id), + HintKind::Defer(calls) => eprintln!("[mir][hint] Defer({})", calls.join(";")), + HintKind::JoinResult(var) => eprintln!("[mir][hint] JoinResult({})", var), + HintKind::LoopCarrier(vars) => { + eprintln!("[mir][hint] LoopCarrier({})", vars.join(",")) } - } + HintKind::LoopHeader => eprintln!("[mir][hint] LoopHeader"), + HintKind::LoopLatch => eprintln!("[mir][hint] LoopLatch"), + HintKind::NoEmptyPhi => eprintln!("[mir][hint] NoEmptyPhi"), + }, HintSinkTarget::Jsonl(ref path) => { // Append one JSON object per line. Best-effort; ignore errors. let _ = append_jsonl(path, &hint); } } } - #[inline] pub fn scope_enter(&mut self, id: u32) { self.record(HintKind::ScopeEnter(id)); } - #[inline] pub fn scope_leave(&mut self, id: u32) { self.record(HintKind::ScopeLeave(id)); } - #[inline] pub fn defer_calls>(&mut self, calls: impl IntoIterator) { - self.record(HintKind::Defer(calls.into_iter().map(|s| s.into()).collect())) + #[inline] + pub fn scope_enter(&mut self, id: u32) { + self.record(HintKind::ScopeEnter(id)); } - #[inline] pub fn join_result>(&mut self, var: S) { self.record(HintKind::JoinResult(var.into())); } - #[inline] pub fn loop_carrier>(&mut self, vars: impl IntoIterator) { - self.record(HintKind::LoopCarrier(vars.into_iter().map(|s| s.into()).collect())) + #[inline] + pub fn scope_leave(&mut self, id: u32) { + self.record(HintKind::ScopeLeave(id)); + } + #[inline] + pub fn defer_calls>(&mut self, calls: impl IntoIterator) { + self.record(HintKind::Defer( + calls.into_iter().map(|s| s.into()).collect(), + )) + } + #[inline] + pub fn join_result>(&mut self, var: S) { + self.record(HintKind::JoinResult(var.into())); + } + #[inline] + pub fn loop_carrier>(&mut self, vars: impl IntoIterator) { + self.record(HintKind::LoopCarrier( + vars.into_iter().map(|s| s.into()).collect(), + )) + } + #[inline] + pub fn loop_header(&mut self) { + self.record(HintKind::LoopHeader); + } + #[inline] + pub fn loop_latch(&mut self) { + self.record(HintKind::LoopLatch); + } + #[inline] + pub fn no_empty_phi(&mut self) { + self.record(HintKind::NoEmptyPhi); } - #[inline] pub fn loop_header(&mut self) { self.record(HintKind::LoopHeader); } - #[inline] pub fn loop_latch(&mut self) { self.record(HintKind::LoopLatch); } - #[inline] pub fn no_empty_phi(&mut self) { self.record(HintKind::NoEmptyPhi); } } // ---- Unified hint config parser ---- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum HintTag { Scope, Join, Loop, Phi } +enum HintTag { + Scope, + Join, + Loop, + Phi, +} fn hint_tag(h: &HintKind) -> HintTag { match h { @@ -101,14 +145,28 @@ fn hint_tag(h: &HintKind) -> HintTag { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum HintKinds { None, Some { scope: bool, join: bool, loopk: bool, phi: bool }, All } +enum HintKinds { + None, + Some { + scope: bool, + join: bool, + loopk: bool, + phi: bool, + }, + All, +} impl HintKinds { fn contains(&self, tag: HintTag) -> bool { match self { HintKinds::All => true, HintKinds::None => false, - HintKinds::Some { scope, join, loopk, phi } => match tag { + HintKinds::Some { + scope, + join, + loopk, + phi, + } => match tag { HintTag::Scope => *scope, HintTag::Join => *join, HintTag::Loop => *loopk, @@ -119,10 +177,17 @@ impl HintKinds { } #[derive(Debug, Clone, PartialEq, Eq)] -enum HintSinkTarget { None, Stderr, Jsonl(String) } +enum HintSinkTarget { + None, + Stderr, + Jsonl(String), +} #[derive(Debug, Clone, PartialEq, Eq)] -struct HintCfg { sink: HintSinkTarget, kinds: HintKinds } +struct HintCfg { + sink: HintSinkTarget, + kinds: HintKinds, +} impl HintCfg { fn parse(spec: &str) -> Self { @@ -131,8 +196,15 @@ impl HintCfg { let mut saw_filter = false; for tok in spec.split('|').map(|s| s.trim()).filter(|s| !s.is_empty()) { let tl = tok.to_ascii_lowercase(); - if tl == "off" { sink = HintSinkTarget::None; kinds = HintKinds::None; continue; } - if tl == "trace" || tl == "stderr" { sink = HintSinkTarget::Stderr; continue; } + if tl == "off" { + sink = HintSinkTarget::None; + kinds = HintKinds::None; + continue; + } + if tl == "trace" || tl == "stderr" { + sink = HintSinkTarget::Stderr; + continue; + } if tl.starts_with("jsonl=") { sink = HintSinkTarget::Jsonl(tok[6..].trim().to_string()); continue; @@ -144,11 +216,42 @@ impl HintCfg { } // Filters match tl.as_str() { - "all" => { kinds = HintKinds::All; saw_filter = true; } - "scope" => kinds = merge_kind(kinds, |k| HintKinds::Some { scope: true, join: matches!(k, HintKinds::Some{ join: true, .. } | HintKinds::All), loopk: matches!(k, HintKinds::Some{ loopk: true, .. } | HintKinds::All), phi: matches!(k, HintKinds::Some{ phi: true, .. } | HintKinds::All) }), - "join" => kinds = merge_kind(kinds, |k| HintKinds::Some { scope: matches!(k, HintKinds::Some{ scope: true, .. } | HintKinds::All), join: true, loopk: matches!(k, HintKinds::Some{ loopk: true, .. } | HintKinds::All), phi: matches!(k, HintKinds::Some{ phi: true, .. } | HintKinds::All) }), - "loop" => kinds = merge_kind(kinds, |k| HintKinds::Some { scope: matches!(k, HintKinds::Some{ scope: true, .. } | HintKinds::All), join: matches!(k, HintKinds::Some{ join: true, .. } | HintKinds::All), loopk: true, phi: matches!(k, HintKinds::Some{ phi: true, .. } | HintKinds::All) }), - "phi" => kinds = merge_kind(kinds, |k| HintKinds::Some { scope: matches!(k, HintKinds::Some{ scope: true, .. } | HintKinds::All), join: matches!(k, HintKinds::Some{ join: true, .. } | HintKinds::All), loopk: matches!(k, HintKinds::Some{ loopk: true, .. } | HintKinds::All), phi: true }), + "all" => { + kinds = HintKinds::All; + saw_filter = true; + } + "scope" => { + kinds = merge_kind(kinds, |k| HintKinds::Some { + scope: true, + join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All), + loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All), + phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All), + }) + } + "join" => { + kinds = merge_kind(kinds, |k| HintKinds::Some { + scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All), + join: true, + loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All), + phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All), + }) + } + "loop" => { + kinds = merge_kind(kinds, |k| HintKinds::Some { + scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All), + join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All), + loopk: true, + phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All), + }) + } + "phi" => { + kinds = merge_kind(kinds, |k| HintKinds::Some { + scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All), + join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All), + loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All), + phi: true, + }) + } _ => {} } } @@ -185,8 +288,13 @@ fn append_jsonl(path: &str, hint: &HintKind) -> std::io::Result<()> { HintKind::NoEmptyPhi => obj["value"] = serde_json::json!({"phi": "no_empty"}), } let line = obj.to_string(); - if let Some(dir) = std::path::Path::new(path).parent() { let _ = std::fs::create_dir_all(dir); } - let mut f = std::fs::OpenOptions::new().create(true).append(true).open(path)?; + if let Some(dir) = std::path::Path::new(path).parent() { + let _ = std::fs::create_dir_all(dir); + } + let mut f = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path)?; writeln!(f, "{}", line)?; Ok(()) } diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index 89052bce..1b81b0fd 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -5,7 +5,7 @@ */ use super::{EffectMask, ValueId}; -use crate::mir::definitions::Callee; // Import Callee from unified definitions +use crate::mir::definitions::Callee; // Import Callee from unified definitions use crate::mir::types::{ BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, }; @@ -67,8 +67,8 @@ pub enum MirInstruction { /// - callee: None -> Fall back to legacy string-based resolution Call { dst: Option, - func: ValueId, // Legacy: string-based resolution (deprecated) - callee: Option, // New: type-safe resolution (preferred) + func: ValueId, // Legacy: string-based resolution (deprecated) + callee: Option, // New: type-safe resolution (preferred) args: Vec, effects: EffectMask, }, @@ -306,8 +306,6 @@ pub enum MirInstruction { }, } - - // Method implementations have been moved to src/mir/instruction/methods.rs #[path = "instruction/methods.rs"] mod methods; diff --git a/src/mir/instruction/methods.rs b/src/mir/instruction/methods.rs index 8efa72b7..1f7f15ea 100644 --- a/src/mir/instruction/methods.rs +++ b/src/mir/instruction/methods.rs @@ -185,7 +185,9 @@ impl MirInstruction { MirInstruction::Return { value } => value.map(|v| vec![v]).unwrap_or_default(), - MirInstruction::Call { func, callee, args, .. } => { + MirInstruction::Call { + func, callee, args, .. + } => { // func は legacy 経路では「関数値」を指すが、現在の unified 経路では // callee にメタ情報が入り、func はダミー (0) になることがある。 // callee が None のときだけ func を SSA 値として扱い、それ以外 @@ -246,7 +248,6 @@ impl MirInstruction { } } - impl ConstValue { /* /// Convert to NyashValue diff --git a/src/mir/instruction/tests.rs b/src/mir/instruction/tests.rs index b9c108f9..b78fd0d5 100644 --- a/src/mir/instruction/tests.rs +++ b/src/mir/instruction/tests.rs @@ -47,7 +47,7 @@ fn test_call_instruction() { let inst = MirInstruction::Call { dst: Some(dst), func, - callee: None, // Legacy mode for test + callee: None, // Legacy mode for test args: vec![arg1, arg2], effects: EffectMask::IO, }; @@ -197,4 +197,4 @@ fn test_extern_call_instruction() { assert_eq!(void_inst.dst_value(), None); assert_eq!(void_inst.used_values(), vec![arg1]); -} \ No newline at end of file +} diff --git a/src/mir/instruction_kinds/mod.rs b/src/mir/instruction_kinds/mod.rs index 46333e84..54cc6a88 100644 --- a/src/mir/instruction_kinds/mod.rs +++ b/src/mir/instruction_kinds/mod.rs @@ -59,16 +59,25 @@ pub struct ConstInst { impl ConstInst { pub fn from_mir(i: &MirInstruction) -> Option { match i { - MirInstruction::Const { dst, value } => Some(ConstInst { dst: *dst, value: value.clone() }), + MirInstruction::Const { dst, value } => Some(ConstInst { + dst: *dst, + value: value.clone(), + }), _ => None, } } } impl InstructionMeta for ConstInst { - fn effects(&self) -> EffectMask { EffectMask::PURE } - fn dst(&self) -> Option { Some(self.dst) } - fn used(&self) -> Vec { Vec::new() } + fn effects(&self) -> EffectMask { + EffectMask::PURE + } + fn dst(&self) -> Option { + Some(self.dst) + } + fn used(&self) -> Vec { + Vec::new() + } } // ---- BinOp ---- @@ -83,103 +92,262 @@ pub struct BinOpInst { impl BinOpInst { pub fn from_mir(i: &MirInstruction) -> Option { match i { - MirInstruction::BinOp { dst, op, lhs, rhs } => Some(BinOpInst { dst: *dst, op: *op, lhs: *lhs, rhs: *rhs }), + MirInstruction::BinOp { dst, op, lhs, rhs } => Some(BinOpInst { + dst: *dst, + op: *op, + lhs: *lhs, + rhs: *rhs, + }), _ => None, } } } impl InstructionMeta for BinOpInst { - fn effects(&self) -> EffectMask { EffectMask::PURE } - fn dst(&self) -> Option { Some(self.dst) } - fn used(&self) -> Vec { vec![self.lhs, self.rhs] } + fn effects(&self) -> EffectMask { + EffectMask::PURE + } + fn dst(&self) -> Option { + Some(self.dst) + } + fn used(&self) -> Vec { + vec![self.lhs, self.rhs] + } } // ---- Helper delegation for MirInstruction methods ---- pub fn effects_via_meta(i: &MirInstruction) -> Option { - if let Some(k) = ConstInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = BinOpInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = UnaryOpInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = CompareInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = LoadInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = CastInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = TypeOpInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = ArrayGetInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = PhiInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = NewBoxInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = StoreInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = ArraySetInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = ReturnInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = BranchInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = JumpInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = PrintInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = DebugInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = TypeCheckInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = CopyInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = NopInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = ThrowInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = CatchInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = SafepointInst::from_mir(i) { return Some(k.effects()); } - if let Some(k) = NewClosureInst::from_mir(i) { return Some(k.effects()); } + if let Some(k) = ConstInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = BinOpInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = UnaryOpInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = CompareInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = LoadInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = CastInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = TypeOpInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = ArrayGetInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = PhiInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = NewBoxInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = StoreInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = ArraySetInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = ReturnInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = BranchInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = JumpInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = PrintInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = DebugInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = TypeCheckInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = CopyInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = NopInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = ThrowInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = CatchInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = SafepointInst::from_mir(i) { + return Some(k.effects()); + } + if let Some(k) = NewClosureInst::from_mir(i) { + return Some(k.effects()); + } None } pub fn dst_via_meta(i: &MirInstruction) -> Option { - if let Some(k) = ConstInst::from_mir(i) { return k.dst(); } - if let Some(k) = BinOpInst::from_mir(i) { return k.dst(); } - if let Some(k) = UnaryOpInst::from_mir(i) { return k.dst(); } - if let Some(k) = CompareInst::from_mir(i) { return k.dst(); } - if let Some(k) = LoadInst::from_mir(i) { return k.dst(); } - if let Some(k) = CastInst::from_mir(i) { return k.dst(); } - if let Some(k) = TypeOpInst::from_mir(i) { return k.dst(); } - if let Some(k) = ArrayGetInst::from_mir(i) { return k.dst(); } - if let Some(k) = PhiInst::from_mir(i) { return k.dst(); } - if let Some(k) = NewBoxInst::from_mir(i) { return k.dst(); } - if let Some(_k) = StoreInst::from_mir(i) { return None; } - if let Some(_k) = ArraySetInst::from_mir(i) { return None; } - if let Some(_k) = ReturnInst::from_mir(i) { return None; } - if let Some(_k) = BranchInst::from_mir(i) { return None; } - if let Some(_k) = JumpInst::from_mir(i) { return None; } - if let Some(_k) = PrintInst::from_mir(i) { return None; } - if let Some(_k) = DebugInst::from_mir(i) { return None; } - if let Some(k) = CallLikeInst::from_mir(i) { return k.dst(); } - if let Some(k) = TypeCheckInst::from_mir(i) { return k.dst(); } - if let Some(k) = CopyInst::from_mir(i) { return k.dst(); } - if let Some(_k) = NopInst::from_mir(i) { return None; } - if let Some(_k) = ThrowInst::from_mir(i) { return None; } - if let Some(k) = CatchInst::from_mir(i) { return k.dst(); } - if let Some(_k) = SafepointInst::from_mir(i) { return None; } - if let Some(k) = NewClosureInst::from_mir(i) { return k.dst(); } + if let Some(k) = ConstInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = BinOpInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = UnaryOpInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = CompareInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = LoadInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = CastInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = TypeOpInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = ArrayGetInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = PhiInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = NewBoxInst::from_mir(i) { + return k.dst(); + } + if let Some(_k) = StoreInst::from_mir(i) { + return None; + } + if let Some(_k) = ArraySetInst::from_mir(i) { + return None; + } + if let Some(_k) = ReturnInst::from_mir(i) { + return None; + } + if let Some(_k) = BranchInst::from_mir(i) { + return None; + } + if let Some(_k) = JumpInst::from_mir(i) { + return None; + } + if let Some(_k) = PrintInst::from_mir(i) { + return None; + } + if let Some(_k) = DebugInst::from_mir(i) { + return None; + } + if let Some(k) = CallLikeInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = TypeCheckInst::from_mir(i) { + return k.dst(); + } + if let Some(k) = CopyInst::from_mir(i) { + return k.dst(); + } + if let Some(_k) = NopInst::from_mir(i) { + return None; + } + if let Some(_k) = ThrowInst::from_mir(i) { + return None; + } + if let Some(k) = CatchInst::from_mir(i) { + return k.dst(); + } + if let Some(_k) = SafepointInst::from_mir(i) { + return None; + } + if let Some(k) = NewClosureInst::from_mir(i) { + return k.dst(); + } None } pub fn used_via_meta(i: &MirInstruction) -> Option> { - if let Some(k) = ConstInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = BinOpInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = UnaryOpInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = CompareInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = LoadInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = CastInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = TypeOpInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = ArrayGetInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = PhiInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = NewBoxInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = StoreInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = ArraySetInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = ReturnInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = BranchInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = JumpInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = PrintInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = DebugInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = CallLikeInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = TypeCheckInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = CopyInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = NopInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = ThrowInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = CatchInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = SafepointInst::from_mir(i) { return Some(k.used()); } - if let Some(k) = NewClosureInst::from_mir(i) { return Some(k.used()); } + if let Some(k) = ConstInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = BinOpInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = UnaryOpInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = CompareInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = LoadInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = CastInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = TypeOpInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = ArrayGetInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = PhiInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = NewBoxInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = StoreInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = ArraySetInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = ReturnInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = BranchInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = JumpInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = PrintInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = DebugInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = CallLikeInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = TypeCheckInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = CopyInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = NopInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = ThrowInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = CatchInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = SafepointInst::from_mir(i) { + return Some(k.used()); + } + if let Some(k) = NewClosureInst::from_mir(i) { + return Some(k.used()); + } None } @@ -548,23 +716,55 @@ inst_meta! { // ---- Call-like (dst/used only; effects fallback in MirInstruction) ---- #[derive(Debug, Clone)] pub enum CallLikeInst { - Call { dst: Option, func: ValueId, args: Vec }, - BoxCall { dst: Option, box_val: ValueId, args: Vec }, - PluginInvoke { dst: Option, box_val: ValueId, args: Vec }, - ExternCall { dst: Option, args: Vec }, + Call { + dst: Option, + func: ValueId, + args: Vec, + }, + BoxCall { + dst: Option, + box_val: ValueId, + args: Vec, + }, + PluginInvoke { + dst: Option, + box_val: ValueId, + args: Vec, + }, + ExternCall { + dst: Option, + args: Vec, + }, } impl CallLikeInst { pub fn from_mir(i: &MirInstruction) -> Option { match i { - MirInstruction::Call { dst, func, args, .. } => - Some(CallLikeInst::Call { dst: *dst, func: *func, args: args.clone() }), - MirInstruction::BoxCall { dst, box_val, args, .. } => - Some(CallLikeInst::BoxCall { dst: *dst, box_val: *box_val, args: args.clone() }), - MirInstruction::PluginInvoke { dst, box_val, args, .. } => - Some(CallLikeInst::PluginInvoke { dst: *dst, box_val: *box_val, args: args.clone() }), - MirInstruction::ExternCall { dst, args, .. } => - Some(CallLikeInst::ExternCall { dst: *dst, args: args.clone() }), + MirInstruction::Call { + dst, func, args, .. + } => Some(CallLikeInst::Call { + dst: *dst, + func: *func, + args: args.clone(), + }), + MirInstruction::BoxCall { + dst, box_val, args, .. + } => Some(CallLikeInst::BoxCall { + dst: *dst, + box_val: *box_val, + args: args.clone(), + }), + MirInstruction::PluginInvoke { + dst, box_val, args, .. + } => Some(CallLikeInst::PluginInvoke { + dst: *dst, + box_val: *box_val, + args: args.clone(), + }), + MirInstruction::ExternCall { dst, args, .. } => Some(CallLikeInst::ExternCall { + dst: *dst, + args: args.clone(), + }), _ => None, } } diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 8de92327..b617fb16 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -9,58 +9,56 @@ pub mod aot_plan_import; pub mod basic_block; pub mod builder; -pub mod definitions; // Unified MIR definitions (MirCall, Callee, etc.) +pub mod definitions; // Unified MIR definitions (MirCall, Callee, etc.) pub mod effect; pub mod function; pub mod instruction; -pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp) pub mod instruction_introspection; // Introspection helpers for tests (instruction names) -pub mod types; // core MIR enums (ConstValue, Ops, MirType) +pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp) pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready) pub mod loop_builder; // SSA loop construction with phi nodes -pub mod ssot; // Shared helpers (SSOT) for instruction lowering pub mod optimizer; +pub mod ssot; // Shared helpers (SSOT) for instruction lowering +pub mod types; // core MIR enums (ConstValue, Ops, MirType) pub mod utils; // Phase 15 control flow utilities for root treatment -// pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range) -pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only) -pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind) + // pub mod lowerers; // reserved: Stage-3 loop lowering (while/for-range) +pub mod control_form; +pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ) +pub mod hints; // scaffold: zero-cost guidance (no-op) pub mod optimizer_passes; // optimizer passes (normalize/diagnostics) pub mod optimizer_stats; // extracted stats struct pub mod passes; +pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only) pub mod printer; -pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ) mod printer_helpers; // internal helpers extracted from printer.rs -pub mod hints; // scaffold: zero-cost guidance (no-op) +pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind) pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs) pub mod value_id; pub mod value_kind; // Phase 26-A: ValueId型安全化 pub mod verification; -pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) -pub mod control_form; // Phase 25.1f: Loop/If 共通ビュー(ControlForm) +pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) // Phase 25.1f: Loop/If 共通ビュー(ControlForm) // Re-export main types for easy access pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator}; pub use builder::MirBuilder; -pub use definitions::{Callee, CallFlags, MirCall}; // Unified call definitions +pub use definitions::{CallFlags, Callee, MirCall}; // Unified call definitions pub use effect::{Effect, EffectMask}; pub use function::{FunctionSignature, MirFunction, MirModule}; pub use instruction::MirInstruction; -pub use types::{ - BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, -}; pub use optimizer::MirOptimizer; pub use printer::MirPrinter; pub use slot_registry::{BoxTypeId, MethodSlot}; +pub use types::{ + BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, +}; pub use value_id::{LocalId, ValueId, ValueIdGenerator}; pub use value_kind::{MirValueKind, TypedValueId}; // Phase 26-A: ValueId型安全化 pub use verification::MirVerifier; pub use verification_types::VerificationError; // Phase 15 control flow utilities (段階的根治戦略) pub use utils::{ - is_current_block_terminated, - capture_actual_predecessor_and_jump, - collect_phi_incoming_if_reachable, - execute_statement_with_termination_check, + capture_actual_predecessor_and_jump, collect_phi_incoming_if_reachable, + execute_statement_with_termination_check, is_current_block_terminated, }; /// MIR compilation result diff --git a/src/mir/optimizer.rs b/src/mir/optimizer.rs index c9ab6ef7..208e682a 100644 --- a/src/mir/optimizer.rs +++ b/src/mir/optimizer.rs @@ -116,7 +116,11 @@ impl MirOptimizer { // Pass 7 (optional): Core-13 pure normalization if crate::config::env::mir_core13_pure() { - stats.merge(crate::mir::optimizer_passes::normalize_core13_pure::normalize_pure_core13(self, module)); + stats.merge( + crate::mir::optimizer_passes::normalize_core13_pure::normalize_pure_core13( + self, module, + ), + ); } if self.debug { @@ -134,8 +138,6 @@ impl MirOptimizer { stats } - - /// Convert instruction to string key for CSE #[allow(dead_code)] fn instruction_to_key(&self, instruction: &MirInstruction) -> String { @@ -159,7 +161,6 @@ impl MirOptimizer { _ => format!("other_{:?}", instruction), } } - } impl MirOptimizer { @@ -275,13 +276,9 @@ impl Default for MirOptimizer { } } - /// Diagnostics: identify unlowered type-ops embedded as strings in Call/BoxCall #[allow(dead_code)] -fn diagnose_unlowered_type_ops( - optimizer: &MirOptimizer, - module: &MirModule, -) -> OptimizationStats { +fn diagnose_unlowered_type_ops(optimizer: &MirOptimizer, module: &MirModule) -> OptimizationStats { let mut stats = OptimizationStats::new(); let diag_on = optimizer.debug || crate::config::env::opt_diag(); for (fname, function) in &module.functions { @@ -354,9 +351,8 @@ fn diagnose_unlowered_type_ops( #[allow(dead_code)] fn diagnose_legacy_instructions(module: &MirModule, debug: bool) -> OptimizationStats { let mut stats = OptimizationStats::new(); - let diag_on = debug - || crate::config::env::opt_diag() - || crate::config::env::opt_diag_forbid_legacy(); + let diag_on = + debug || crate::config::env::opt_diag() || crate::config::env::opt_diag_forbid_legacy(); for (fname, function) in &module.functions { let mut count = 0usize; for (_bb, block) in &function.blocks { diff --git a/src/mir/optimizer_passes/diagnostics.rs b/src/mir/optimizer_passes/diagnostics.rs index 29043bbc..edad6989 100644 --- a/src/mir/optimizer_passes/diagnostics.rs +++ b/src/mir/optimizer_passes/diagnostics.rs @@ -41,7 +41,7 @@ pub fn diagnose_unlowered_type_ops( if let Some(b) = function.blocks.get(&bb) { if idx < b.instructions.len() { if let MirInstruction::Const { - value: crate::mir::ConstValue::String(s), + value: crate::mir::ConstValue::String(s), .. } = &b.instructions[idx] { diff --git a/src/mir/optimizer_passes/mod.rs b/src/mir/optimizer_passes/mod.rs index 9d9c6144..c560d1f5 100644 --- a/src/mir/optimizer_passes/mod.rs +++ b/src/mir/optimizer_passes/mod.rs @@ -2,5 +2,5 @@ pub mod boxfield; pub mod diagnostics; pub mod intrinsics; pub mod normalize; -pub mod reorder; pub mod normalize_core13_pure; +pub mod reorder; diff --git a/src/mir/optimizer_passes/normalize.rs b/src/mir/optimizer_passes/normalize.rs index 0db5e7bd..5d62cbf7 100644 --- a/src/mir/optimizer_passes/normalize.rs +++ b/src/mir/optimizer_passes/normalize.rs @@ -26,9 +26,14 @@ pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> O for fname in func_names { if idemp_enabled() { let key = idemp_key(pass_name, &fname); - if idemp_already_done(module, &key) { continue; } + if idemp_already_done(module, &key) { + continue; + } } - let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue }; + let function = match module.functions.get_mut(&fname) { + Some(f) => f, + None => continue, + }; for (_bb, block) in &mut function.blocks { for inst in &mut block.instructions { if let I::BoxCall { @@ -51,7 +56,10 @@ pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> O } } } - if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); } + if idemp_enabled() { + let key = idemp_key(pass_name, &fname); + idemp_mark(module, key); + } } stats } @@ -67,9 +75,14 @@ pub fn normalize_python_helper_calls( for fname in func_names { if idemp_enabled() { let key = idemp_key(pass_name, &fname); - if idemp_already_done(module, &key) { continue; } + if idemp_already_done(module, &key) { + continue; + } } - let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue }; + let function = match module.functions.get_mut(&fname) { + Some(f) => f, + None => continue, + }; for (_bb, block) in &mut function.blocks { for inst in &mut block.instructions { if let I::PluginInvoke { @@ -93,7 +106,10 @@ pub fn normalize_python_helper_calls( } } } - if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); } + if idemp_enabled() { + let key = idemp_key(pass_name, &fname); + idemp_mark(module, key); + } } stats } @@ -117,9 +133,14 @@ pub fn normalize_legacy_instructions( for fname in func_names { if idemp_enabled() { let key = idemp_key(pass_name, &fname); - if idemp_already_done(module, &key) { continue; } + if idemp_already_done(module, &key) { + continue; + } } - let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue }; + let function = match module.functions.get_mut(&fname) { + Some(f) => f, + None => continue, + }; for (_bb, block) in &mut function.blocks { for inst in &mut block.instructions { match inst { @@ -384,7 +405,10 @@ pub fn normalize_legacy_instructions( } } } - if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); } + if idemp_enabled() { + let key = idemp_key(pass_name, &fname); + idemp_mark(module, key); + } } stats } @@ -400,9 +424,14 @@ pub fn normalize_ref_field_access( for fname in func_names { if idemp_enabled() { let key = idemp_key(pass_name, &fname); - if idemp_already_done(module, &key) { continue; } + if idemp_already_done(module, &key) { + continue; + } } - let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue }; + let function = match module.functions.get_mut(&fname) { + Some(f) => f, + None => continue, + }; for (_bb, block) in &mut function.blocks { let mut out: Vec = Vec::with_capacity(block.instructions.len() + 2); let old = std::mem::take(&mut block.instructions); @@ -509,7 +538,10 @@ pub fn normalize_ref_field_access( }); } } - if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); } + if idemp_enabled() { + let key = idemp_key(pass_name, &fname); + idemp_mark(module, key); + } } stats } diff --git a/src/mir/optimizer_passes/normalize_core13_pure.rs b/src/mir/optimizer_passes/normalize_core13_pure.rs index 1dd13e9f..6673393f 100644 --- a/src/mir/optimizer_passes/normalize_core13_pure.rs +++ b/src/mir/optimizer_passes/normalize_core13_pure.rs @@ -39,11 +39,18 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> }); stats.intrinsic_optimizations += 1; } - I::NewBox { dst, box_type, mut args } => { + I::NewBox { + dst, + box_type, + mut args, + } => { // prepend type name as Const String let ty_id = ValueId::new(function.next_value_id); function.next_value_id += 1; - out.push(I::Const { dst: ty_id, value: crate::mir::ConstValue::String(box_type) }); + out.push(I::Const { + dst: ty_id, + value: crate::mir::ConstValue::String(box_type), + }); let mut call_args = Vec::with_capacity(1 + args.len()); call_args.push(ty_id); call_args.append(&mut args); @@ -61,20 +68,44 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> crate::mir::UnaryOp::Neg => { let zero = ValueId::new(function.next_value_id); function.next_value_id += 1; - out.push(I::Const { dst: zero, value: crate::mir::ConstValue::Integer(0) }); - out.push(I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand }); + out.push(I::Const { + dst: zero, + value: crate::mir::ConstValue::Integer(0), + }); + out.push(I::BinOp { + dst, + op: BinaryOp::Sub, + lhs: zero, + rhs: operand, + }); } crate::mir::UnaryOp::Not => { let f = ValueId::new(function.next_value_id); function.next_value_id += 1; - out.push(I::Const { dst: f, value: crate::mir::ConstValue::Bool(false) }); - out.push(I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f }); + out.push(I::Const { + dst: f, + value: crate::mir::ConstValue::Bool(false), + }); + out.push(I::Compare { + dst, + op: CompareOp::Eq, + lhs: operand, + rhs: f, + }); } crate::mir::UnaryOp::BitNot => { let all1 = ValueId::new(function.next_value_id); function.next_value_id += 1; - out.push(I::Const { dst: all1, value: crate::mir::ConstValue::Integer(-1) }); - out.push(I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 }); + out.push(I::Const { + dst: all1, + value: crate::mir::ConstValue::Integer(-1), + }); + out.push(I::BinOp { + dst, + op: BinaryOp::BitXor, + lhs: operand, + rhs: all1, + }); } } stats.intrinsic_optimizations += 1; @@ -100,10 +131,17 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> args: vec![ptr, value], effects: EffectMask::WRITE, }, - I::NewBox { dst, box_type, mut args } => { + I::NewBox { + dst, + box_type, + mut args, + } => { let ty_id = ValueId::new(function.next_value_id); function.next_value_id += 1; - block.instructions.push(I::Const { dst: ty_id, value: ConstValue::String(box_type) }); + block.instructions.push(I::Const { + dst: ty_id, + value: ConstValue::String(box_type), + }); let mut call_args = Vec::with_capacity(1 + args.len()); call_args.push(ty_id); call_args.append(&mut args); @@ -119,20 +157,44 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> crate::mir::UnaryOp::Neg => { let zero = ValueId::new(function.next_value_id); function.next_value_id += 1; - block.instructions.push(I::Const { dst: zero, value: ConstValue::Integer(0) }); - I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand } + block.instructions.push(I::Const { + dst: zero, + value: ConstValue::Integer(0), + }); + I::BinOp { + dst, + op: BinaryOp::Sub, + lhs: zero, + rhs: operand, + } } crate::mir::UnaryOp::Not => { let f = ValueId::new(function.next_value_id); function.next_value_id += 1; - block.instructions.push(I::Const { dst: f, value: ConstValue::Bool(false) }); - I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f } + block.instructions.push(I::Const { + dst: f, + value: ConstValue::Bool(false), + }); + I::Compare { + dst, + op: CompareOp::Eq, + lhs: operand, + rhs: f, + } } crate::mir::UnaryOp::BitNot => { let all1 = ValueId::new(function.next_value_id); function.next_value_id += 1; - block.instructions.push(I::Const { dst: all1, value: ConstValue::Integer(-1) }); - I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 } + block.instructions.push(I::Const { + dst: all1, + value: ConstValue::Integer(-1), + }); + I::BinOp { + dst, + op: BinaryOp::BitXor, + lhs: operand, + rhs: all1, + } } }, other => other, diff --git a/src/mir/passes/cse.rs b/src/mir/passes/cse.rs index 5d225207..cddae833 100644 --- a/src/mir/passes/cse.rs +++ b/src/mir/passes/cse.rs @@ -41,10 +41,14 @@ fn cse_in_function(function: &mut MirFunction) -> usize { MirInstruction::BinOp { op, lhs, rhs, .. } => { // Only rewrite Add when both operands are numeric (avoid String + String) let allow = match op { - crate::mir::BinaryOp::Add => is_numeric(*lhs) && is_numeric(*rhs), + crate::mir::BinaryOp::Add => { + is_numeric(*lhs) && is_numeric(*rhs) + } _ => true, }; - if allow { *inst = MirInstruction::Copy { dst, src: existing }; } + if allow { + *inst = MirInstruction::Copy { dst, src: existing }; + } } MirInstruction::Compare { .. } | MirInstruction::UnaryOp { .. } diff --git a/src/mir/phi_core/body_local_phi_builder.rs b/src/mir/phi_core/body_local_phi_builder.rs index 047c304e..447370b4 100644 --- a/src/mir/phi_core/body_local_phi_builder.rs +++ b/src/mir/phi_core/body_local_phi_builder.rs @@ -228,7 +228,7 @@ mod tests { // Pinned variable always needs exit PHI let needs_phi = builder.should_generate_exit_phi( "s", - &["s".to_string()], // pinned + &["s".to_string()], // pinned &[], &[BasicBlockId(1), BasicBlockId(2)], ); @@ -246,7 +246,7 @@ mod tests { let needs_phi = builder.should_generate_exit_phi( "i", &[], - &["i".to_string()], // carrier + &["i".to_string()], // carrier &[BasicBlockId(1), BasicBlockId(2)], ); @@ -265,12 +265,8 @@ mod tests { let builder = BodyLocalPhiBuilder::new(classifier, inspector); // BodyLocalExit: defined in all exit preds → needs exit PHI - let needs_phi = builder.should_generate_exit_phi( - "x", - &[], - &[], - &[BasicBlockId(1), BasicBlockId(2)], - ); + let needs_phi = + builder.should_generate_exit_phi("x", &[], &[], &[BasicBlockId(1), BasicBlockId(2)]); assert!(needs_phi); } @@ -287,14 +283,10 @@ mod tests { // BodyLocalInternal: defined only in block 5, but exit preds are [2, 5] // → NO exit PHI (Option C fix!) - let needs_phi = builder.should_generate_exit_phi( - "ch", - &[], - &[], - &[BasicBlockId(2), BasicBlockId(5)], - ); + let needs_phi = + builder.should_generate_exit_phi("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]); - assert!(!needs_phi); // ← Skip exit PHI! + assert!(!needs_phi); // ← Skip exit PHI! } #[test] @@ -333,7 +325,7 @@ mod tests { assert!(phi_vars.contains(&"s".to_string())); assert!(phi_vars.contains(&"idx".to_string())); assert!(phi_vars.contains(&"n".to_string())); - assert!(!phi_vars.contains(&"ch".to_string())); // ← Filtered out! + assert!(!phi_vars.contains(&"ch".to_string())); // ← Filtered out! } #[test] @@ -358,13 +350,14 @@ mod tests { // Exit preds: [block 2 (early break), block 5 (after ch definition)] let exit_preds = vec![BasicBlockId(2), BasicBlockId(5)]; - let phi_vars = builder.filter_exit_phi_candidates(&all_vars, &pinned, &carrier, &exit_preds); + let phi_vars = + builder.filter_exit_phi_candidates(&all_vars, &pinned, &carrier, &exit_preds); // Expected: s, idx (ch filtered out!) assert_eq!(phi_vars.len(), 2); assert!(phi_vars.contains(&"s".to_string())); assert!(phi_vars.contains(&"idx".to_string())); - assert!(!phi_vars.contains(&"ch".to_string())); // ← Option C fix! + assert!(!phi_vars.contains(&"ch".to_string())); // ← Option C fix! } #[test] @@ -376,12 +369,7 @@ mod tests { let builder = BodyLocalPhiBuilder::new(classifier, inspector); - let class = builder.classify_variable( - "ch", - &[], - &[], - &[BasicBlockId(2), BasicBlockId(5)], - ); + let class = builder.classify_variable("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]); assert_eq!(class, LoopVarClass::BodyLocalInternal); assert!(!class.needs_exit_phi()); @@ -394,15 +382,12 @@ mod tests { let mut builder = BodyLocalPhiBuilder::new(classifier, inspector); // Test mutable access - builder.inspector_mut().record_definition("test", BasicBlockId(10)); + builder + .inspector_mut() + .record_definition("test", BasicBlockId(10)); // Verify recording worked - let class = builder.classify_variable( - "test", - &[], - &[], - &[BasicBlockId(10)], - ); + let class = builder.classify_variable("test", &[], &[], &[BasicBlockId(10)]); assert_eq!(class, LoopVarClass::BodyLocalExit); } @@ -421,12 +406,7 @@ mod tests { let builder = BodyLocalPhiBuilder::new(classifier, inspector); // Should be classified as BodyLocalInternal - let class = builder.classify_variable( - "__pin$42$@binop_lhs", - &[], - &[], - &[BasicBlockId(5)], - ); + let class = builder.classify_variable("__pin$42$@binop_lhs", &[], &[], &[BasicBlockId(5)]); assert_eq!(class, LoopVarClass::BodyLocalInternal); assert!(!class.needs_exit_phi()); @@ -435,12 +415,8 @@ mod tests { let all_vars = vec!["__pin$42$@binop_lhs".to_string(), "s".to_string()]; let pinned = vec!["s".to_string()]; - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &pinned, - &[], - &[BasicBlockId(5)], - ); + let phi_vars = + builder.filter_exit_phi_candidates(&all_vars, &pinned, &[], &[BasicBlockId(5)]); // Only s should remain assert_eq!(phi_vars.len(), 1); diff --git a/src/mir/phi_core/common.rs b/src/mir/phi_core/common.rs index 1efa48e0..4e0baecf 100644 --- a/src/mir/phi_core/common.rs +++ b/src/mir/phi_core/common.rs @@ -16,11 +16,19 @@ pub fn debug_verify_phi_inputs( ) { use std::collections::HashSet; // Always compute when env toggle is set; otherwise no-op in release use. - let verify_on = std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()) - .map(|v| v == "1" || v == "true" || v == "on").unwrap_or(false) - || std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()) - .map(|v| v == "1" || v == "true" || v == "on").unwrap_or(false); - if !verify_on { return; } + let verify_on = std::env::var("HAKO_PHI_VERIFY") + .ok() + .map(|v| v.to_ascii_lowercase()) + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false) + || std::env::var("NYASH_PHI_VERIFY") + .ok() + .map(|v| v.to_ascii_lowercase()) + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false); + if !verify_on { + return; + } // Rebuild CFG to avoid stale predecessor sets let mut func = function.clone(); diff --git a/src/mir/phi_core/conservative.rs b/src/mir/phi_core/conservative.rs index 44f09ac8..8fac46d1 100644 --- a/src/mir/phi_core/conservative.rs +++ b/src/mir/phi_core/conservative.rs @@ -38,11 +38,8 @@ impl ConservativeMerge { all_vars.extend(else_map.keys().cloned()); } - let changed = crate::mir::phi_core::if_phi::compute_modified_names( - pre_if, - then_end, - else_end_opt, - ); + let changed = + crate::mir::phi_core::if_phi::compute_modified_names(pre_if, then_end, else_end_opt); let changed_vars = changed.into_iter().collect(); Self { @@ -102,7 +99,12 @@ impl ConservativeMerge { } /// Debug trace 出力(Conservative PHI生成の可視化) - pub fn trace_if_enabled(&self, pre_if: &HashMap, then_end: &HashMap, else_end_opt: &Option>) { + pub fn trace_if_enabled( + &self, + pre_if: &HashMap, + then_end: &HashMap, + else_end_opt: &Option>, + ) { let trace_conservative = std::env::var("NYASH_CONSERVATIVE_PHI_TRACE") .ok() .as_deref() diff --git a/src/mir/phi_core/header_phi_builder.rs b/src/mir/phi_core/header_phi_builder.rs index 5cfff839..719716a1 100644 --- a/src/mir/phi_core/header_phi_builder.rs +++ b/src/mir/phi_core/header_phi_builder.rs @@ -240,9 +240,7 @@ impl HeaderPhiBuilder { /// } /// ``` pub fn find_pinned_phi(&self, var_name: &str) -> Option<&PinnedPhiInfo> { - self.pinned_phis - .iter() - .find(|phi| phi.var_name == var_name) + self.pinned_phis.iter().find(|phi| phi.var_name == var_name) } /// Find carrier PHI by variable name @@ -354,12 +352,7 @@ mod tests { #[test] fn test_prepare_pinned_phi() { let mut builder = HeaderPhiBuilder::new(); - builder.prepare_pinned_phi( - "x".to_string(), - ValueId(10), - ValueId(1), - ValueId(2), - ); + builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2)); assert_eq!(builder.pinned_phi_count(), 1); let phi = &builder.pinned_phis()[0]; @@ -372,12 +365,7 @@ mod tests { #[test] fn test_prepare_carrier_phi() { let mut builder = HeaderPhiBuilder::new(); - builder.prepare_carrier_phi( - "i".to_string(), - ValueId(20), - ValueId(0), - ValueId(3), - ); + builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3)); assert_eq!(builder.carrier_phi_count(), 1); let phi = &builder.carrier_phis()[0]; diff --git a/src/mir/phi_core/if_phi.rs b/src/mir/phi_core/if_phi.rs index 775571f8..61bc3943 100644 --- a/src/mir/phi_core/if_phi.rs +++ b/src/mir/phi_core/if_phi.rs @@ -48,7 +48,11 @@ pub fn extract_assigned_var(ast: &ASTNode) -> Option { ASTNode::Program { statements, .. } => { statements.last().and_then(|st| extract_assigned_var(st)) } - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown(), @@ -80,13 +84,25 @@ pub fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::HashSet< } } ASTNode::Program { statements, .. } => { - for s in statements { collect_assigned_vars(s, out); } + for s in statements { + collect_assigned_vars(s, out); + } } - ASTNode::If { then_body, else_body, .. } => { - let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() }; + ASTNode::If { + then_body, + else_body, + .. + } => { + let tp = ASTNode::Program { + statements: then_body.clone(), + span: crate::ast::Span::unknown(), + }; collect_assigned_vars(&tp, out); if let Some(eb) = else_body { - let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() }; + let ep = ASTNode::Program { + statements: eb.clone(), + span: crate::ast::Span::unknown(), + }; collect_assigned_vars(&ep, out); } } @@ -104,9 +120,13 @@ pub fn compute_modified_names( use std::collections::BTreeSet; // 決定的順序のためBTreeSet使用 let mut names: BTreeSet<&str> = BTreeSet::new(); - for k in then_map_end.keys() { names.insert(k.as_str()); } + for k in then_map_end.keys() { + names.insert(k.as_str()); + } if let Some(emap) = else_map_end_opt.as_ref() { - for k in emap.keys() { names.insert(k.as_str()); } + for k in emap.keys() { + names.insert(k.as_str()); + } } let mut changed: Vec = Vec::new(); // アルファベット順で決定的にイテレート @@ -181,18 +201,19 @@ pub fn merge_modified_at_merge_with( // Build incoming pairs from reachable predecessors only let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new(); // Only include reachable predecessors; do not synthesize else_block when unreachable. - if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); } - if let Some(ep) = else_pred_opt { inputs.push((ep, else_v)); } + if let Some(tp) = then_pred_opt { + inputs.push((tp, then_v)); + } + if let Some(ep) = else_pred_opt { + inputs.push((ep, else_v)); + } match inputs.len() { 0 => {} 1 => { let (_pred, v) = inputs[0]; if trace { - eprintln!( - "[if-trace] merge bind var={} v={:?} (single pred)", - name, v - ); + eprintln!("[if-trace] merge bind var={} v={:?} (single pred)", name, v); } ops.update_var(name, v); } @@ -201,10 +222,7 @@ pub fn merge_modified_at_merge_with( let dst = ops.new_value(); ops.emit_phi_at_block_start(merge_bb, dst, inputs)?; if trace { - eprintln!( - "[if-trace] merge phi var={} dst={:?}", - name, dst - ); + eprintln!("[if-trace] merge phi var={} dst={:?}", name, dst); } ops.update_var(name, dst); } diff --git a/src/mir/phi_core/local_scope_inspector.rs b/src/mir/phi_core/local_scope_inspector.rs index cb0f205f..a277b877 100644 --- a/src/mir/phi_core/local_scope_inspector.rs +++ b/src/mir/phi_core/local_scope_inspector.rs @@ -17,7 +17,6 @@ /// This box has ONE job: track definition locations. /// It doesn't know about loops, PHI nodes, or exit blocks. /// It's a pure data structure that other boxes can query. - use crate::mir::{BasicBlockId, ValueId}; use std::collections::{HashMap, HashSet}; @@ -62,11 +61,7 @@ impl LocalScopeInspectorBox { /// ]); /// inspector.record_snapshot(BasicBlockId::new(5), &snapshot); /// ``` - pub fn record_snapshot( - &mut self, - block: BasicBlockId, - vars: &HashMap, - ) { + pub fn record_snapshot(&mut self, block: BasicBlockId, vars: &HashMap) { for var_name in vars.keys() { self.record_definition(var_name, block); } @@ -117,7 +112,9 @@ impl LocalScopeInspectorBox { /// ``` pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool { if let Some(defining_blocks) = self.var_definitions.get(var_name) { - required_blocks.iter().all(|block| defining_blocks.contains(block)) + required_blocks + .iter() + .all(|block| defining_blocks.contains(block)) } else { // Variable doesn't exist at all false @@ -191,10 +188,7 @@ mod tests { inspector.record_definition("i", BasicBlockId::new(5)); inspector.record_definition("i", BasicBlockId::new(7)); - let required = vec![ - BasicBlockId::new(2), - BasicBlockId::new(5), - ]; + let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)]; assert!(inspector.is_available_in_all("i", &required)); } @@ -206,10 +200,7 @@ mod tests { // "ch" only defined in block 5 inspector.record_definition("ch", BasicBlockId::new(5)); - let required = vec![ - BasicBlockId::new(2), - BasicBlockId::new(5), - ]; + let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)]; // Should fail because block 2 is missing assert!(!inspector.is_available_in_all("ch", &required)); @@ -249,7 +240,10 @@ mod tests { fn test_get_defining_blocks_unknown() { let inspector = LocalScopeInspectorBox::new(); - assert_eq!(inspector.get_defining_blocks("unknown"), Vec::::new()); + assert_eq!( + inspector.get_defining_blocks("unknown"), + Vec::::new() + ); } #[test] diff --git a/src/mir/phi_core/loop_phi.rs b/src/mir/phi_core/loop_phi.rs index 616d4cf7..65d83744 100644 --- a/src/mir/phi_core/loop_phi.rs +++ b/src/mir/phi_core/loop_phi.rs @@ -7,8 +7,8 @@ * - Phase 31.x 以降で段階的に削除予定。新しい PHI 実装をここに追加してはいけない。 */ -use crate::mir::{BasicBlockId, ValueId}; use crate::ast::ASTNode; +use crate::mir::{BasicBlockId, ValueId}; /// Loop-local placeholder of an incomplete PHI (header-time declaration). /// Moved from loop_builder to centralize PHI-related types. @@ -27,7 +27,9 @@ pub type SnapshotAt = (BasicBlockId, VarSnapshot); pub struct LoopPhiManager; impl LoopPhiManager { - pub fn new() -> Self { Self::default() } + pub fn new() -> Self { + Self::default() + } } /// Operations required from a loop builder to finalize PHIs. @@ -41,7 +43,12 @@ pub trait LoopPhiOps { ) -> Result<(), String>; fn update_var(&mut self, name: String, value: ValueId); fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option; - fn debug_verify_phi_inputs(&mut self, _merge_bb: BasicBlockId, _inputs: &[(BasicBlockId, ValueId)]) {} + fn debug_verify_phi_inputs( + &mut self, + _merge_bb: BasicBlockId, + _inputs: &[(BasicBlockId, ValueId)], + ) { + } /// PHI UseBeforeDef修正: preheaderブロックでCopy命令を先行生成 fn emit_copy_at_preheader( @@ -57,7 +64,9 @@ pub trait LoopPhiOps { &mut self, _block: BasicBlockId, _pred: BasicBlockId, - ) -> Result<(), String> { Ok(()) } + ) -> Result<(), String> { + Ok(()) + } } /// Finalize PHIs at loop exit (merge of break points and header fall-through). @@ -151,7 +160,7 @@ pub fn seal_incomplete_phis_with( // preheaderの値を使用する let latch_value = if value_after == phi.phi_id { // ループ不変変数:preheaderの値を使用 - phi.known_inputs[0].1 // preheaderからの初期値 + phi.known_inputs[0].1 // preheaderからの初期値 } else { value_after }; @@ -198,7 +207,7 @@ pub fn prepare_loop_variables_with( let inc = IncompletePhi { phi_id, var_name: var_name.clone(), - known_inputs: vec![(preheader_id, pre_copy)], // ensure def at preheader + known_inputs: vec![(preheader_id, pre_copy)], // ensure def at preheader }; // Insert an initial PHI at header with only the preheader input so that // the header condition reads the PHI value (first iteration = preheader). @@ -225,17 +234,31 @@ pub fn collect_carrier_assigns(node: &ASTNode, vars: &mut Vec, has_ctrl: } } } - ASTNode::Break { .. } | ASTNode::Continue { .. } => { *has_ctrl = true; } - ASTNode::If { then_body, else_body, .. } => { - let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() }; + ASTNode::Break { .. } | ASTNode::Continue { .. } => { + *has_ctrl = true; + } + ASTNode::If { + then_body, + else_body, + .. + } => { + let tp = ASTNode::Program { + statements: then_body.clone(), + span: crate::ast::Span::unknown(), + }; collect_carrier_assigns(&tp, vars, has_ctrl); if let Some(eb) = else_body { - let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() }; + let ep = ASTNode::Program { + statements: eb.clone(), + span: crate::ast::Span::unknown(), + }; collect_carrier_assigns(&ep, vars, has_ctrl); } } ASTNode::Program { statements, .. } => { - for s in statements { collect_carrier_assigns(s, vars, has_ctrl); } + for s in statements { + collect_carrier_assigns(s, vars, has_ctrl); + } } _ => {} } diff --git a/src/mir/phi_core/loop_snapshot_manager.rs b/src/mir/phi_core/loop_snapshot_manager.rs index cf067a6e..8255f1d8 100644 --- a/src/mir/phi_core/loop_snapshot_manager.rs +++ b/src/mir/phi_core/loop_snapshot_manager.rs @@ -106,11 +106,7 @@ impl LoopSnapshotManager { /// ```ignore /// manager.add_continue_snapshot(BasicBlockId(7), continue_vars); /// ``` - pub fn add_continue_snapshot( - &mut self, - block: BasicBlockId, - vars: HashMap, - ) { + pub fn add_continue_snapshot(&mut self, block: BasicBlockId, vars: HashMap) { self.continue_snapshots.push((block, vars)); } diff --git a/src/mir/phi_core/loop_var_classifier.rs b/src/mir/phi_core/loop_var_classifier.rs index 8ad1f0db..25de55d5 100644 --- a/src/mir/phi_core/loop_var_classifier.rs +++ b/src/mir/phi_core/loop_var_classifier.rs @@ -19,7 +19,6 @@ /// This box has ONE job: classify variables. /// It doesn't generate PHI nodes or modify IR. /// It's a pure decision box that other boxes can query. - use super::local_scope_inspector::LocalScopeInspectorBox; use crate::mir::BasicBlockId; @@ -82,7 +81,7 @@ impl LoopVarClass { LoopVarClass::Pinned => true, LoopVarClass::Carrier => true, LoopVarClass::BodyLocalExit => true, - LoopVarClass::BodyLocalInternal => false, // ← Option C: Skip exit PHI! + LoopVarClass::BodyLocalInternal => false, // ← Option C: Skip exit PHI! } } @@ -91,7 +90,7 @@ impl LoopVarClass { match self { LoopVarClass::Pinned => true, LoopVarClass::Carrier => true, - LoopVarClass::BodyLocalExit => false, // Body-local: no header PHI + LoopVarClass::BodyLocalExit => false, // Body-local: no header PHI LoopVarClass::BodyLocalInternal => false, } } @@ -261,7 +260,7 @@ mod tests { assert!(LoopVarClass::Pinned.needs_exit_phi()); assert!(LoopVarClass::Carrier.needs_exit_phi()); assert!(LoopVarClass::BodyLocalExit.needs_exit_phi()); - assert!(!LoopVarClass::BodyLocalInternal.needs_exit_phi()); // ← Option C! + assert!(!LoopVarClass::BodyLocalInternal.needs_exit_phi()); // ← Option C! } #[test] @@ -279,7 +278,7 @@ mod tests { let class = classifier.classify( "limit", - &["limit".to_string()], // pinned + &["limit".to_string()], // pinned &[], &inspector, &[], @@ -298,7 +297,7 @@ mod tests { let class = classifier.classify( "i", &[], - &["i".to_string()], // carrier + &["i".to_string()], // carrier &inspector, &[], ); @@ -322,16 +321,10 @@ mod tests { let exit_preds = vec![block_2, block_5]; - let class = classifier.classify( - "x", - &[], - &[], - &inspector, - &exit_preds, - ); + let class = classifier.classify("x", &[], &[], &inspector, &exit_preds); assert_eq!(class, LoopVarClass::BodyLocalExit); - assert!(class.needs_exit_phi()); // Should get exit PHI + assert!(class.needs_exit_phi()); // Should get exit PHI assert!(!class.needs_header_phi()); } @@ -344,20 +337,14 @@ mod tests { let block_2 = BasicBlockId::new(2); let block_5 = BasicBlockId::new(5); - inspector.record_definition("ch", block_5); // Only block 5! + inspector.record_definition("ch", block_5); // Only block 5! let exit_preds = vec![block_2, block_5]; - let class = classifier.classify( - "ch", - &[], - &[], - &inspector, - &exit_preds, - ); + let class = classifier.classify("ch", &[], &[], &inspector, &exit_preds); assert_eq!(class, LoopVarClass::BodyLocalInternal); - assert!(!class.needs_exit_phi()); // ← Option C: Skip exit PHI! + assert!(!class.needs_exit_phi()); // ← Option C: Skip exit PHI! assert!(!class.needs_header_phi()); } @@ -407,7 +394,7 @@ mod tests { // ch should NOT need exit PHI (Option C: prevents PHI pred mismatch!) assert_eq!(ch_class, LoopVarClass::BodyLocalInternal); - assert!(!ch_class.needs_exit_phi()); // ← This fixes the bug! + assert!(!ch_class.needs_exit_phi()); // ← This fixes the bug! } #[test] @@ -435,7 +422,10 @@ mod tests { assert_eq!(results.len(), 3); assert_eq!(results[0], ("i".to_string(), LoopVarClass::Carrier)); assert_eq!(results[1], ("n".to_string(), LoopVarClass::Pinned)); - assert_eq!(results[2], ("ch".to_string(), LoopVarClass::BodyLocalInternal)); + assert_eq!( + results[2], + ("ch".to_string(), LoopVarClass::BodyLocalInternal) + ); } #[test] @@ -469,7 +459,7 @@ mod tests { assert_eq!(candidates.len(), 2); assert!(candidates.contains(&"i".to_string())); assert!(candidates.contains(&"n".to_string())); - assert!(!candidates.contains(&"ch".to_string())); // ← Option C: ch is filtered out! + assert!(!candidates.contains(&"ch".to_string())); // ← Option C: ch is filtered out! } /// Test Task先生の発見: __pin$ temporary variables should be BodyLocalInternal @@ -490,26 +480,48 @@ mod tests { for pin_var in pin_vars { let class = classifier.classify( pin_var, - &[], // Not pinned - &[], // Not carrier + &[], // Not pinned + &[], // Not carrier &inspector, - &[], // No exit preds + &[], // No exit preds ); - assert_eq!(class, LoopVarClass::BodyLocalInternal, - "Variable '{}' should be BodyLocalInternal", pin_var); - assert!(!class.needs_exit_phi(), - "Variable '{}' should NOT need exit PHI", pin_var); - assert!(!class.needs_header_phi(), - "Variable '{}' should NOT need header PHI", pin_var); + assert_eq!( + class, + LoopVarClass::BodyLocalInternal, + "Variable '{}' should be BodyLocalInternal", + pin_var + ); + assert!( + !class.needs_exit_phi(), + "Variable '{}' should NOT need exit PHI", + pin_var + ); + assert!( + !class.needs_header_phi(), + "Variable '{}' should NOT need header PHI", + pin_var + ); } } #[test] fn test_description() { - assert_eq!(LoopVarClass::Pinned.description(), "Loop-crossing parameter"); - assert_eq!(LoopVarClass::Carrier.description(), "Loop-modified variable"); - assert_eq!(LoopVarClass::BodyLocalExit.description(), "Body-local (all exits)"); - assert_eq!(LoopVarClass::BodyLocalInternal.description(), "Body-local (partial exits)"); + assert_eq!( + LoopVarClass::Pinned.description(), + "Loop-crossing parameter" + ); + assert_eq!( + LoopVarClass::Carrier.description(), + "Loop-modified variable" + ); + assert_eq!( + LoopVarClass::BodyLocalExit.description(), + "Body-local (all exits)" + ); + assert_eq!( + LoopVarClass::BodyLocalInternal.description(), + "Body-local (partial exits)" + ); } } diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index d559bb75..25bc0c79 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -8,9 +8,9 @@ * Status: Always-on(LoopForm PHI v2 は既定実装。NYASH_LOOPFORM_PHI_V2 は互換目的のみで、挙動切り替えには使われない) */ -use crate::mir::{BasicBlockId, ValueId}; use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; use crate::mir::phi_core::phi_input_collector::PhiInputCollector; +use crate::mir::{BasicBlockId, ValueId}; use std::collections::HashMap; /// 📦 LoopForm Context - Box-First理論に基づくパラメータ予約明示化 @@ -50,19 +50,19 @@ impl LoopFormContext { #[derive(Debug, Clone)] pub struct CarrierVariable { pub name: String, - pub init_value: ValueId, // Initial value from preheader (local variable) - pub preheader_copy: ValueId, // Copy allocated in preheader block - pub header_phi: ValueId, // PHI node allocated in header block - pub latch_value: ValueId, // Updated value computed in latch (set during sealing) + pub init_value: ValueId, // Initial value from preheader (local variable) + pub preheader_copy: ValueId, // Copy allocated in preheader block + pub header_phi: ValueId, // PHI node allocated in header block + pub latch_value: ValueId, // Updated value computed in latch (set during sealing) } /// A pinned variable: not modified in loop body (loop-invariant, typically parameters) #[derive(Debug, Clone)] pub struct PinnedVariable { pub name: String, - pub param_value: ValueId, // Original parameter or loop-invariant value - pub preheader_copy: ValueId, // Copy allocated in preheader block - pub header_phi: ValueId, // PHI node allocated in header block + pub param_value: ValueId, // Original parameter or loop-invariant value + pub preheader_copy: ValueId, // Copy allocated in preheader block + pub header_phi: ValueId, // PHI node allocated in header block } /// LoopForm Meta-Box: Structured representation of loop SSA construction @@ -98,7 +98,7 @@ impl LoopFormBuilder { pinned: Vec::new(), preheader_id, header_id, - preheader_vars: HashMap::new(), // Will be set in prepare_structure() + preheader_vars: HashMap::new(), // Will be set in prepare_structure() } } @@ -118,7 +118,10 @@ impl LoopFormBuilder { self.preheader_vars = current_vars.clone(); if debug_enabled { - eprintln!("[loopform/prepare] === START prepare_structure() === {} variables", current_vars.len()); + eprintln!( + "[loopform/prepare] === START prepare_structure() === {} variables", + current_vars.len() + ); eprintln!("[loopform/prepare] Full variable list:"); let mut sorted_vars: Vec<_> = current_vars.iter().collect(); sorted_vars.sort_by_key(|(name, _)| name.as_str()); @@ -147,7 +150,10 @@ impl LoopFormBuilder { let max_existing_id = current_vars.values().map(|v| v.0).max().unwrap_or(0); if debug_enabled { - eprintln!("[loopform/prepare] Calling ensure_counter_after(max_existing_id={})", max_existing_id); + eprintln!( + "[loopform/prepare] Calling ensure_counter_after(max_existing_id={})", + max_existing_id + ); } ops.ensure_counter_after(max_existing_id)?; @@ -178,8 +184,10 @@ impl LoopFormBuilder { header_phi: ops.new_value(), // Allocate NOW }; if debug_enabled { - eprintln!("[loopform/prepare] PINNED: {} -> init={:?}, copy={:?}, phi={:?}", - name, value, pinned.preheader_copy, pinned.header_phi); + eprintln!( + "[loopform/prepare] PINNED: {} -> init={:?}, copy={:?}, phi={:?}", + name, value, pinned.preheader_copy, pinned.header_phi + ); } self.pinned.push(pinned); } else { @@ -193,8 +201,10 @@ impl LoopFormBuilder { latch_value: ValueId(0), // Will be set during seal (placeholder) }; if debug_enabled { - eprintln!("[loopform/prepare] CARRIER: {} -> init={:?}, copy={:?}, phi={:?}", - name, value, carrier.preheader_copy, carrier.header_phi); + eprintln!( + "[loopform/prepare] CARRIER: {} -> init={:?}, copy={:?}, phi={:?}", + name, value, carrier.preheader_copy, carrier.header_phi + ); } self.carriers.push(carrier); } @@ -217,26 +227,17 @@ impl LoopFormBuilder { /// 2. Carrier variables second /// /// This ordering ensures consistent ValueId allocation across runs. - pub fn emit_preheader( - &self, - ops: &mut O, - ) -> Result<(), String> { + pub fn emit_preheader(&self, ops: &mut O) -> Result<(), String> { ops.set_current_block(self.preheader_id)?; // Emit copies for pinned variables for pinned in &self.pinned { - ops.emit_copy( - pinned.preheader_copy, - pinned.param_value, - )?; + ops.emit_copy(pinned.preheader_copy, pinned.param_value)?; } // Emit copies for carrier variables for carrier in &self.carriers { - ops.emit_copy( - carrier.preheader_copy, - carrier.init_value, - )?; + ops.emit_copy(carrier.preheader_copy, carrier.init_value)?; } // Jump to header @@ -249,10 +250,7 @@ impl LoopFormBuilder { /// /// Creates incomplete PHI nodes with only preheader input. /// These will be completed in seal_phis() after loop body is lowered. - pub fn emit_header_phis( - &mut self, - ops: &mut O, - ) -> Result<(), String> { + pub fn emit_header_phis(&mut self, ops: &mut O) -> Result<(), String> { ops.set_current_block(self.header_id)?; // Emit PHIs for pinned variables @@ -277,8 +275,9 @@ impl LoopFormBuilder { // Also update all previous pin levels (__pin$1$@recv, __pin$2$@recv, etc.) // Extract the pin counter and update all lower levels if let Some(idx) = pinned.name.find("$") { - if let Some(end_idx) = pinned.name[idx+1..].find("$") { - if let Ok(counter) = pinned.name[idx+1..idx+1+end_idx].parse::() { + if let Some(end_idx) = pinned.name[idx + 1..].find("$") { + if let Ok(counter) = pinned.name[idx + 1..idx + 1 + end_idx].parse::() + { // Update all previous pin levels (1 through counter-1) for i in 1..counter { let alias = format!("__pin${}$@recv", i); @@ -321,7 +320,7 @@ impl LoopFormBuilder { ops: &mut O, latch_id: BasicBlockId, continue_snapshots: &[(BasicBlockId, HashMap)], - _writes: &std::collections::HashSet, // Step 5-1/5-2: Reserved for future optimization + _writes: &std::collections::HashSet, // Step 5-1/5-2: Reserved for future optimization ) -> Result<(), String> { let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); @@ -402,14 +401,18 @@ impl LoopFormBuilder { // They don't have explicit values at the latch block, so we use the header PHI // itself as the latch value (creating a self-referencing PHI that will be // optimized by PHI reduction). - carrier.latch_value = if carrier.name.starts_with("__pin$") && carrier.name.contains("$@") { - carrier.header_phi // Use the PHI value itself as the latch input - } else { - ops.get_variable_at_block(&carrier.name, latch_id) - .ok_or_else(|| { - format!("Carrier variable '{}' not found at latch block", carrier.name) - })? - }; + carrier.latch_value = + if carrier.name.starts_with("__pin$") && carrier.name.contains("$@") { + carrier.header_phi // Use the PHI value itself as the latch input + } else { + ops.get_variable_at_block(&carrier.name, latch_id) + .ok_or_else(|| { + format!( + "Carrier variable '{}' not found at latch block", + carrier.name + ) + })? + }; // Phase 26-B-3: Use PhiInputCollector for unified PHI input handling let mut collector = PhiInputCollector::new(); @@ -488,15 +491,27 @@ impl LoopFormBuilder { let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); if debug { eprintln!("[DEBUG/exit_phi] ====== Exit PHI Generation ======"); - eprintln!("[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}", - exit_id, self.header_id, branch_source_block); - eprintln!("[DEBUG/exit_phi] exit_snapshots.len() = {}", exit_snapshots.len()); + eprintln!( + "[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}", + exit_id, self.header_id, branch_source_block + ); + eprintln!( + "[DEBUG/exit_phi] exit_snapshots.len() = {}", + exit_snapshots.len() + ); for (i, (bb, snap)) in exit_snapshots.iter().enumerate() { - eprintln!("[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", - i, bb, snap.len()); + eprintln!( + "[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", + i, + bb, + snap.len() + ); } - eprintln!("[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}", - self.pinned.len(), self.carriers.len()); + eprintln!( + "[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}", + self.pinned.len(), + self.carriers.len() + ); } // Phase 25.2: LoopSnapshotMergeBox を使って exit PHI 統合 @@ -512,7 +527,8 @@ impl LoopFormBuilder { // 2. body_local_vars を収集(決定的順序のためBTreeSet使用) let mut body_local_names = Vec::new(); - let mut body_local_set: std::collections::BTreeSet = std::collections::BTreeSet::new(); + let mut body_local_set: std::collections::BTreeSet = + std::collections::BTreeSet::new(); for (_block_id, snapshot) in exit_snapshots { // 決定的順序のため、keysをソートしてからイテレート let mut sorted_keys: Vec<_> = snapshot.keys().collect(); @@ -537,7 +553,10 @@ impl LoopFormBuilder { } if debug && !body_local_names.is_empty() { - eprintln!("[DEBUG/exit_phi] Found {} body-local variables", body_local_names.len()); + eprintln!( + "[DEBUG/exit_phi] Found {} body-local variables", + body_local_names.len() + ); } // Option C: Body-local variables should NOT be added to header_vals! @@ -554,13 +573,19 @@ impl LoopFormBuilder { for (block_id, snapshot) in exit_snapshots { if !ops.block_exists(*block_id) { if debug { - eprintln!("[DEBUG/exit_phi] ⚠️ Skipping non-existent block {:?}", block_id); + eprintln!( + "[DEBUG/exit_phi] ⚠️ Skipping non-existent block {:?}", + block_id + ); } continue; } if !exit_preds.contains(block_id) { if debug { - eprintln!("[DEBUG/exit_phi] ⚠️ Skipping block {:?} (not in CFG predecessors)", block_id); + eprintln!( + "[DEBUG/exit_phi] ⚠️ Skipping block {:?} (not in CFG predecessors)", + block_id + ); } continue; } @@ -590,7 +615,10 @@ impl LoopFormBuilder { if exit_preds.contains(&branch_source_block) { inspector.record_snapshot(branch_source_block, &header_vals); if debug { - eprintln!("[DEBUG/exit_phi] Recorded header snapshot for block {:?}", branch_source_block); + eprintln!( + "[DEBUG/exit_phi] Recorded header snapshot for block {:?}", + branch_source_block + ); } } @@ -606,7 +634,7 @@ impl LoopFormBuilder { branch_source_block, &header_vals, &filtered_snapshots, - &exit_preds_vec, // ← 実際のCFG predecessorsを渡す + &exit_preds_vec, // ← 実際のCFG predecessorsを渡す &pinned_names, &carrier_names, inspector, @@ -620,15 +648,21 @@ impl LoopFormBuilder { collector.sanitize(); if debug { - eprintln!("[DEBUG/exit_phi] Variable '{}': {} inputs", var_name, collector.len()); + eprintln!( + "[DEBUG/exit_phi] Variable '{}': {} inputs", + var_name, + collector.len() + ); } // Phase 25.2: optimize_same_value() で最適化判定 if let Some(same_val) = collector.optimize_same_value() { // 全て同じ値 or 単一入力 → PHI 不要 if debug { - eprintln!("[DEBUG/exit_phi] Variable '{}': single/same value, direct binding to {:?}", - var_name, same_val); + eprintln!( + "[DEBUG/exit_phi] Variable '{}': single/same value, direct binding to {:?}", + var_name, same_val + ); } ops.update_var(var_name, same_val); } else { @@ -637,8 +671,12 @@ impl LoopFormBuilder { let phi_id = ops.new_value(); if debug { - eprintln!("[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", - phi_id, var_name, final_inputs.len()); + eprintln!( + "[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", + phi_id, + var_name, + final_inputs.len() + ); for (bb, val) in &final_inputs { eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); } @@ -672,7 +710,10 @@ pub trait LoopFormOps { /// 📦 Get actual CFG predecessors for a block (Hotfix 6: PHI input validation) /// Returns the set of blocks that actually branch to this block in the CFG. /// Used to validate exit PHI inputs against actual control flow. - fn get_block_predecessors(&self, block: BasicBlockId) -> std::collections::HashSet; + fn get_block_predecessors( + &self, + block: BasicBlockId, + ) -> std::collections::HashSet; /// Phase 26-A-4: ValueIdベースのパラメータ判定(型安全化) /// @@ -724,7 +765,10 @@ pub fn build_exit_phis_for_control( loopform: &LoopFormBuilder, ops: &mut O, form: &crate::mir::control_form::ControlForm, - exit_snapshots: &[(crate::mir::BasicBlockId, std::collections::HashMap)], + exit_snapshots: &[( + crate::mir::BasicBlockId, + std::collections::HashMap, + )], // Existing implementation requires branch_source_block, so we accept it as a parameter branch_source_block: crate::mir::BasicBlockId, ) -> Result<(), String> { @@ -756,7 +800,13 @@ pub fn build_exit_phis_for_control( // - Records pinned/carrier definitions in header // - Records filtered exit snapshots // - Records header snapshot if it's an exit predecessor - loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots, &mut inspector) + loopform.build_exit_phis( + ops, + exit_id, + branch_source_block, + exit_snapshots, + &mut inspector, + ) } #[cfg(test)] @@ -812,7 +862,10 @@ mod tests { true } - fn get_block_predecessors(&self, _block: BasicBlockId) -> std::collections::HashSet { + fn get_block_predecessors( + &self, + _block: BasicBlockId, + ) -> std::collections::HashSet { // MockOps: return empty set (no CFG in test) std::collections::HashSet::new() } @@ -1008,14 +1061,8 @@ mod tests { fn update_var(&mut self, _name: String, _value: ValueId) {} - fn get_variable_at_block( - &self, - name: &str, - block: BasicBlockId, - ) -> Option { - self.vars_at_block - .get(&(block, name.to_string())) - .copied() + fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option { + self.vars_at_block.get(&(block, name.to_string())).copied() } } @@ -1035,7 +1082,7 @@ mod tests { // Act: seal PHIs use std::collections::HashSet; - let writes = HashSet::new(); // Empty writes for test + let writes = HashSet::new(); // Empty writes for test builder .seal_phis(&mut ops, latch, &continue_snapshots, &writes) .expect("seal_phis should succeed"); @@ -1044,14 +1091,14 @@ mod tests { assert_eq!(ops.phi_updates.len(), 2); // Helper to find inputs for a given phi id - let find_inputs = |phi_id: ValueId, - updates: &[(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)]| { - updates - .iter() - .find(|(_, id, _)| *id == phi_id) - .map(|(_, _, inputs)| inputs.clone()) - .expect("phi id not found in updates") - }; + let find_inputs = + |phi_id: ValueId, updates: &[(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)]| { + updates + .iter() + .find(|(_, id, _)| *id == phi_id) + .map(|(_, _, inputs)| inputs.clone()) + .expect("phi id not found in updates") + }; let pinned_inputs = find_inputs(ValueId::new(20), &ops.phi_updates); assert!(pinned_inputs.contains(&(preheader, ValueId::new(10)))); diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index 372c7f72..b7d43d89 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -19,12 +19,12 @@ pub mod local_scope_inspector; pub mod loop_var_classifier; // Phase 26-B: Box-First Refactoring -pub mod phi_input_collector; pub mod body_local_phi_builder; +pub mod phi_input_collector; // Phase 26-C: Loop Snapshot & Header PHI Management -pub mod loop_snapshot_manager; pub mod header_phi_builder; +pub mod loop_snapshot_manager; // Phase 26-D: Exit PHI Management pub mod exit_phi_builder; diff --git a/src/mir/phi_core/phi_input_collector.rs b/src/mir/phi_core/phi_input_collector.rs index ab508139..57f7f7e7 100644 --- a/src/mir/phi_core/phi_input_collector.rs +++ b/src/mir/phi_core/phi_input_collector.rs @@ -44,9 +44,7 @@ impl PhiInputCollector { /// # Returns /// Empty collector ready to collect PHI inputs pub fn new() -> Self { - Self { - inputs: Vec::new(), - } + Self { inputs: Vec::new() } } /// Add preheader input @@ -282,11 +280,14 @@ mod tests { let inputs = collector.finalize(); // Should be sorted by BasicBlockId - assert_eq!(inputs, vec![ - (BasicBlockId(2), ValueId(20)), - (BasicBlockId(3), ValueId(30)), - (BasicBlockId(5), ValueId(50)), - ]); + assert_eq!( + inputs, + vec![ + (BasicBlockId(2), ValueId(20)), + (BasicBlockId(3), ValueId(30)), + (BasicBlockId(5), ValueId(50)), + ] + ); } #[test] @@ -310,7 +311,7 @@ mod tests { let mut collector = PhiInputCollector::new(); // Typical loop PHI scenario - collector.add_preheader(BasicBlockId(100), ValueId(0)); // init value + collector.add_preheader(BasicBlockId(100), ValueId(0)); // init value // Continue snapshots collector.add_snapshot(&[ @@ -322,7 +323,7 @@ mod tests { collector.add_latch(BasicBlockId(130), ValueId(3)); // Add duplicate to test sanitization - collector.add_preheader(BasicBlockId(100), ValueId(99)); // Duplicate + collector.add_preheader(BasicBlockId(100), ValueId(99)); // Duplicate assert_eq!(collector.len(), 5); @@ -337,12 +338,15 @@ mod tests { let inputs = collector.finalize(); // Should be sorted - assert_eq!(inputs, vec![ - (BasicBlockId(100), ValueId(99)), // Last value for bb 100 - (BasicBlockId(110), ValueId(1)), - (BasicBlockId(120), ValueId(2)), - (BasicBlockId(130), ValueId(3)), - ]); + assert_eq!( + inputs, + vec![ + (BasicBlockId(100), ValueId(99)), // Last value for bb 100 + (BasicBlockId(110), ValueId(1)), + (BasicBlockId(120), ValueId(2)), + (BasicBlockId(130), ValueId(3)), + ] + ); } #[test] @@ -358,8 +362,8 @@ mod tests { // Continue snapshots (idx updated in loop body) collector.add_snapshot(&[ - (BasicBlockId(210), ValueId(50)), // idx after first iteration - (BasicBlockId(220), ValueId(51)), // idx after second iteration + (BasicBlockId(210), ValueId(50)), // idx after first iteration + (BasicBlockId(220), ValueId(51)), // idx after second iteration ]); collector.sanitize(); diff --git a/src/mir/printer.rs b/src/mir/printer.rs index 96cce105..f4d1abb4 100644 --- a/src/mir/printer.rs +++ b/src/mir/printer.rs @@ -4,8 +4,8 @@ * Implements pretty-printing for MIR modules and functions */ -use super::{BasicBlock, MirFunction, MirInstruction, MirModule, MirType, ValueId}; use super::printer_helpers; +use super::{BasicBlock, MirFunction, MirInstruction, MirModule, MirType, ValueId}; use crate::debug::log as dlog; use std::collections::HashMap; use std::fmt::Write; diff --git a/src/mir/printer_helpers.rs b/src/mir/printer_helpers.rs index 19309cc9..6817049f 100644 --- a/src/mir/printer_helpers.rs +++ b/src/mir/printer_helpers.rs @@ -43,13 +43,7 @@ pub fn format_instruction( } MirInstruction::Compare { dst, op, lhs, rhs } => { - format!( - "{} icmp {:?} {}, {}", - format_dst(dst, types), - op, - lhs, - rhs - ) + format!("{} icmp {:?} {}, {}", format_dst(dst, types), op, lhs, rhs) } MirInstruction::Load { dst, ptr } => { @@ -79,7 +73,13 @@ pub fn format_instruction( super::Callee::Global(name) => { format!("call_global {}({})", name, args_str) } - super::Callee::Method { box_name, method, receiver, certainty, .. } => { + super::Callee::Method { + box_name, + method, + receiver, + certainty, + .. + } => { if let Some(recv) = receiver { format!( "call_method {}.{}({}) [recv: {}] [{}]", @@ -87,7 +87,12 @@ pub fn format_instruction( method, args_str, recv, - match certainty { crate::mir::definitions::call_unified::TypeCertainty::Known => "Known", crate::mir::definitions::call_unified::TypeCertainty::Union => "Union" } + match certainty { + crate::mir::definitions::call_unified::TypeCertainty::Known => + "Known", + crate::mir::definitions::call_unified::TypeCertainty::Union => + "Union", + } ) } else { format!( @@ -95,22 +100,34 @@ pub fn format_instruction( box_name, method, args_str, - match certainty { crate::mir::definitions::call_unified::TypeCertainty::Known => "Known", crate::mir::definitions::call_unified::TypeCertainty::Union => "Union" } + match certainty { + crate::mir::definitions::call_unified::TypeCertainty::Known => + "Known", + crate::mir::definitions::call_unified::TypeCertainty::Union => + "Union", + } ) } } super::Callee::Constructor { box_type } => { format!("call_constructor {}({})", box_type, args_str) } - super::Callee::Closure { params, captures, me_capture } => { + super::Callee::Closure { + params, + captures, + me_capture, + } => { let params_str = params.join(", "); - let captures_str = captures.iter() + let captures_str = captures + .iter() .map(|(name, val)| format!("{}={}", name, val)) .collect::>() .join(", "); let me_str = me_capture.map_or(String::new(), |v| format!(" [me={}]", v)); - format!("call_closure ({}) [captures: {}]{}", - params_str, captures_str, me_str) + format!( + "call_closure ({}) [captures: {}]{}", + params_str, captures_str, me_str + ) } super::Callee::Value(func_val) => { format!("call_value {}({})", func_val, args_str) @@ -144,7 +161,11 @@ pub fn format_instruction( .collect::>() .join(", "); let me_s = me.map(|m| format!(" me={}", m)).unwrap_or_default(); - let cap_s = if c.is_empty() { String::new() } else { format!(" [{}]", c) }; + let cap_s = if c.is_empty() { + String::new() + } else { + format!(" [{}]", c) + }; format!( "{} new_closure ({}) {{...{}}}{}{}", format_dst(dst, types), @@ -236,22 +257,25 @@ pub fn format_instruction( format!("{} phi {}", format_dst(dst, types), inputs_str) } - MirInstruction::NewBox { dst, box_type, args } => { + MirInstruction::NewBox { + dst, + box_type, + args, + } => { let args_str = args .iter() .map(|v| format!("{}", v)) .collect::>() .join(", "); - format!( - "{} new {}({})", - format_dst(dst, types), - box_type, - args_str - ) + format!("{} new {}({})", format_dst(dst, types), box_type, args_str) } // Legacy -> Unified print: TypeCheck as TypeOp(check) - MirInstruction::TypeCheck { dst, value, expected_type } => { + MirInstruction::TypeCheck { + dst, + value, + expected_type, + } => { format!( "{} typeop check {} {}", format_dst(dst, types), @@ -269,7 +293,11 @@ pub fn format_instruction( s } - MirInstruction::Cast { dst, value, target_type } => { + MirInstruction::Cast { + dst, + value, + target_type, + } => { format!( "{} cast {} to {:?}", format_dst(dst, types), @@ -296,7 +324,11 @@ pub fn format_instruction( format!("{} {}[{}]", format_dst(dst, types), array, index) } - MirInstruction::ArraySet { array, index, value } => { + MirInstruction::ArraySet { + array, + index, + value, + } => { format!("{}[{}] = {}", array, index, value) } @@ -315,11 +347,18 @@ pub fn format_instruction( MirInstruction::Nop => "nop".to_string(), // Phase 5: Control flow & exception handling - MirInstruction::Throw { exception, effects: _ } => { + MirInstruction::Throw { + exception, + effects: _, + } => { format!("throw {}", exception) } - MirInstruction::Catch { exception_type, exception_value, handler_bb } => { + MirInstruction::Catch { + exception_type, + exception_value, + handler_bb, + } => { if let Some(ref exc_type) = exception_type { format!("catch {} {} -> {}", exc_type, exception_value, handler_bb) } else { @@ -334,16 +373,19 @@ pub fn format_instruction( format!("{} ref_new {}", format_dst(dst, types), box_val) } - MirInstruction::RefGet { dst, reference, field } => { - format!( - "{} ref_get {}.{}", - format_dst(dst, types), - reference, - field - ) + MirInstruction::RefGet { + dst, + reference, + field, + } => { + format!("{} ref_get {}.{}", format_dst(dst, types), reference, field) } - MirInstruction::RefSet { reference, field, value } => { + MirInstruction::RefSet { + reference, + field, + value, + } => { format!("ref_set {}.{} = {}", reference, field, value) } @@ -367,12 +409,7 @@ pub fn format_instruction( super::WeakRefOp::New => "new", super::WeakRefOp::Load => "load", }; - format!( - "{} weakref {} {}", - format_dst(dst, types), - op_str, - value - ) + format!("{} weakref {} {}", format_dst(dst, types), op_str, value) } MirInstruction::Barrier { op, ptr } => { @@ -397,7 +434,13 @@ pub fn format_instruction( } // Phase 9.7: External Function Calls - MirInstruction::ExternCall { dst, iface_name, method_name, args, effects } => { + MirInstruction::ExternCall { + dst, + iface_name, + method_name, + args, + effects, + } => { let args_str = args .iter() .map(|v| format!("{}", v)) diff --git a/src/mir/region/function_slot_registry.rs b/src/mir/region/function_slot_registry.rs index 19f7d8ae..ca8dd330 100644 --- a/src/mir/region/function_slot_registry.rs +++ b/src/mir/region/function_slot_registry.rs @@ -10,8 +10,8 @@ * - MirBuilder.variable_map や PHI 生成ロジックには影響を与えないよ。 */ -use crate::mir::MirType; use super::RefSlotKind; +use crate::mir::MirType; use std::collections::HashMap; /// 1 関数内でのスロット ID だよ(添字ベースの薄いラッパー)。 @@ -52,10 +52,7 @@ impl FunctionSlotRegistry { pub fn ensure_slot(&mut self, name: &str, ty: Option) -> SlotId { if let Some(slot) = self.name_to_slot.get(name).copied() { // 既存スロットに対しては、型がまだ None で新しい情報があれば埋める程度に留める - if let (Some(new_ty), Some(info)) = ( - ty, - self.slots.get_mut(slot.0 as usize), - ) { + if let (Some(new_ty), Some(info)) = (ty, self.slots.get_mut(slot.0 as usize)) { if info.ty.is_none() { info.ty = Some(new_ty); } @@ -95,4 +92,3 @@ impl FunctionSlotRegistry { self.slots.get(slot.0 as usize) } } - diff --git a/src/mir/region/mod.rs b/src/mir/region/mod.rs index f65d1535..2c21e410 100644 --- a/src/mir/region/mod.rs +++ b/src/mir/region/mod.rs @@ -70,5 +70,5 @@ impl Region { } } -pub mod observer; pub mod function_slot_registry; +pub mod observer; diff --git a/src/mir/region/observer.rs b/src/mir/region/observer.rs index 9aede37b..ef4cf0ac 100644 --- a/src/mir/region/observer.rs +++ b/src/mir/region/observer.rs @@ -17,10 +17,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; static NEXT_REGION_ID: AtomicU32 = AtomicU32::new(0); fn is_region_trace_on() -> bool { - std::env::var("NYASH_REGION_TRACE") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_REGION_TRACE").ok().as_deref() == Some("1") } /// ControlForm と MirBuilder から Region 情報を観測・ログ出力するよ。 @@ -191,13 +188,7 @@ fn classify_slot(builder: &MirBuilder, v: ValueId, name: &str) -> RefSlotKind { fn classify_slot_name_only(name: &str) -> RefSlotKind { if matches!( name, - "args" - | "src" - | "body_src" - | "bundles" - | "bundle_names" - | "bundle_srcs" - | "require_mods" + "args" | "src" | "body_src" | "bundles" | "bundle_names" | "bundle_srcs" | "require_mods" ) { RefSlotKind::StrongRoot } else { diff --git a/src/mir/ssot/cf_common.rs b/src/mir/ssot/cf_common.rs index 83920465..8f5c21b4 100644 --- a/src/mir/ssot/cf_common.rs +++ b/src/mir/ssot/cf_common.rs @@ -23,7 +23,11 @@ pub fn set_branch( else_bb: BasicBlockId, ) { if let Some(bb) = f.get_block_mut(cur_bb) { - bb.set_terminator(MirInstruction::Branch { condition, then_bb, else_bb }); + bb.set_terminator(MirInstruction::Branch { + condition, + then_bb, + else_bb, + }); } if let Some(tb) = f.get_block_mut(then_bb) { tb.add_predecessor(cur_bb); diff --git a/src/mir/ssot/loop_common.rs b/src/mir/ssot/loop_common.rs index f5aafd4e..88f9b75f 100644 --- a/src/mir/ssot/loop_common.rs +++ b/src/mir/ssot/loop_common.rs @@ -1,4 +1,4 @@ -use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId, BinaryOp, ConstValue}; +use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirFunction, MirInstruction, ValueId}; use std::collections::HashMap; /// Apply `var += step` before continue so that header sees updated value. @@ -10,16 +10,27 @@ pub fn apply_increment_before_continue( var_name: &str, step: i64, ) -> Option { - let cur_val = match vars.get(var_name) { Some(v) => *v, None => return None }; + let cur_val = match vars.get(var_name) { + Some(v) => *v, + None => return None, + }; // Emit const step let step_v = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::Const { dst: step_v, value: ConstValue::Integer(step) }); + bb.add_instruction(MirInstruction::Const { + dst: step_v, + value: ConstValue::Integer(step), + }); } // Emit add let new_v = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::BinOp { dst: new_v, op: BinaryOp::Add, lhs: cur_val, rhs: step_v }); + bb.add_instruction(MirInstruction::BinOp { + dst: new_v, + op: BinaryOp::Add, + lhs: cur_val, + rhs: step_v, + }); } vars.insert(var_name.to_string(), new_v); Some(new_v) diff --git a/src/mir/ssot/mod.rs b/src/mir/ssot/mod.rs index 29e63779..9d3d6df1 100644 --- a/src/mir/ssot/mod.rs +++ b/src/mir/ssot/mod.rs @@ -1,3 +1,3 @@ pub mod binop_lower; -pub mod loop_common; pub mod cf_common; +pub mod loop_common; diff --git a/src/mir/types.rs b/src/mir/types.rs index 0af3bac4..42be1e05 100644 --- a/src/mir/types.rs +++ b/src/mir/types.rs @@ -108,4 +108,3 @@ pub enum BarrierOp { Read, Write, } - diff --git a/src/mir/utils/control_flow.rs b/src/mir/utils/control_flow.rs index abbcf9f9..37abf265 100644 --- a/src/mir/utils/control_flow.rs +++ b/src/mir/utils/control_flow.rs @@ -1,6 +1,6 @@ /*! * Control Flow Utilities - 制御フロー処理の共通ユーティリティ - * + * * PHI incoming修正とブロック終端検出の汎用関数群 * フェーズS(即効止血)からフェーズL(根本解決)まで共通利用 */ @@ -8,9 +8,9 @@ use super::super::{BasicBlockId, MirBuilder}; /// **外部関数**: 現在のブロックが終端済みかチェック -/// +/// /// loop_builder.rsで3箇所重複していた処理を統一 -/// +/// /// # 使用例 /// ```rust /// if is_current_block_terminated(builder)? { @@ -18,9 +18,10 @@ use super::super::{BasicBlockId, MirBuilder}; /// } /// ``` pub fn is_current_block_terminated(builder: &MirBuilder) -> Result { - let cur_id = builder.current_block + let cur_id = builder + .current_block .ok_or_else(|| "No current block".to_string())?; - + if let Some(ref function) = builder.current_function { if let Some(bb) = function.get_block(cur_id) { Ok(bb.is_terminated()) @@ -33,14 +34,14 @@ pub fn is_current_block_terminated(builder: &MirBuilder) -> Result } /// **外部関数**: 実到達ブロックを捕捉してJump発行 -/// +/// /// 最強モード指摘の「実到達predecessor捕捉」を汎用化 /// break/continue後の到達不能ブロックは除外 -/// +/// /// # 戻り値 /// - `Some(predecessor_id)`: Jump発行済み、PHI incomingに使用可能 /// - `None`: 既に終端済み、PHI incomingから除外すべき -/// +/// /// # 使用例 /// ```rust /// if let Some(pred_id) = capture_actual_predecessor_and_jump(builder, merge_bb)? { @@ -51,16 +52,17 @@ pub fn capture_actual_predecessor_and_jump( builder: &mut MirBuilder, target_block: BasicBlockId, ) -> Result, String> { - let cur_id = builder.current_block + let cur_id = builder + .current_block .ok_or_else(|| "No current block".to_string())?; - + let need_jump = !is_current_block_terminated(builder)?; - + if need_jump { // Jump発行前に実到達ブロックID捕捉(重要!) // 既存control_flowモジュールと同じパターンを使用 - builder.emit_instruction(super::super::MirInstruction::Jump { - target: target_block + builder.emit_instruction(super::super::MirInstruction::Jump { + target: target_block, })?; Ok(Some(cur_id)) } else { @@ -70,10 +72,10 @@ pub fn capture_actual_predecessor_and_jump( } /// **外部関数**: 条件付きPHI incoming収集 -/// +/// /// 到達可能な場合のみincomingをリストに追加 /// フェーズM、フェーズLでの型安全性向上にも対応 -/// +/// /// # 使用例 /// ```rust /// let mut incomings = Vec::new(); @@ -92,10 +94,10 @@ pub fn collect_phi_incoming_if_reachable( } /// **外部関数**: 終端チェック付きステートメント実行 -/// +/// /// build_statement後の終端チェックを自動化 /// フェーズSでの「終端ガード徹底」を支援 -/// +/// /// # 戻り値 /// - `Ok(true)`: 正常実行、継続可能 /// - `Ok(false)`: 終端済み、ループ脱出すべき @@ -105,7 +107,7 @@ pub fn execute_statement_with_termination_check( statement: crate::ast::ASTNode, ) -> Result { let _result = builder.build_expression(statement)?; - + // 終端チェック(統一処理) let terminated = is_current_block_terminated(builder)?; Ok(!terminated) @@ -114,9 +116,9 @@ pub fn execute_statement_with_termination_check( #[cfg(test)] mod tests { use super::*; - + // ユニットテスト(将来追加) // - 終端検出の正確性 // - 実到達ブロック捕捉の正確性 // - PHI incoming除外の正確性 -} \ No newline at end of file +} diff --git a/src/mir/utils/mod.rs b/src/mir/utils/mod.rs index ec5e5cdc..7ad4b32b 100644 --- a/src/mir/utils/mod.rs +++ b/src/mir/utils/mod.rs @@ -1,10 +1,10 @@ /*! * MIR Utilities - Phase 15 段階的根治戦略の共通ユーティリティ - * + * * フェーズS: 即効止血 * フェーズM: PHI一本化 * フェーズL: 根本解決 - * + * * 全フェーズで使用する汎用関数を提供 */ @@ -13,10 +13,8 @@ pub mod phi_helpers; // 外部公開API pub use control_flow::{ - is_current_block_terminated, - capture_actual_predecessor_and_jump, - collect_phi_incoming_if_reachable, - execute_statement_with_termination_check, + capture_actual_predecessor_and_jump, collect_phi_incoming_if_reachable, + execute_statement_with_termination_check, is_current_block_terminated, }; // PHI挿入ヘルパー(MirBuilderのextension methodsとして実装) diff --git a/src/mir/utils/phi_helpers.rs b/src/mir/utils/phi_helpers.rs index 0294e013..3c43ae6b 100644 --- a/src/mir/utils/phi_helpers.rs +++ b/src/mir/utils/phi_helpers.rs @@ -25,8 +25,8 @@ * ``` */ -use crate::mir::{BasicBlockId, MirInstruction, ValueId}; use crate::mir::builder::MirBuilder; +use crate::mir::{BasicBlockId, MirInstruction, ValueId}; /// PHI挿入ヘルパー - MirBuilderへのextension methods impl MirBuilder { @@ -64,9 +64,9 @@ impl MirBuilder { // This prevents PHI dst ValueIds from colliding with function-local IDs allocated later. // Same pattern as pin_to_slot() and the loop builder fix in e2d061d1. let phi_val = if let Some(ref mut f) = self.current_function { - f.next_value_id() // Function context: use local ID allocator + f.next_value_id() // Function context: use local ID allocator } else { - self.value_gen.next() // Module context: use global ID allocator + self.value_gen.next() // Module context: use global ID allocator }; // 統一された挿入ロジック(既存パターンと完全互換) @@ -75,7 +75,10 @@ impl MirBuilder { crate::mir::ssot::cf_common::insert_phi_at_head(func, cur_bb, phi_val, inputs); } else { // フォールバック: 直接emit(主にテストや特殊ケース用) - self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?; + self.emit_instruction(MirInstruction::Phi { + dst: phi_val, + inputs, + })?; } Ok(phi_val) @@ -152,7 +155,11 @@ impl MirBuilder { /// } /// ``` #[inline] - pub fn insert_phi_single(&mut self, pred: BasicBlockId, value: ValueId) -> Result { + pub fn insert_phi_single( + &mut self, + pred: BasicBlockId, + value: ValueId, + ) -> Result { self.insert_phi(vec![(pred, value)]) } @@ -205,7 +212,10 @@ impl MirBuilder { updated_value: ValueId, ) -> Result { // ループヘッダーPHIは論理的に[entry, backedge]の順序が自然 - self.insert_phi(vec![(entry_pred, init_value), (backedge_pred, updated_value)]) + self.insert_phi(vec![ + (entry_pred, init_value), + (backedge_pred, updated_value), + ]) } /// **短絡評価用PHI挿入** - AND/ORの合流点用 diff --git a/src/mir/value_kind.rs b/src/mir/value_kind.rs index d0a1ba76..0aeefefc 100644 --- a/src/mir/value_kind.rs +++ b/src/mir/value_kind.rs @@ -5,8 +5,8 @@ // - ValueIdの意味的分類(Parameter, Local, Constant等)を導入 // - GUARDバグのような「ValueId(0)の曖昧性」から生じるバグを根絶 -use crate::mir::ValueId; use crate::mir::ConstValue; +use crate::mir::ValueId; /// ValueIdの意味的分類(型安全性強化) /// diff --git a/src/mir/verification.rs b/src/mir/verification.rs index 7d27cee5..6a2a8632 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -7,14 +7,13 @@ use super::{MirFunction, MirModule}; use crate::debug::log as dlog; use crate::mir::verification_types::VerificationError; -mod cfg; -mod dom; mod awaits; mod barrier; +mod cfg; +mod dom; mod legacy; -mod utils; mod ssa; - +mod utils; /// MIR verifier for SSA form and semantic correctness pub struct MirVerifier { @@ -43,10 +42,7 @@ impl MirVerifier { // 併せて、同じトグルで「任意の関数」に対する UndefinedValue も // 簡易ログとして出力し、どの関数で支配関係が崩れているかを // 追いやすくしている(箱理論の観測レイヤー強化)。 - if std::env::var("NYASH_BREAKFINDER_SSA_TRACE") - .ok() - .as_deref() == Some("1") - { + if std::env::var("NYASH_BREAKFINDER_SSA_TRACE").ok().as_deref() == Some("1") { // 1) BreakFinderBox / LoopSSA 向けの詳細ログ if function.signature.name.starts_with("BreakFinderBox.") || function.signature.name.starts_with("LoopSSA.") @@ -97,8 +93,7 @@ impl MirVerifier { instruction_index ); if let Some(bb) = function.blocks.get(block) { - let inst_opt = - bb.all_instructions().nth(*instruction_index); + let inst_opt = bb.all_instructions().nth(*instruction_index); if let Some(inst) = inst_opt { eprintln!("[mir-ssa-debug-inst] inst={:?}", inst); } @@ -316,26 +311,31 @@ impl MirVerifier { } // Merge block itself must not contain a Copy to the merged value - let has_self_copy_in_merge = merge_bb - .instructions - .iter() - .any(|inst| matches!(inst, super::MirInstruction::Copy { dst, .. } if *dst == u)); + let has_self_copy_in_merge = merge_bb.instructions.iter().any( + |inst| matches!(inst, super::MirInstruction::Copy { dst, .. } if *dst == u), + ); if has_self_copy_in_merge { errors.push(VerificationError::EdgeCopyStrictViolation { block: *merge_bid, value: u, pred_block: None, - reason: "merge block contains Copy to merged value; use predecessor copies only".to_string(), + reason: + "merge block contains Copy to merged value; use predecessor copies only" + .to_string(), }); } // Each predecessor must provide a Copy into the merged destination for pred in &p { - let Some(pbb) = function.blocks.get(pred) else { continue; }; - let has_copy = pbb.instructions.iter().any(|inst| matches!( - inst, - super::MirInstruction::Copy { dst, .. } if *dst == u - )); + let Some(pbb) = function.blocks.get(pred) else { + continue; + }; + let has_copy = pbb.instructions.iter().any(|inst| { + matches!( + inst, + super::MirInstruction::Copy { dst, .. } if *dst == u + ) + }); if !has_copy { errors.push(VerificationError::MergeUsesPredecessorValue { value: u, @@ -347,12 +347,19 @@ impl MirVerifier { } } - if errors.is_empty() { Ok(()) } else { Err(errors) } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } /// Verify that any block ending with Return contains no side-effecting instructions before it. /// Allowed before Return: Const, Copy, Phi, Nop only. Others are considered side-effecting for this policy. - fn verify_ret_block_purity(&self, function: &MirFunction) -> Result<(), Vec> { + fn verify_ret_block_purity( + &self, + function: &MirFunction, + ) -> Result<(), Vec> { use super::MirInstruction as I; let mut errors = Vec::new(); for (bid, bb) in &function.blocks { @@ -373,7 +380,11 @@ impl MirVerifier { } } } - if errors.is_empty() { Ok(()) } else { Err(errors) } + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } } /// Reject legacy instructions that should be rewritten to Core-15 equivalents @@ -436,7 +447,6 @@ impl MirVerifier { pub fn clear_errors(&mut self) { self.errors.clear(); } - } impl Default for MirVerifier { @@ -445,6 +455,5 @@ impl Default for MirVerifier { } } - #[cfg(test)] mod tests {} diff --git a/src/mir/verification/barrier.rs b/src/mir/verification/barrier.rs index bbec9bce..f18484f9 100644 --- a/src/mir/verification/barrier.rs +++ b/src/mir/verification/barrier.rs @@ -69,7 +69,7 @@ pub fn check_weakref_and_barrier(function: &MirFunction) -> Result<(), Vec { if let Some((_db, _di, def_inst)) = def_map.get(ptr) { if let MirInstruction::Const { - value: crate::mir::ConstValue::Void, + value: crate::mir::ConstValue::Void, .. } = def_inst { diff --git a/src/mir/verification/cfg.rs b/src/mir/verification/cfg.rs index fbe6046d..db497945 100644 --- a/src/mir/verification/cfg.rs +++ b/src/mir/verification/cfg.rs @@ -1,7 +1,7 @@ use crate::mir::function::MirFunction; -use crate::mir::{BasicBlockId, ValueId}; -use crate::mir::verification_types::VerificationError; use crate::mir::verification::utils; +use crate::mir::verification_types::VerificationError; +use crate::mir::{BasicBlockId, ValueId}; use std::collections::{HashMap, HashSet}; /// Verify CFG references and reachability @@ -23,12 +23,18 @@ pub fn check_control_flow(function: &MirFunction) -> Result<(), Vec Result<(), Vec> { - if crate::config::env::verify_allow_no_phi() { return Ok(()); } + if crate::config::env::verify_allow_no_phi() { + return Ok(()); + } let mut errors = Vec::new(); let preds = utils::compute_predecessors(function); let def_block = utils::compute_def_blocks(function); @@ -37,28 +43,43 @@ pub fn check_merge_uses(function: &MirFunction) -> Result<(), Vec Result<(), Vec> { - if crate::config::env::verify_allow_no_phi() { return Ok(()); } + if crate::config::env::verify_allow_no_phi() { + return Ok(()); + } let mut errors = Vec::new(); let def_block = utils::compute_def_blocks(function); let dominators = utils::compute_dominators(function); for (use_block_id, block) in &function.blocks { for instruction in block.all_instructions() { - if let crate::mir::MirInstruction::Phi { .. } = instruction { continue; } + if let crate::mir::MirInstruction::Phi { .. } = instruction { + continue; + } for used_value in instruction.used_values() { if let Some(&def_bb) = def_block.get(&used_value) { if def_bb != *use_block_id { @@ -27,6 +31,9 @@ pub fn check_dominance(function: &MirFunction) -> Result<(), Vec Result<(), Vec> { @@ -45,5 +45,9 @@ pub fn check_ssa_form(function: &MirFunction) -> Result<(), Vec { + VerificationError::EdgeCopyStrictViolation { + block, + value, + pred_block, + reason, + } => { if let Some(pb) = pred_block { write!( f, @@ -217,7 +222,11 @@ impl std::fmt::Display for VerificationError { ) } } - VerificationError::RetBlockSideEffect { block, instruction_index, name } => { + VerificationError::RetBlockSideEffect { + block, + instruction_index, + name, + } => { write!( f, "RetBlockSideEffect: side-effecting '{}' at block {} before return (instr #{})", diff --git a/src/parser/common.rs b/src/parser/common.rs index c59d2af2..033c87a4 100644 --- a/src/parser/common.rs +++ b/src/parser/common.rs @@ -58,13 +58,17 @@ pub trait ParserUtils { // 環境変数 NYASH_PARSER_TOKEN_CURSOR=1 の場合は Cursor 側で一元管理する。 let cursor_on = std::env::var("NYASH_PARSER_TOKEN_CURSOR").ok().as_deref() == Some("1"); if !cursor_on { - let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| { - let lv = v.to_ascii_lowercase(); - !(lv == "0" || lv == "false" || lv == "off") - }).unwrap_or(true); + let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON") + .ok() + .map(|v| { + let lv = v.to_ascii_lowercase(); + !(lv == "0" || lv == "false" || lv == "off") + }) + .unwrap_or(true); loop { let is_nl = matches!(self.current_token().token_type, TokenType::NEWLINE); - let is_sc = allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON); + let is_sc = + allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON); if (is_nl || is_sc) && !self.is_at_end() { *self.current_mut() += 1; // 非再帰的に前進 continue; diff --git a/src/parser/cursor.rs b/src/parser/cursor.rs index a239e765..4d4d25ba 100644 --- a/src/parser/cursor.rs +++ b/src/parser/cursor.rs @@ -92,7 +92,8 @@ impl<'a> TokenCursor<'a> { /// 明示的に改行をスキップ pub fn skip_newlines(&mut self) { while self.idx < self.tokens.len() - && matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE) { + && matches!(self.tokens[self.idx].token_type, TokenType::NEWLINE) + { self.idx += 1; } } @@ -241,11 +242,31 @@ mod tests { #[test] fn test_basic_cursor_operations() { let tokens = vec![ - Token { token_type: TokenType::LOCAL, line: 1, column: 1 }, - Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 7 }, - Token { token_type: TokenType::ASSIGN, line: 1, column: 9 }, - Token { token_type: TokenType::NUMBER(42), line: 1, column: 11 }, - Token { token_type: TokenType::EOF, line: 1, column: 13 }, + Token { + token_type: TokenType::LOCAL, + line: 1, + column: 1, + }, + Token { + token_type: TokenType::IDENTIFIER("x".to_string()), + line: 1, + column: 7, + }, + Token { + token_type: TokenType::ASSIGN, + line: 1, + column: 9, + }, + Token { + token_type: TokenType::NUMBER(42), + line: 1, + column: 11, + }, + Token { + token_type: TokenType::EOF, + line: 1, + column: 13, + }, ]; let mut cursor = TokenCursor::new(&tokens); @@ -253,7 +274,10 @@ mod tests { assert!(cursor.match_token(&TokenType::LOCAL)); cursor.advance(); - assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_))); + assert!(matches!( + cursor.current().token_type, + TokenType::IDENTIFIER(_) + )); cursor.advance(); assert!(cursor.match_token(&TokenType::ASSIGN)); @@ -268,27 +292,70 @@ mod tests { #[test] fn test_newline_skipping_in_braces() { let tokens = vec![ - Token { token_type: TokenType::LBRACE, line: 1, column: 1 }, - Token { token_type: TokenType::NEWLINE, line: 1, column: 2 }, - Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 2, column: 1 }, - Token { token_type: TokenType::RBRACE, line: 2, column: 2 }, - Token { token_type: TokenType::EOF, line: 2, column: 3 }, + Token { + token_type: TokenType::LBRACE, + line: 1, + column: 1, + }, + Token { + token_type: TokenType::NEWLINE, + line: 1, + column: 2, + }, + Token { + token_type: TokenType::IDENTIFIER("x".to_string()), + line: 2, + column: 1, + }, + Token { + token_type: TokenType::RBRACE, + line: 2, + column: 2, + }, + Token { + token_type: TokenType::EOF, + line: 2, + column: 3, + }, ]; let mut cursor = TokenCursor::new(&tokens); cursor.advance(); // consume LBRACE, should skip NEWLINE - assert!(matches!(cursor.current().token_type, TokenType::IDENTIFIER(_))); + assert!(matches!( + cursor.current().token_type, + TokenType::IDENTIFIER(_) + )); } #[test] fn test_expr_mode() { let tokens = vec![ - Token { token_type: TokenType::IDENTIFIER("x".to_string()), line: 1, column: 1 }, - Token { token_type: TokenType::NEWLINE, line: 1, column: 2 }, - Token { token_type: TokenType::PLUS, line: 2, column: 1 }, - Token { token_type: TokenType::NUMBER(1), line: 2, column: 3 }, - Token { token_type: TokenType::EOF, line: 2, column: 4 }, + Token { + token_type: TokenType::IDENTIFIER("x".to_string()), + line: 1, + column: 1, + }, + Token { + token_type: TokenType::NEWLINE, + line: 1, + column: 2, + }, + Token { + token_type: TokenType::PLUS, + line: 2, + column: 1, + }, + Token { + token_type: TokenType::NUMBER(1), + line: 2, + column: 3, + }, + Token { + token_type: TokenType::EOF, + line: 2, + column: 4, + }, ]; let mut cursor = TokenCursor::new(&tokens); diff --git a/src/parser/declarations/box_def/header.rs b/src/parser/declarations/box_def/header.rs index 1ea71df1..bf0a0dc9 100644 --- a/src/parser/declarations/box_def/header.rs +++ b/src/parser/declarations/box_def/header.rs @@ -1,7 +1,7 @@ //! Header parsing: `Name from Parent1, Parent2` //! Assumes the caller already consumed the leading `box` token. -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; /// Parse the leading header of a box declaration and return diff --git a/src/parser/declarations/box_def/interface.rs b/src/parser/declarations/box_def/interface.rs index bf68cddd..c9ea30fb 100644 --- a/src/parser/declarations/box_def/interface.rs +++ b/src/parser/declarations/box_def/interface.rs @@ -1,7 +1,7 @@ //! Interface box parser: `interface box Name { methods... }` use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; diff --git a/src/parser/declarations/box_def/members/common.rs b/src/parser/declarations/box_def/members/common.rs index 2c5a51a1..515d4c0f 100644 --- a/src/parser/declarations/box_def/members/common.rs +++ b/src/parser/declarations/box_def/members/common.rs @@ -1,6 +1,6 @@ //! Shared helpers for members parsing (scaffold) -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; #[derive(Debug, Clone, PartialEq, Eq)] @@ -23,10 +23,14 @@ pub(crate) fn classify_member(p: &mut NyashParser) -> Result { - if p.peek_token() == &TokenType::LPAREN { return Ok(MemberKind::Constructor); } + if p.peek_token() == &TokenType::LPAREN { + return Ok(MemberKind::Constructor); + } } - TokenType::IDENTIFIER(name) if (name == "init" || name == "birth" || name == "pack") - && p.peek_token() == &TokenType::LPAREN => { + TokenType::IDENTIFIER(name) + if (name == "init" || name == "birth" || name == "pack") + && p.peek_token() == &TokenType::LPAREN => + { return Ok(MemberKind::Constructor); } _ => {} diff --git a/src/parser/declarations/box_def/members/constructors.rs b/src/parser/declarations/box_def/members/constructors.rs index 74772952..3db02a55 100644 --- a/src/parser/declarations/box_def/members/constructors.rs +++ b/src/parser/declarations/box_def/members/constructors.rs @@ -1,7 +1,7 @@ //! Constructors parsing (init/pack/birth) use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; /// Try to parse a constructor at current position. @@ -66,7 +66,12 @@ pub(crate) fn try_parse_constructor( } else { None }; - body = vec![ASTNode::TryCatch { try_body: body, catch_clauses, finally_body, span: Span::unknown() }]; + body = vec![ASTNode::TryCatch { + try_body: body, + catch_clauses, + finally_body, + span: Span::unknown(), + }]; } let node = ASTNode::FunctionDeclaration { name: name.clone(), diff --git a/src/parser/declarations/box_def/members/fields.rs b/src/parser/declarations/box_def/members/fields.rs index 4218bf04..3ecb45a3 100644 --- a/src/parser/declarations/box_def/members/fields.rs +++ b/src/parser/declarations/box_def/members/fields.rs @@ -1,7 +1,7 @@ //! Fields parsing (header-first: `name: Type` + unified members gates) use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; @@ -25,7 +25,7 @@ pub(crate) fn try_parse_header_first_field_or_property( return Ok(true); } p.advance(); // consume ':' - // Optional type name (identifier). For now we accept and ignore. + // Optional type name (identifier). For now we accept and ignore. if let TokenType::IDENTIFIER(_ty) = &p.current_token().token_type { p.advance(); } else { @@ -65,7 +65,10 @@ pub(crate) fn try_parse_header_first_field_or_property( // name: Type { ... } [postfix] if p.match_token(&TokenType::LBRACE) { let body = p.parse_block_statements()?; - let body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, body)?; + let body = + crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix( + p, body, + )?; let getter_name = format!("__get_{}", fname); let method = ASTNode::FunctionDeclaration { name: getter_name.clone(), @@ -108,10 +111,16 @@ pub(crate) fn try_parse_visibility_block_or_single( while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() { if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type { let fname = fname.clone(); - if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); } + if visibility == "public" { + public_fields.push(fname.clone()); + } else { + private_fields.push(fname.clone()); + } fields.push(fname); p.advance(); - if p.match_token(&TokenType::COMMA) { p.advance(); } + if p.match_token(&TokenType::COMMA) { + p.advance(); + } continue; } return Err(ParseError::UnexpectedToken { @@ -127,11 +136,19 @@ pub(crate) fn try_parse_visibility_block_or_single( let fname = n.clone(); p.advance(); if try_parse_header_first_field_or_property(p, fname.clone(), methods, fields)? { - if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); } + if visibility == "public" { + public_fields.push(fname.clone()); + } else { + private_fields.push(fname.clone()); + } *last_method_name = None; return Ok(true); } else { - if visibility == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); } + if visibility == "public" { + public_fields.push(fname.clone()); + } else { + private_fields.push(fname.clone()); + } fields.push(fname); return Ok(true); } @@ -167,10 +184,17 @@ pub(crate) fn parse_init_block_if_any( weak_fields.push(field_name.clone()); } p.advance(); - if p.match_token(&TokenType::COMMA) { p.advance(); } + if p.match_token(&TokenType::COMMA) { + p.advance(); + } } else { return Err(ParseError::UnexpectedToken { - expected: if is_weak { "field name after 'weak'" } else { "field name" }.to_string(), + expected: if is_weak { + "field name after 'weak'" + } else { + "field name" + } + .to_string(), found: p.current_token().token_type.clone(), line: p.current_token().line, }); diff --git a/src/parser/declarations/box_def/members/methods.rs b/src/parser/declarations/box_def/members/methods.rs index a9b53c34..1d6d0074 100644 --- a/src/parser/declarations/box_def/members/methods.rs +++ b/src/parser/declarations/box_def/members/methods.rs @@ -1,7 +1,7 @@ //! Methods parsing (name(params){ body }) with special birth() prologue use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; /// Try to parse a method declaration starting at `method_name` (already consumed identifier). @@ -35,7 +35,9 @@ pub(crate) fn try_parse_method( if method_name == "birth" && !birth_once_props.is_empty() { let mut injected: Vec = Vec::new(); for pprop in birth_once_props.iter() { - let me_node = ASTNode::Me { span: Span::unknown() }; + let me_node = ASTNode::Me { + span: Span::unknown(), + }; let compute_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: format!("__compute_birth_{}", pprop), @@ -56,7 +58,10 @@ pub(crate) fn try_parse_method( value: crate::ast::LiteralValue::String(format!("__birth_{}", pprop)), span: Span::unknown(), }, - ASTNode::Variable { name: tmp, span: Span::unknown() }, + ASTNode::Variable { + name: tmp, + span: Span::unknown(), + }, ], span: Span::unknown(), }; diff --git a/src/parser/declarations/box_def/members/mod.rs b/src/parser/declarations/box_def/members/mod.rs index 7cf699ee..020e5f5a 100644 --- a/src/parser/declarations/box_def/members/mod.rs +++ b/src/parser/declarations/box_def/members/mod.rs @@ -1,7 +1,6 @@ pub mod common; +pub mod constructors; pub mod fields; pub mod methods; -pub mod constructors; -pub mod properties; pub mod postfix; - +pub mod properties; diff --git a/src/parser/declarations/box_def/members/postfix.rs b/src/parser/declarations/box_def/members/postfix.rs index 548d13c9..f458beb0 100644 --- a/src/parser/declarations/box_def/members/postfix.rs +++ b/src/parser/declarations/box_def/members/postfix.rs @@ -1,7 +1,7 @@ //! Postfix handlers (catch/cleanup) utilities for unified members use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; @@ -60,7 +60,9 @@ pub(crate) fn try_parse_method_postfix_after_last_method( methods: &mut HashMap, last_method_name: &Option, ) -> Result { - if !(p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP)) || last_method_name.is_none() { + if !(p.match_token(&TokenType::CATCH) || p.match_token(&TokenType::CLEANUP)) + || last_method_name.is_none() + { return Ok(false); } let mname = last_method_name.clone().unwrap(); @@ -94,7 +96,9 @@ pub(crate) fn try_parse_method_postfix_after_last_method( }; if let Some(mnode) = methods.get_mut(&mname) { if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode { - let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. })); + let already = body + .iter() + .any(|n| matches!(n, crate::ast::ASTNode::TryCatch { .. })); if already { let line = p.current_token().line; return Err(ParseError::UnexpectedToken { diff --git a/src/parser/declarations/box_def/members/properties.rs b/src/parser/declarations/box_def/members/properties.rs index 25c6957f..22d5c4aa 100644 --- a/src/parser/declarations/box_def/members/properties.rs +++ b/src/parser/declarations/box_def/members/properties.rs @@ -1,11 +1,10 @@ //! Properties parsing (once/birth_once, header-first) use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; - /// Try to parse a unified member property: `once name: Type ...` or `birth_once name: Type ...` /// Returns Ok(true) if consumed and handled; otherwise Ok(false). pub(crate) fn try_parse_unified_property( @@ -52,12 +51,18 @@ pub(crate) fn try_parse_unified_property( let orig_body: Vec = if p.match_token(&TokenType::FatArrow) { p.advance(); // consume '=>' let expr = p.parse_expression()?; - vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }] + vec![ASTNode::Return { + value: Some(Box::new(expr)), + span: Span::unknown(), + }] } else { p.parse_block_statements()? }; // Optional postfix handlers (Stage-3) directly after body - let final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, orig_body)?; + let final_body = + crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix( + p, orig_body, + )?; if kind_kw == "once" { // once: synthesize compute + getter with poison/cache let compute_name = format!("__compute_once_{}", name); @@ -76,43 +81,182 @@ pub(crate) fn try_parse_unified_property( let cached_local = format!("__ny_cached_{}", name); let poison_local = format!("__ny_poison_{}", name); let val_local = format!("__ny_val_{}", name); - let me_node = ASTNode::Me { span: Span::unknown() }; + let me_node = ASTNode::Me { + span: Span::unknown(), + }; let get_cached = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), - arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(key.clone()), + span: Span::unknown(), + }], span: Span::unknown(), }; - let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() }; - let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() }; - let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }]; - let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() }; - let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() }; - let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() }; - let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() }; - let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }]; - let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() }; - let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() }; - let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() }; - let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() }; - let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() }; - let getter_body = vec![local_cached, if_cached, local_poison, if_poison, local_val, set_call, ret_stmt]; + let local_cached = ASTNode::Local { + variables: vec![cached_local.clone()], + initial_values: vec![Some(Box::new(get_cached))], + span: Span::unknown(), + }; + let cond_cached = ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::NotEqual, + left: Box::new(ASTNode::Variable { + name: cached_local.clone(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::Null, + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let then_ret_cached = vec![ASTNode::Return { + value: Some(Box::new(ASTNode::Variable { + name: cached_local.clone(), + span: Span::unknown(), + })), + span: Span::unknown(), + }]; + let if_cached = ASTNode::If { + condition: Box::new(cond_cached), + then_body: then_ret_cached, + else_body: None, + span: Span::unknown(), + }; + let get_poison = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "getField".to_string(), + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(poison_key.clone()), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + let local_poison = ASTNode::Local { + variables: vec![poison_local.clone()], + initial_values: vec![Some(Box::new(get_poison))], + span: Span::unknown(), + }; + let cond_poison = ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::NotEqual, + left: Box::new(ASTNode::Variable { + name: poison_local.clone(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::Null, + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let then_throw = vec![ASTNode::Throw { + expression: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::String(format!( + "once '{}' previously failed", + name + )), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; + let if_poison = ASTNode::If { + condition: Box::new(cond_poison), + then_body: then_throw, + else_body: None, + span: Span::unknown(), + }; + let call_compute = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: compute_name.clone(), + arguments: vec![], + span: Span::unknown(), + }; + let local_val = ASTNode::Local { + variables: vec![val_local.clone()], + initial_values: vec![Some(Box::new(call_compute))], + span: Span::unknown(), + }; + let set_call = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "setField".to_string(), + arguments: vec![ + ASTNode::Literal { + value: crate::ast::LiteralValue::String(key.clone()), + span: Span::unknown(), + }, + ASTNode::Variable { + name: val_local.clone(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let ret_stmt = ASTNode::Return { + value: Some(Box::new(ASTNode::Variable { + name: val_local.clone(), + span: Span::unknown(), + })), + span: Span::unknown(), + }; + let getter_body = vec![ + local_cached, + if_cached, + local_poison, + if_poison, + local_val, + set_call, + ret_stmt, + ]; let getter_name = format!("__get_once_{}", name); - let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() }; + let getter = ASTNode::FunctionDeclaration { + name: getter_name.clone(), + params: vec![], + body: getter_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(getter_name, getter); return Ok(true); } // birth_once birth_once_props.push(name.clone()); let compute_name = format!("__compute_birth_{}", name); - let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() }; + let compute = ASTNode::FunctionDeclaration { + name: compute_name.clone(), + params: vec![], + body: final_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(compute_name.clone(), compute); - let me_node = ASTNode::Me { span: Span::unknown() }; + let me_node = ASTNode::Me { + span: Span::unknown(), + }; // getter: me.getField("__birth_name") - let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), span: Span::unknown() }], span: Span::unknown() }; - let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }]; + let get_call = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "getField".to_string(), + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + let getter_body = vec![ASTNode::Return { + value: Some(Box::new(get_call)), + span: Span::unknown(), + }]; let getter_name = format!("__get_birth_{}", name); - let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() }; + let getter = ASTNode::FunctionDeclaration { + name: getter_name.clone(), + params: vec![], + body: getter_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(getter_name, getter); Ok(true) } @@ -194,7 +338,10 @@ pub(crate) fn try_parse_block_first_property( } // 5) Optional postfix handlers (Stage‑3) directly after block (shared helper) - final_body = crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix(p, final_body)?; + final_body = + crate::parser::declarations::box_def::members::postfix::wrap_with_optional_postfix( + p, final_body, + )?; // 6) Generate methods per kind (fully equivalent to legacy inline branch) if kind == "once" { @@ -216,31 +363,143 @@ pub(crate) fn try_parse_block_first_property( let cached_local = format!("__ny_cached_{}", name); let poison_local = format!("__ny_poison_{}", name); let val_local = format!("__ny_val_{}", name); - let me_node = ASTNode::Me { span: Span::unknown() }; + let me_node = ASTNode::Me { + span: Span::unknown(), + }; let get_cached = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), - arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(key.clone()), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + let local_cached = ASTNode::Local { + variables: vec![cached_local.clone()], + initial_values: vec![Some(Box::new(get_cached))], + span: Span::unknown(), + }; + let cond_cached = ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::NotEqual, + left: Box::new(ASTNode::Variable { + name: cached_local.clone(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::Null, + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let then_ret_cached = vec![ASTNode::Return { + value: Some(Box::new(ASTNode::Variable { + name: cached_local.clone(), + span: Span::unknown(), + })), + span: Span::unknown(), + }]; + let if_cached = ASTNode::If { + condition: Box::new(cond_cached), + then_body: then_ret_cached, + else_body: None, span: Span::unknown(), }; - let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() }; - let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() }; - let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }]; - let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() }; - let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() }; - let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() }; - let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() }; - let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }]; - let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() }; + let get_poison = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "getField".to_string(), + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(poison_key.clone()), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + let local_poison = ASTNode::Local { + variables: vec![poison_local.clone()], + initial_values: vec![Some(Box::new(get_poison))], + span: Span::unknown(), + }; + let cond_poison = ASTNode::BinaryOp { + operator: crate::ast::BinaryOperator::NotEqual, + left: Box::new(ASTNode::Variable { + name: poison_local.clone(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::Null, + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let then_throw = vec![ASTNode::Throw { + expression: Box::new(ASTNode::Literal { + value: crate::ast::LiteralValue::String(format!( + "once '{}' previously failed", + name + )), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; + let if_poison = ASTNode::If { + condition: Box::new(cond_poison), + then_body: then_throw, + else_body: None, + span: Span::unknown(), + }; - let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() }; - let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() }; - let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() }; - let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() }; - let getter_body = vec![local_cached, if_cached, local_poison, if_poison, local_val, set_call, ret_stmt]; + let call_compute = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: compute_name.clone(), + arguments: vec![], + span: Span::unknown(), + }; + let local_val = ASTNode::Local { + variables: vec![val_local.clone()], + initial_values: vec![Some(Box::new(call_compute))], + span: Span::unknown(), + }; + let set_call = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "setField".to_string(), + arguments: vec![ + ASTNode::Literal { + value: crate::ast::LiteralValue::String(key.clone()), + span: Span::unknown(), + }, + ASTNode::Variable { + name: val_local.clone(), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + let ret_stmt = ASTNode::Return { + value: Some(Box::new(ASTNode::Variable { + name: val_local.clone(), + span: Span::unknown(), + })), + span: Span::unknown(), + }; + let getter_body = vec![ + local_cached, + if_cached, + local_poison, + if_poison, + local_val, + set_call, + ret_stmt, + ]; let getter_name = format!("__get_once_{}", name); - let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() }; + let getter = ASTNode::FunctionDeclaration { + name: getter_name.clone(), + params: vec![], + body: getter_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(getter_name, getter); return Ok(true); } @@ -248,13 +507,40 @@ pub(crate) fn try_parse_block_first_property( // birth_once birth_once_props.push(name.clone()); let compute_name = format!("__compute_birth_{}", name); - let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() }; + let compute = ASTNode::FunctionDeclaration { + name: compute_name.clone(), + params: vec![], + body: final_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(compute_name.clone(), compute); - let me_node = ASTNode::Me { span: Span::unknown() }; - let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), span: Span::unknown() }], span: Span::unknown() }; - let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }]; + let me_node = ASTNode::Me { + span: Span::unknown(), + }; + let get_call = ASTNode::MethodCall { + object: Box::new(me_node.clone()), + method: "getField".to_string(), + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(format!("__birth_{}", name)), + span: Span::unknown(), + }], + span: Span::unknown(), + }; + let getter_body = vec![ASTNode::Return { + value: Some(Box::new(get_call)), + span: Span::unknown(), + }]; let getter_name = format!("__get_birth_{}", name); - let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() }; + let getter = ASTNode::FunctionDeclaration { + name: getter_name.clone(), + params: vec![], + body: getter_body, + is_static: false, + is_override: false, + span: Span::unknown(), + }; methods.insert(getter_name, getter); Ok(true) } diff --git a/src/parser/declarations/box_def/mod.rs b/src/parser/declarations/box_def/mod.rs index 0e494fa0..89e31894 100644 --- a/src/parser/declarations/box_def/mod.rs +++ b/src/parser/declarations/box_def/mod.rs @@ -4,15 +4,15 @@ //! Nyashの中核概念「Everything is Box」を実現する重要モジュール use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; pub mod header; +pub mod interface; pub mod members; pub mod validators; -pub mod interface; /// Thin wrappers to keep the main loop tidy (behavior-preserving) fn box_try_block_first_property( @@ -20,9 +20,7 @@ fn box_try_block_first_property( methods: &mut HashMap, birth_once_props: &mut Vec, ) -> Result { - members::properties::try_parse_block_first_property( - p, methods, birth_once_props, - ) + members::properties::try_parse_block_first_property(p, methods, birth_once_props) } fn box_try_method_postfix_after_last( @@ -30,9 +28,7 @@ fn box_try_method_postfix_after_last( methods: &mut HashMap, last_method_name: &Option, ) -> Result { - members::postfix::try_parse_method_postfix_after_last_method( - p, methods, last_method_name, - ) + members::postfix::try_parse_method_postfix_after_last_method(p, methods, last_method_name) } fn box_try_init_block( @@ -40,9 +36,7 @@ fn box_try_init_block( init_fields: &mut Vec, weak_fields: &mut Vec, ) -> Result { - members::fields::parse_init_block_if_any( - p, init_fields, weak_fields, - ) + members::fields::parse_init_block_if_any(p, init_fields, weak_fields) } fn box_try_constructor( @@ -88,23 +82,15 @@ fn box_try_method_or_field( 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, - )? { + 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, - ) + members::fields::try_parse_header_first_field_or_property(p, name, methods, fields) } /// box宣言をパース: box Name { fields... methods... } @@ -118,8 +104,7 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result }); } p.advance(); // consume BOX or FLOW - let (name, type_parameters, extends, implements) = - header::parse_header(p)?; + let (name, type_parameters, extends, implements) = header::parse_header(p)?; p.consume(TokenType::LBRACE)?; @@ -130,7 +115,7 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result 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() + // 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; @@ -143,10 +128,14 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result } // nyashモード(block-first): { body } as (once|birth_once)? name : Type - if box_try_block_first_property(p, &mut methods, &mut birth_once_props)? { continue; } + 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; } + if box_try_method_postfix_after_last(p, &mut methods, &last_method_name)? { + continue; + } // RBRACEに到達していればループを抜ける if p.match_token(&TokenType::RBRACE) { @@ -154,7 +143,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result } // initブロックの処理(initメソッドではない場合のみ) - if box_try_init_block(p, &mut init_fields, &mut weak_fields)? { continue; } + if box_try_init_block(p, &mut init_fields, &mut weak_fields)? { + continue; + } // overrideキーワードをチェック let mut is_override = false; @@ -164,7 +155,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result } // constructor parsing moved to members::constructors - if box_try_constructor(p, is_override, &mut constructors)? { continue; } + if box_try_constructor(p, is_override, &mut constructors)? { + continue; + } // 🚨 birth()統一システム: Box名コンストラクタ無効化 validators::forbid_box_named_constructor(p, &name)?; @@ -183,10 +176,14 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result &mut public_fields, &mut private_fields, &mut last_method_name, - )? { continue; } + )? { + 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::config::env::unified_members() + && (field_or_method == "once" || field_or_method == "birth_once") + { if members::properties::try_parse_unified_property( p, &field_or_method, @@ -207,7 +204,9 @@ pub fn parse_box_declaration(p: &mut NyashParser) -> Result &mut fields, &birth_once_props, &mut last_method_name, - )? { continue; } + )? { + continue; + } } else { return Err(ParseError::UnexpectedToken { expected: "method or field name".to_string(), diff --git a/src/parser/declarations/box_def/validators.rs b/src/parser/declarations/box_def/validators.rs index a4af1afc..4d11fe6e 100644 --- a/src/parser/declarations/box_def/validators.rs +++ b/src/parser/declarations/box_def/validators.rs @@ -1,7 +1,7 @@ //! Validators and light analysis for box members use crate::ast::ASTNode; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::{HashMap, HashSet}; @@ -67,11 +67,17 @@ pub(crate) fn validate_birth_once_cycles( temp: &mut HashSet, perm: &mut HashSet, ) -> bool { - if perm.contains(node) { return false; } - if !temp.insert(node.to_string()) { return true; } // back-edge + if perm.contains(node) { + return false; + } + if !temp.insert(node.to_string()) { + return true; + } // back-edge if let Some(ns) = deps.get(node) { for n in ns { - if has_cycle(n, deps, temp, perm) { return true; } + if has_cycle(n, deps, temp, perm) { + return true; + } } } temp.remove(node); @@ -94,7 +100,10 @@ pub(crate) fn validate_birth_once_cycles( } /// Forbid constructor call with the same name as the box; enforce `birth()` usage. -pub(crate) fn forbid_box_named_constructor(p: &mut NyashParser, box_name: &str) -> Result<(), ParseError> { +pub(crate) fn forbid_box_named_constructor( + p: &mut NyashParser, + box_name: &str, +) -> Result<(), ParseError> { if let TokenType::IDENTIFIER(id) = &p.current_token().token_type { if id == box_name && p.peek_token() == &TokenType::LPAREN { return Err(ParseError::UnexpectedToken { @@ -122,14 +131,29 @@ fn ast_collect_me_fields(nodes: &Vec) -> std::collections::HashSet scan(statements, out), - ASTNode::If { then_body, else_body, .. } => { + ASTNode::If { + then_body, + else_body, + .. + } => { scan(then_body, out); - if let Some(eb) = else_body { scan(eb, out); } + if let Some(eb) = else_body { + scan(eb, out); + } } - ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + ASTNode::TryCatch { + try_body, + catch_clauses, + finally_body, + .. + } => { scan(try_body, out); - for c in catch_clauses { scan(&c.body, out); } - if let Some(fb) = finally_body { scan(fb, out); } + for c in catch_clauses { + scan(&c.body, out); + } + if let Some(fb) = finally_body { + scan(fb, out); + } } ASTNode::FunctionDeclaration { body, .. } => scan(body, out), _ => {} diff --git a/src/parser/declarations/static_def/header.rs b/src/parser/declarations/static_def/header.rs index 702496ce..8ce28910 100644 --- a/src/parser/declarations/static_def/header.rs +++ b/src/parser/declarations/static_def/header.rs @@ -1,6 +1,6 @@ //! Header parsing for `static box Name from Parent1, ... [interface ...]` (staged) -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; /// Parse the leading header of a static box and return @@ -30,7 +30,11 @@ pub(crate) fn parse_static_header( if let TokenType::IDENTIFIER(param_name) = &p.current_token().token_type { params.push(param_name.clone()); p.advance(); - if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; } + if p.match_token(&TokenType::COMMA) { + p.advance(); + } else { + break; + } } else { let line = p.current_token().line; return Err(ParseError::UnexpectedToken { @@ -42,7 +46,9 @@ pub(crate) fn parse_static_header( } p.consume(TokenType::GREATER)?; params - } else { Vec::new() }; + } else { + Vec::new() + }; // extends: from Parent1, Parent2 let extends = if p.match_token(&TokenType::FROM) { @@ -52,7 +58,11 @@ pub(crate) fn parse_static_header( if let TokenType::IDENTIFIER(parent_name) = &p.current_token().token_type { parents.push(parent_name.clone()); p.advance(); - if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; } + if p.match_token(&TokenType::COMMA) { + p.advance(); + } else { + break; + } } else { let line = p.current_token().line; return Err(ParseError::UnexpectedToken { @@ -63,7 +73,9 @@ pub(crate) fn parse_static_header( } } parents - } else { Vec::new() }; + } else { + Vec::new() + }; // implements: `interface A, B` (optional) let implements = if p.match_token(&TokenType::INTERFACE) { @@ -73,7 +85,11 @@ pub(crate) fn parse_static_header( if let TokenType::IDENTIFIER(interface_name) = &p.current_token().token_type { list.push(interface_name.clone()); p.advance(); - if p.match_token(&TokenType::COMMA) { p.advance(); } else { break; } + if p.match_token(&TokenType::COMMA) { + p.advance(); + } else { + break; + } } else { let line = p.current_token().line; return Err(ParseError::UnexpectedToken { @@ -84,8 +100,9 @@ pub(crate) fn parse_static_header( } } list - } else { Vec::new() }; + } else { + Vec::new() + }; Ok((name, type_parameters, extends, implements)) } - diff --git a/src/parser/declarations/static_def/members.rs b/src/parser/declarations/static_def/members.rs index ee6c93d8..6e55b15d 100644 --- a/src/parser/declarations/static_def/members.rs +++ b/src/parser/declarations/static_def/members.rs @@ -1,7 +1,7 @@ //! Members helpers for static box (staged) use crate::ast::ASTNode; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; @@ -10,8 +10,13 @@ use std::collections::HashMap; pub(crate) fn parse_static_initializer_if_any( p: &mut NyashParser, ) -> Result>, ParseError> { - if !p.match_token(&TokenType::STATIC) { return Ok(None); } - let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT").ok().as_deref() == Some("1"); + if !p.match_token(&TokenType::STATIC) { + return Ok(None); + } + let strict = std::env::var("NYASH_PARSER_STATIC_INIT_STRICT") + .ok() + .as_deref() + == Some("1"); if strict { match p.peek_token() { TokenType::LBRACE => { @@ -61,18 +66,26 @@ pub(crate) fn try_parse_method_or_field( if !p.match_token(&TokenType::LPAREN) { // Lookahead skipping NEWLINE to see if a '(' follows → treat as method head let mut k = 0usize; - while matches!(p.peek_nth_token(k), TokenType::NEWLINE) { k += 1; } + while matches!(p.peek_nth_token(k), TokenType::NEWLINE) { + k += 1; + } if matches!(p.peek_nth_token(k), TokenType::LPAREN) { // Consume intervening NEWLINEs so current becomes '(' - while p.match_token(&TokenType::NEWLINE) { p.advance(); } + while p.match_token(&TokenType::NEWLINE) { + p.advance(); + } } else { - if trace { eprintln!("[parser][static-box] field detected: {}", name); } + if trace { + eprintln!("[parser][static-box] field detected: {}", name); + } // Field fields.push(name); return Ok(true); } } - if trace { eprintln!("[parser][static-box] method head detected: {}(..)", name); } + if trace { + eprintln!("[parser][static-box] method head detected: {}(..)", name); + } // Method p.advance(); // consume '(' let mut params = Vec::new(); @@ -95,7 +108,11 @@ pub(crate) fn try_parse_method_or_field( } _ => { // Unexpected token handling - if std::env::var("NYASH_PARSER_METHOD_PARAM_STRICT").ok().as_deref() == Some("1") { + if std::env::var("NYASH_PARSER_METHOD_PARAM_STRICT") + .ok() + .as_deref() + == Some("1") + { return Err(ParseError::UnexpectedToken { found: p.current_token().token_type.clone(), expected: "parameter identifier, comma, or )".to_string(), @@ -110,9 +127,15 @@ pub(crate) fn try_parse_method_or_field( } p.consume(TokenType::RPAREN)?; // Allow NEWLINE(s) between ')' and '{' of method body - while p.match_token(&TokenType::NEWLINE) { p.advance(); } + while p.match_token(&TokenType::NEWLINE) { + p.advance(); + } // Parse method body; optionally use strict method-body guard when enabled - let body = if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") { + let body = if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT") + .ok() + .as_deref() + == Some("1") + { p.parse_method_body_statements()? } else { p.parse_block_statements()? diff --git a/src/parser/declarations/static_def/mod.rs b/src/parser/declarations/static_def/mod.rs index 431a7aa3..7f6ebda8 100644 --- a/src/parser/declarations/static_def/mod.rs +++ b/src/parser/declarations/static_def/mod.rs @@ -2,8 +2,8 @@ #![allow(dead_code)] use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; use std::collections::HashMap; @@ -14,8 +14,7 @@ pub mod validators; /// 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)?; + let (name, type_parameters, extends, implements) = header::parse_static_header(p)?; p.consume(TokenType::LBRACE)?; @@ -30,7 +29,9 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { 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(); } + 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!( @@ -56,7 +57,11 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { } 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_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)"); } @@ -66,14 +71,23 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { // initブロックの処理(共通ヘルパに委譲) if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any( - p, &mut init_fields, &mut weak_fields, - )? { continue; } + 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 => { + 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); @@ -87,13 +101,15 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { // 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; } + 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"); + 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!( @@ -120,7 +136,11 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { 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, + p, + field_or_method, + &mut methods, + &mut fields, + &mut last_method_name, )?; } _ => { @@ -134,7 +154,9 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { } // Tolerate trailing NEWLINE(s) before the closing '}' of the static box - while p.match_token(&TokenType::NEWLINE) { p.advance(); } + 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={:?}", @@ -146,14 +168,16 @@ pub fn parse_static_box(p: &mut NyashParser) -> Result { 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); + 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); + p.static_box_dependencies.insert(name.clone(), dependencies); } else { p.static_box_dependencies .insert(name.clone(), std::collections::HashSet::new()); diff --git a/src/parser/declarations/static_def/validators.rs b/src/parser/declarations/static_def/validators.rs index 5e8d7006..400117f4 100644 --- a/src/parser/declarations/static_def/validators.rs +++ b/src/parser/declarations/static_def/validators.rs @@ -7,6 +7,7 @@ pub(crate) struct StaticValidators; impl StaticValidators { #[allow(dead_code)] - pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> { Ok(()) } + pub(crate) fn validate(_p: &mut NyashParser) -> Result<(), ParseError> { + Ok(()) + } } - diff --git a/src/parser/expr/match_expr.rs b/src/parser/expr/match_expr.rs index a09855d5..1725b35f 100644 --- a/src/parser/expr/match_expr.rs +++ b/src/parser/expr/match_expr.rs @@ -8,13 +8,22 @@ impl NyashParser { /// MVP: リテラルパターン+OR+デフォルト(_) のみ。アーム本体は式またはブロック。 pub(crate) fn expr_parse_match(&mut self) -> Result { self.advance(); // consume 'match' - // Scrutinee: 通常の式を受理(演算子優先順位を含む) + // Scrutinee: 通常の式を受理(演算子優先順位を含む) let scrutinee = self.parse_expression()?; self.consume(TokenType::LBRACE)?; enum MatchArm { - Lit { lits: Vec, guard: Option, body: ASTNode }, - Type { ty: String, bind: String, guard: Option, body: ASTNode }, + Lit { + lits: Vec, + guard: Option, + body: ASTNode, + }, + Type { + ty: String, + bind: String, + guard: Option, + body: ASTNode, + }, Default, } @@ -34,10 +43,11 @@ impl NyashParser { } // default '_' or type/literal arm - let is_default = matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_"); + let is_default = + matches!(self.current_token().token_type, TokenType::IDENTIFIER(ref s) if s == "_"); if is_default { self.advance(); // consume '_' - // MVP: default '_' does not accept guard + // MVP: default '_' does not accept guard if self.match_token(&TokenType::IF) { let line = self.current_token().line; return Err(ParseError::UnexpectedToken { @@ -106,7 +116,9 @@ impl NyashParser { let g = self.parse_expression()?; saw_guard = true; Some(g) - } else { None }; + } else { + None + }; self.consume(TokenType::FatArrow)?; let body = if self.match_token(&TokenType::LBRACE) { if self.is_object_literal() { @@ -123,14 +135,22 @@ impl NyashParser { } } self.consume(TokenType::RBRACE)?; - ASTNode::Program { statements: stmts, span: Span::unknown() } + ASTNode::Program { + statements: stmts, + span: Span::unknown(), + } } } else { // 値アームは通常の式全体を受理 self.parse_expression()? }; // type arm parsed - arms_any.push(MatchArm::Type { ty, bind, guard, body }); + arms_any.push(MatchArm::Type { + ty, + bind, + guard, + body, + }); saw_type_arm = true; handled = true; } @@ -151,7 +171,9 @@ impl NyashParser { let g = self.parse_expression()?; saw_guard = true; Some(g) - } else { None }; + } else { + None + }; self.consume(TokenType::FatArrow)?; let expr = if self.match_token(&TokenType::LBRACE) { if self.is_object_literal() { @@ -168,13 +190,20 @@ impl NyashParser { } } self.consume(TokenType::RBRACE)?; - ASTNode::Program { statements: stmts, span: Span::unknown() } + ASTNode::Program { + statements: stmts, + span: Span::unknown(), + } } } else { // 値アームは通常の式全体を受理 self.parse_expression()? }; - arms_any.push(MatchArm::Lit { lits, guard, body: expr }); + arms_any.push(MatchArm::Lit { + lits, + guard, + body: expr, + }); } } @@ -197,7 +226,11 @@ impl NyashParser { let mut lit_arms: Vec<(LiteralValue, ASTNode)> = Vec::new(); for arm in arms_any.into_iter() { match arm { - MatchArm::Lit { lits, guard: _, body } => { + MatchArm::Lit { + lits, + guard: _, + body, + } => { for lit in lits.into_iter() { lit_arms.push((lit, body.clone())); } @@ -226,7 +259,10 @@ impl NyashParser { // 2) アーム順に If 連鎖を構築 let mut else_node: ASTNode = else_expr; // Wrap else body in Program for uniformity - else_node = ASTNode::Program { statements: vec![else_node], span: Span::unknown() }; + else_node = ASTNode::Program { + statements: vec![else_node], + span: Span::unknown(), + }; // Process arms in reverse to build nested If for arm in arms_any.into_iter().rev() { @@ -240,8 +276,14 @@ impl NyashParser { for lit in lits.into_iter() { let eq = ASTNode::BinaryOp { operator: BinaryOperator::Equal, - left: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }), - right: Box::new(ASTNode::Literal { value: lit, span: Span::unknown() }), + left: Box::new(ASTNode::Variable { + name: scr_var.clone(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: lit, + span: Span::unknown(), + }), span: Span::unknown(), }; cond = Some(match cond { @@ -254,7 +296,10 @@ impl NyashParser { }, }); } - let else_statements = match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] }; + let else_statements = match else_node.clone() { + ASTNode::Program { statements, .. } => statements, + other => vec![other], + }; let then_body_statements = if let Some(g) = guard { // Nested guard: if g then body else else_node let guard_if = ASTNode::If { @@ -268,25 +313,44 @@ impl NyashParser { vec![body] }; else_node = ASTNode::If { - condition: Box::new(cond.expect("literal arm must have at least one literal")), + condition: Box::new( + cond.expect("literal arm must have at least one literal"), + ), then_body: then_body_statements, else_body: Some(else_statements), span: Span::unknown(), }; } - MatchArm::Type { ty, bind, guard, body } => { + MatchArm::Type { + ty, + bind, + guard, + body, + } => { // condition: scr.is("Type") let is_call = ASTNode::MethodCall { - object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }), + object: Box::new(ASTNode::Variable { + name: scr_var.clone(), + span: Span::unknown(), + }), method: "is".to_string(), - arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }], + arguments: vec![ASTNode::Literal { + value: LiteralValue::String(ty.clone()), + span: Span::unknown(), + }], span: Span::unknown(), }; // then: local bind = scr.as("Type"); let cast = ASTNode::MethodCall { - object: Box::new(ASTNode::Variable { name: scr_var.clone(), span: Span::unknown() }), + object: Box::new(ASTNode::Variable { + name: scr_var.clone(), + span: Span::unknown(), + }), method: "as".to_string(), - arguments: vec![ASTNode::Literal { value: LiteralValue::String(ty.clone()), span: Span::unknown() }], + arguments: vec![ASTNode::Literal { + value: LiteralValue::String(ty.clone()), + span: Span::unknown(), + }], span: Span::unknown(), }; let bind_local = ASTNode::Local { @@ -294,7 +358,10 @@ impl NyashParser { initial_values: vec![Some(Box::new(cast))], span: Span::unknown(), }; - let else_statements = match else_node.clone() { ASTNode::Program { statements, .. } => statements, other => vec![other] }; + let else_statements = match else_node.clone() { + ASTNode::Program { statements, .. } => statements, + other => vec![other], + }; let then_body_statements = if let Some(g) = guard { // After binding, check guard then branch to body else fallthrough to else_node let guard_if = ASTNode::If { @@ -344,7 +411,7 @@ impl NyashParser { } matches!(self.peek_nth_token(lookahead_idx), TokenType::COLON) } - _ => false + _ => false, } } diff --git a/src/parser/expr/mod.rs b/src/parser/expr/mod.rs index 13bd9543..6004c76e 100644 --- a/src/parser/expr/mod.rs +++ b/src/parser/expr/mod.rs @@ -4,9 +4,9 @@ pub(crate) mod coalesce; pub(crate) mod compare; pub(crate) mod factor; pub(crate) mod logic; +pub(crate) mod match_expr; pub(crate) mod primary; pub(crate) mod range; pub(crate) mod shift; pub(crate) mod term; pub(crate) mod ternary; -pub(crate) mod match_expr; diff --git a/src/parser/expr/primary.rs b/src/parser/expr/primary.rs index 33cd992d..22396e05 100644 --- a/src/parser/expr/primary.rs +++ b/src/parser/expr/primary.rs @@ -46,8 +46,8 @@ impl NyashParser { // デフォルトでIDENTIFIERキーを許可(basicが明示的に指定された場合のみ無効) let ident_key_on = std::env::var("NYASH_ENABLE_MAP_IDENT_KEY").ok().as_deref() == Some("1") - || sugar_level.as_deref() != Some("basic"); // basic以外は全て許可(デフォルト含む) - // skip_newlines削除: brace_depth > 0なので自動スキップされる + || sugar_level.as_deref() != Some("basic"); // basic以外は全て許可(デフォルト含む) + // skip_newlines削除: brace_depth > 0なので自動スキップされる while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() { // skip_newlines削除: brace_depth > 0なので自動スキップされる let key = match &self.current_token().token_type { diff --git a/src/parser/expr_cursor.rs b/src/parser/expr_cursor.rs index 52eda1c5..d7b93c17 100644 --- a/src/parser/expr_cursor.rs +++ b/src/parser/expr_cursor.rs @@ -1,8 +1,8 @@ use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::parser::cursor::TokenCursor; +use crate::parser::sugar_gate; use crate::parser::ParseError; use crate::tokenizer::TokenType; -use crate::parser::sugar_gate; /// TokenCursorを使用した式パーサー(実験的実装) pub struct ExprParserWithCursor; @@ -11,9 +11,7 @@ impl ExprParserWithCursor { /// 式をパース(TokenCursor版) pub fn parse_expression(cursor: &mut TokenCursor) -> Result { // 式モードで実行(改行を自動的にスキップ) - cursor.with_expr_mode(|c| { - Self::parse_or_expr(c) - }) + cursor.with_expr_mode(|c| Self::parse_or_expr(c)) } /// OR式をパース @@ -300,7 +298,10 @@ impl ExprParserWithCursor { } } cursor.consume(TokenType::RBRACK)?; - Ok(ASTNode::ArrayLiteral { elements, span: Span::unknown() }) + Ok(ASTNode::ArrayLiteral { + elements, + span: Span::unknown(), + }) } TokenType::NUMBER(n) => { let value = *n; @@ -409,10 +410,17 @@ impl ExprParserWithCursor { while !cursor.match_token(&TokenType::RPAREN) && !cursor.is_at_end() { let arg = Self::parse_expression(cursor)?; arguments.push(arg); - if cursor.match_token(&TokenType::COMMA) { cursor.advance(); } + if cursor.match_token(&TokenType::COMMA) { + cursor.advance(); + } } cursor.consume(TokenType::RPAREN)?; - Ok(ASTNode::New { class, arguments, type_arguments, span: Span::unknown() }) + Ok(ASTNode::New { + class, + arguments, + type_arguments, + span: Span::unknown(), + }) } _ => { let line = cursor.current().line; diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 7355d7f7..3fd15a2a 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -9,9 +9,9 @@ use super::common::ParserUtils; use super::{NyashParser, ParseError}; use crate::ast::{ASTNode, Span, UnaryOperator}; -use crate::tokenizer::TokenType; use crate::parser::cursor::TokenCursor; use crate::parser::expr_cursor::ExprParserWithCursor; +use crate::tokenizer::TokenType; // Debug macros are now imported from the parent module via #[macro_export] use crate::must_advance; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 598a0b36..fd63d1d8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -182,31 +182,76 @@ impl NyashParser { while let Some(c) = it.next() { if in_line { out.push(c); - if c == '\n' { in_line = false; } + if c == '\n' { + in_line = false; + } continue; } if in_block { out.push(c); - if c == '*' && matches!(it.peek(), Some('/')) { out.push('/'); it.next(); in_block = false; } + if c == '*' && matches!(it.peek(), Some('/')) { + out.push('/'); + it.next(); + in_block = false; + } continue; } if in_str { out.push(c); - if c == '\\' { if let Some(nc) = it.next() { out.push(nc); } continue; } - if c == '"' { in_str = false; } + if c == '\\' { + if let Some(nc) = it.next() { + out.push(nc); + } + continue; + } + if c == '"' { + in_str = false; + } continue; } match c { - '"' => { in_str = true; out.push(c); } - '/' => { - match it.peek() { Some('/') => { out.push('/'); out.push('/'); it.next(); in_line = true; }, Some('*') => { out.push('/'); out.push('*'); it.next(); in_block = true; }, _ => out.push('/') } + '"' => { + in_str = true; + out.push(c); + } + '/' => match it.peek() { + Some('/') => { + out.push('/'); + out.push('/'); + it.next(); + in_line = true; + } + Some('*') => { + out.push('/'); + out.push('*'); + it.next(); + in_block = true; + } + _ => out.push('/'), + }, + '#' => { + in_line = true; + out.push('#'); } - '#' => { in_line = true; out.push('#'); } '|' => { - if matches!(it.peek(), Some('|')) { out.push_str(" or "); it.next(); } else if matches!(it.peek(), Some('>')) { out.push('|'); out.push('>'); it.next(); } else { out.push('|'); } + if matches!(it.peek(), Some('|')) { + out.push_str(" or "); + it.next(); + } else if matches!(it.peek(), Some('>')) { + out.push('|'); + out.push('>'); + it.next(); + } else { + out.push('|'); + } } '&' => { - if matches!(it.peek(), Some('&')) { out.push_str(" and "); it.next(); } else { out.push('&'); } + if matches!(it.peek(), Some('&')) { + out.push_str(" and "); + it.next(); + } else { + out.push('&'); + } } _ => out.push(c), } @@ -247,10 +292,13 @@ impl NyashParser { let mut statements = Vec::new(); let mut _statement_count = 0; - let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| { - let lv = v.to_ascii_lowercase(); - !(lv == "0" || lv == "false" || lv == "off") - }).unwrap_or(true); + let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON") + .ok() + .map(|v| { + let lv = v.to_ascii_lowercase(); + !(lv == "0" || lv == "false" || lv == "off") + }) + .unwrap_or(true); while !self.is_at_end() { // EOF tokenはスキップ @@ -293,13 +341,13 @@ impl NyashParser { // 左辺が代入可能な形式かチェック match &expr { - ASTNode::Variable { .. } - | ASTNode::FieldAccess { .. } - | ASTNode::Index { .. } => Ok(ASTNode::Assignment { - target: Box::new(expr), - value, - span: Span::unknown(), - }), + ASTNode::Variable { .. } | ASTNode::FieldAccess { .. } | ASTNode::Index { .. } => { + Ok(ASTNode::Assignment { + target: Box::new(expr), + value, + span: Span::unknown(), + }) + } _ => { let line = self.current_token().line; Err(ParseError::InvalidStatement { line }) @@ -365,9 +413,17 @@ impl NyashParser { // ---- Minimal ParserUtils impl (depth-less; TokenCursor handles newline policy) ---- impl common::ParserUtils for NyashParser { - fn tokens(&self) -> &Vec { &self.tokens } - fn current(&self) -> usize { self.current } - fn current_mut(&mut self) -> &mut usize { &mut self.current } - fn update_depth_before_advance(&mut self) { /* no-op (legacy removed) */ } - fn update_depth_after_advance(&mut self) { /* no-op (legacy removed) */ } + fn tokens(&self) -> &Vec { + &self.tokens + } + fn current(&self) -> usize { + self.current + } + fn current_mut(&mut self) -> &mut usize { + &mut self.current + } + fn update_depth_before_advance(&mut self) { /* no-op (legacy removed) */ + } + fn update_depth_after_advance(&mut self) { /* no-op (legacy removed) */ + } } diff --git a/src/parser/statements/control_flow.rs b/src/parser/statements/control_flow.rs index 5d084f74..59ff9f55 100644 --- a/src/parser/statements/control_flow.rs +++ b/src/parser/statements/control_flow.rs @@ -9,9 +9,9 @@ */ use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; use crate::parser::cursor::TokenCursor; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { diff --git a/src/parser/statements/declarations.rs b/src/parser/statements/declarations.rs index b355f5f2..49421db0 100644 --- a/src/parser/statements/declarations.rs +++ b/src/parser/statements/declarations.rs @@ -6,8 +6,8 @@ */ use crate::ast::ASTNode; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -17,7 +17,9 @@ impl NyashParser { 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 => crate::parser::declarations::box_def::parse_interface_box_declaration(self), + 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(), @@ -28,4 +30,4 @@ impl NyashParser { }), } } -} \ No newline at end of file +} diff --git a/src/parser/statements/exceptions.rs b/src/parser/statements/exceptions.rs index e0650e8f..39baa7db 100644 --- a/src/parser/statements/exceptions.rs +++ b/src/parser/statements/exceptions.rs @@ -8,8 +8,8 @@ */ use crate::ast::{ASTNode, CatchClause, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -76,7 +76,9 @@ impl NyashParser { } /// Parse catch parameter: (ExceptionType varName) or (varName) or () - pub(crate) fn parse_catch_param(&mut self) -> Result<(Option, Option), ParseError> { + pub(crate) fn parse_catch_param( + &mut self, + ) -> Result<(Option, Option), ParseError> { match &self.current_token().token_type { TokenType::IDENTIFIER(first) => { let first_str = first.clone(); @@ -121,4 +123,4 @@ impl NyashParser { line: self.current_token().line, }) } -} \ No newline at end of file +} diff --git a/src/parser/statements/helpers.rs b/src/parser/statements/helpers.rs index 78b01acc..6935d5b2 100644 --- a/src/parser/statements/helpers.rs +++ b/src/parser/statements/helpers.rs @@ -5,9 +5,9 @@ */ use crate::ast::ASTNode; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; use crate::parser::cursor::TokenCursor; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; /// Check if token cursor is enabled @@ -16,7 +16,6 @@ pub(super) fn cursor_enabled() -> bool { } impl NyashParser { - /// Thin adapter: when Cursor route is enabled, align statement start position /// by letting TokenCursor apply its statement-mode newline policy pub(super) fn with_stmt_cursor(&mut self, f: F) -> Result diff --git a/src/parser/statements/io_async.rs b/src/parser/statements/io_async.rs index 5e31a640..8fccc3d1 100644 --- a/src/parser/statements/io_async.rs +++ b/src/parser/statements/io_async.rs @@ -7,9 +7,9 @@ */ use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; use crate::parser::cursor::TokenCursor; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -71,4 +71,4 @@ impl NyashParser { span: Span::unknown(), }) } -} \ No newline at end of file +} diff --git a/src/parser/statements/mod.rs b/src/parser/statements/mod.rs index 708b527b..2caacf22 100644 --- a/src/parser/statements/mod.rs +++ b/src/parser/statements/mod.rs @@ -27,8 +27,8 @@ pub mod exceptions; pub mod modules; use crate::ast::{ASTNode, CatchClause, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -94,14 +94,20 @@ impl NyashParser { // Critical: Skip any leading NEWLINE tokens immediately after '{' // This ensures the first statement starts at the correct position - while self.match_token(&TokenType::NEWLINE) { self.advance(); } + while self.match_token(&TokenType::NEWLINE) { + self.advance(); + } let mut statements = Vec::new(); // Be tolerant to blank lines within blocks: skip NEWLINE tokens between statements while !self.is_at_end() { - while self.match_token(&TokenType::NEWLINE) { self.advance(); } - if self.match_token(&TokenType::RBRACE) { break; } + while self.match_token(&TokenType::NEWLINE) { + self.advance(); + } + if self.match_token(&TokenType::RBRACE) { + break; + } statements.push(self.parse_statement()?); } if trace_blocks { @@ -137,21 +143,33 @@ impl NyashParser { TokenType::IDENTIFIER(_) => { // Expect '(' after optional NEWLINE let mut k = 1usize; - while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; } - if !matches!(this.peek_nth_token(k), TokenType::LPAREN) { return false; } + while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { + k += 1; + } + if !matches!(this.peek_nth_token(k), TokenType::LPAREN) { + return false; + } // Walk to matching ')' k += 1; // after '(' let mut depth: i32 = 1; while !matches!(this.peek_nth_token(k), TokenType::EOF) { match this.peek_nth_token(k) { TokenType::LPAREN => depth += 1, - TokenType::RPAREN => { depth -= 1; if depth == 0 { k += 1; break; } }, + TokenType::RPAREN => { + depth -= 1; + if depth == 0 { + k += 1; + break; + } + } _ => {} } k += 1; } // Allow NEWLINE(s) between ')' and '{' - while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; } + while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { + k += 1; + } matches!(this.peek_nth_token(k), TokenType::LBRACE) } _ => false, @@ -160,14 +178,22 @@ impl NyashParser { while !self.is_at_end() { // Skip blank lines at method body top-level - while self.match_token(&TokenType::NEWLINE) { self.advance(); } + while self.match_token(&TokenType::NEWLINE) { + self.advance(); + } // Stop at end of current method body - if self.match_token(&TokenType::RBRACE) { break; } + if self.match_token(&TokenType::RBRACE) { + break; + } // Optional seam guard: if the upcoming tokens form a method head // like `ident '(' ... ')' NEWLINE* '{'`, bail out so the caller // (static box member parser) can handle it as a declaration, not // as a function call expression inside this body. - if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") { + if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT") + .ok() + .as_deref() + == Some("1") + { if looks_like_method_head(self) { break; } diff --git a/src/parser/statements/modules.rs b/src/parser/statements/modules.rs index affda408..ed2895de 100644 --- a/src/parser/statements/modules.rs +++ b/src/parser/statements/modules.rs @@ -8,8 +8,8 @@ */ use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -123,7 +123,10 @@ impl NyashParser { } } - Ok(ASTNode::UsingStatement { namespace_name: namespace, span: Span::unknown() }) + Ok(ASTNode::UsingStatement { + namespace_name: namespace, + span: Span::unknown(), + }) } /// Parse from statement: from Parent.method(args) diff --git a/src/parser/statements/variables.rs b/src/parser/statements/variables.rs index fcfd8b2b..e7ae63a2 100644 --- a/src/parser/statements/variables.rs +++ b/src/parser/statements/variables.rs @@ -8,9 +8,9 @@ */ use crate::ast::{ASTNode, Span}; -use crate::parser::{NyashParser, ParseError}; use crate::parser::common::ParserUtils; use crate::parser::cursor::TokenCursor; +use crate::parser::{NyashParser, ParseError}; use crate::tokenizer::TokenType; impl NyashParser { @@ -29,10 +29,14 @@ impl NyashParser { /// Parse local variable declaration: local var1, var2, var3 or local x = 10 pub(super) fn parse_local(&mut self) -> Result { - let debug_parse_local = std::env::var("NYASH_DEBUG_PARSE_LOCAL").ok().as_deref() == Some("1"); + let debug_parse_local = + std::env::var("NYASH_DEBUG_PARSE_LOCAL").ok().as_deref() == Some("1"); if debug_parse_local { - eprintln!("[parse_local] entry: current_token={:?} at line {}", - self.current_token().token_type, self.current_token().line); + eprintln!( + "[parse_local] entry: current_token={:?} at line {}", + self.current_token().token_type, + self.current_token().line + ); } // Always skip leading NEWLINEs before consuming 'local' keyword @@ -54,8 +58,11 @@ impl NyashParser { } if debug_parse_local { - eprintln!("[parse_local] after advance: current_token={:?} at line {}", - self.current_token().token_type, self.current_token().line); + eprintln!( + "[parse_local] after advance: current_token={:?} at line {}", + self.current_token().token_type, + self.current_token().line + ); } let mut names = Vec::new(); @@ -107,8 +114,11 @@ impl NyashParser { } else { // Enhanced error message for debugging if debug_parse_local { - eprintln!("[parse_local] ERROR: Expected IDENTIFIER, found {:?} at line {}", - self.current_token().token_type, self.current_token().line); + eprintln!( + "[parse_local] ERROR: Expected IDENTIFIER, found {:?} at line {}", + self.current_token().token_type, + self.current_token().line + ); eprintln!("[parse_local] ERROR: Previous 3 tokens:"); for i in 1..=3 { if self.current >= i { @@ -168,4 +178,4 @@ impl NyashParser { }) } } -} \ No newline at end of file +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs index d2469e3f..c63ab7c6 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1,4 +1,3 @@ //! Providers root module — exposes ring-1 core providers. pub mod ring1; - diff --git a/src/providers/ring1/file/core_ro.rs b/src/providers/ring1/file/core_ro.rs index 4065776b..1194a21d 100644 --- a/src/providers/ring1/file/core_ro.rs +++ b/src/providers/ring1/file/core_ro.rs @@ -1,7 +1,7 @@ //! Core read‑only File I/O provider (ring‑1). //! Provides basic read-only file operations using std::fs::File. -use crate::boxes::file::provider::{FileCaps, FileError, FileIo, FileResult, normalize_newlines}; +use crate::boxes::file::provider::{normalize_newlines, FileCaps, FileError, FileIo, FileResult}; use std::fs::File; use std::io::Read; use std::sync::RwLock; @@ -12,12 +12,16 @@ pub struct CoreRoFileIo { impl CoreRoFileIo { pub fn new() -> Self { - Self { handle: RwLock::new(None) } + Self { + handle: RwLock::new(None), + } } } impl FileIo for CoreRoFileIo { - fn caps(&self) -> FileCaps { FileCaps::read_only() } + fn caps(&self) -> FileCaps { + FileCaps::read_only() + } fn open(&self, path: &str) -> FileResult<()> { let file = File::open(path) @@ -43,4 +47,3 @@ impl FileIo for CoreRoFileIo { Ok(()) } } - diff --git a/src/providers/ring1/file/mod.rs b/src/providers/ring1/file/mod.rs index 668f7e0f..4fecee53 100644 --- a/src/providers/ring1/file/mod.rs +++ b/src/providers/ring1/file/mod.rs @@ -1,2 +1 @@ pub mod core_ro; - diff --git a/src/providers/ring1/mod.rs b/src/providers/ring1/mod.rs index fa9c04a6..43b686b6 100644 --- a/src/providers/ring1/mod.rs +++ b/src/providers/ring1/mod.rs @@ -1,4 +1,3 @@ //! ring1 — Core Providers module pub mod file; - diff --git a/src/runner/cli_directives.rs b/src/runner/cli_directives.rs index f9dc9023..d6231b37 100644 --- a/src/runner/cli_directives.rs +++ b/src/runner/cli_directives.rs @@ -62,15 +62,32 @@ pub(super) fn apply_cli_directives_from_source( if std::env::var("NYASH_ASI_STRICT").ok().as_deref() == Some("1") { // operators to check (suffixes) const OP2: [&str; 6] = ["==", "!=", "<=", ">=", "&&", "||"]; - const OP1: [&str; 7] = ["+", "-", "*", "/", "%", "<", ">"]; + const OP1: [&str; 7] = ["+", "-", "*", "/", "%", "<", ">"]; for (i, line) in code.lines().enumerate() { let l = line.trim_end(); - if l.is_empty() { continue; } + if l.is_empty() { + continue; + } let mut bad = false; - for op in OP2.iter() { if l.ends_with(op) { bad = true; break; } } - if !bad { for op in OP1.iter() { if l.ends_with(op) { bad = true; break; } } } + for op in OP2.iter() { + if l.ends_with(op) { + bad = true; + break; + } + } + if !bad { + for op in OP1.iter() { + if l.ends_with(op) { + bad = true; + break; + } + } + } if bad { - return Err(format!("Parse error: Strict ASI violation — line {} ends with operator", i + 1)); + return Err(format!( + "Parse error: Strict ASI violation — line {} ends with operator", + i + 1 + )); } } } @@ -100,7 +117,10 @@ pub(super) fn apply_cli_directives_from_source( // find left token end and right token start let (left_ok, right_ok) = (peek_ident_left(l, at), peek_ident_right(l, at + 2)); if left_ok && right_ok { - return Err(format!("Type error: '==' on boxes — use equals() (line {})", i + 1)); + return Err(format!( + "Type error: '==' on boxes — use equals() (line {})", + i + 1 + )); } idx = at + 2; } @@ -127,22 +147,37 @@ fn find_plus_operands(line: &str) -> Option<(&str, &str)> { if bytes[i] as char == '+' { // extract left token let mut l = i; - while l > 0 && bytes[l - 1].is_ascii_whitespace() { l -= 1; } + while l > 0 && bytes[l - 1].is_ascii_whitespace() { + l -= 1; + } let mut lstart = l; while lstart > 0 { let c = bytes[lstart - 1] as char; - if c.is_ascii_alphanumeric() || c == '_' || c == '"' { lstart -= 1; } else { break; } + if c.is_ascii_alphanumeric() || c == '_' || c == '"' { + lstart -= 1; + } else { + break; + } } let left = &line[lstart..l]; // extract right token let mut r = i + 1; - while r < bytes.len() && bytes[r].is_ascii_whitespace() { r += 1; } + while r < bytes.len() && bytes[r].is_ascii_whitespace() { + r += 1; + } let mut rend = r; while rend < bytes.len() { let c = bytes[rend] as char; - if c.is_ascii_alphanumeric() || c == '_' || c == '"' { rend += 1; } else { break; } + if c.is_ascii_alphanumeric() || c == '_' || c == '"' { + rend += 1; + } else { + break; + } + } + if r <= rend { + let right = &line[r..rend]; + return Some((left, right)); } - if r <= rend { let right = &line[r..rend]; return Some((left, right)); } return None; } i += 1; @@ -153,33 +188,61 @@ fn find_plus_operands(line: &str) -> Option<(&str, &str)> { fn peek_ident_left(s: &str, pos: usize) -> bool { // scan left for first non-space token end, then back to token start let bytes = s.as_bytes(); - if pos == 0 { return false; } + if pos == 0 { + return false; + } let mut i = pos; // skip spaces - while i > 0 && bytes[i - 1].is_ascii_whitespace() { i -= 1; } - if i == 0 { return false; } + while i > 0 && bytes[i - 1].is_ascii_whitespace() { + i -= 1; + } + if i == 0 { + return false; + } // now consume identifier chars backwards let mut j = i; while j > 0 { let c = bytes[j - 1] as char; - if c.is_ascii_alphanumeric() || c == '_' { j -= 1; } else { break; } + if c.is_ascii_alphanumeric() || c == '_' { + j -= 1; + } else { + break; + } + } + if j == i { + return false; } - if j == i { return false; } // ensure not starting with digit only (avoid numeric literal) let tok = &s[j..i]; - !tok.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) + !tok.chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) } fn peek_ident_right(s: &str, pos: usize) -> bool { let bytes = s.as_bytes(); let mut i = pos; - while i < bytes.len() && bytes[i].is_ascii_whitespace() { i += 1; } - if i >= bytes.len() { return false; } + while i < bytes.len() && bytes[i].is_ascii_whitespace() { + i += 1; + } + if i >= bytes.len() { + return false; + } let mut j = i; while j < bytes.len() { let c = bytes[j] as char; - if c.is_ascii_alphanumeric() || c == '_' { j += 1; } else { break; } + if c.is_ascii_alphanumeric() || c == '_' { + j += 1; + } else { + break; + } + } + if j == i { + return false; } - if j == i { return false; } let tok = &s[i..j]; - !tok.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) + !tok.chars() + .next() + .map(|c| c.is_ascii_digit()) + .unwrap_or(false) } diff --git a/src/runner/core_executor.rs b/src/runner/core_executor.rs index 3ed2662d..ce4ecdbd 100644 --- a/src/runner/core_executor.rs +++ b/src/runner/core_executor.rs @@ -26,13 +26,18 @@ pub fn run_json_v0(runner: &NyashRunner, json: &str) -> i32 { let looks_like_mir = json.contains("\"functions\"") && json.contains("\"blocks\""); if looks_like_mir { // In-proc prototype (opt-in): HAKO_CORE_DIRECT_INPROC=1 (alias NYASH_CORE_DIRECT_INPROC) - let core_direct_inproc = std::env::var("HAKO_CORE_DIRECT_INPROC").ok().as_deref() == Some("1") + let core_direct_inproc = std::env::var("HAKO_CORE_DIRECT_INPROC").ok().as_deref() + == Some("1") || std::env::var("NYASH_CORE_DIRECT_INPROC").ok().as_deref() == Some("1"); if core_direct_inproc { - if let Some(rc) = try_run_core_direct_inproc(runner, json) { return rc; } + if let Some(rc) = try_run_core_direct_inproc(runner, json) { + return rc; + } eprintln!("[core-exec] direct Core (inproc) failed; trying child wrapper"); } - if let Some(rc) = try_run_core_direct(json) { return rc; } + if let Some(rc) = try_run_core_direct(json) { + return rc; + } eprintln!("[core-exec] direct Core (child) failed; falling back to VM interpreter"); } // else: skip direct Core and continue to bridge/VM path @@ -41,7 +46,9 @@ pub fn run_json_v0(runner: &NyashRunner, json: &str) -> i32 { // Optional: downconvert/canonicalize even for v1 when requested (dev diagnostics) if crate::config::env::nyvm_v1_downconvert() { - if let Ok(j) = crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&payload) { + if let Ok(j) = + crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&payload) + { payload = j; } } @@ -90,7 +97,9 @@ pub fn run_json_v0(runner: &NyashRunner, json: &str) -> i32 { } // For non‑v1 input, attempt canonicalization and v1 bridge (Stage‑B program → MIR). - if let Ok(j) = crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&payload) { + if let Ok(j) = + crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&payload) + { payload = j; } match crate::runner::json_v1_bridge::try_parse_v1_to_module(&payload) { diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 5992d0e0..193fe7b2 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -18,7 +18,9 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { if runner.try_run_selfhost_pipeline(filename) { return; } else { - crate::cli_v!("[ny-compiler] fallback to default path (MVP unavailable for this input)"); + crate::cli_v!( + "[ny-compiler] fallback to default path (MVP unavailable for this input)" + ); } } @@ -31,7 +33,11 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { // Try schema v1 first (preferred by emitter) match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { Ok(Some(module)) => { - crate::cli_v!("[mir-json] schema=v1 executing {} (len={})", path, text.len()); + crate::cli_v!( + "[mir-json] schema=v1 executing {} (len={})", + path, + text.len() + ); let rc = runner.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } @@ -40,7 +46,11 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { if text.contains("\"functions\"") && text.contains("\"blocks\"") { match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) { Ok(module) => { - crate::cli_v!("[mir-json] schema=v0 executing {} (len={})", path, text.len()); + crate::cli_v!( + "[mir-json] schema=v0 executing {} (len={})", + path, + text.len() + ); let rc = runner.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } @@ -77,7 +87,10 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { }; match json_v0_bridge::parse_source_v0_to_module(&code) { Ok(module) => { - crate::cli_v!("🚀 Hakorune MIR Interpreter - (parser=ny) Executing file: {} 🚀", filename); + crate::cli_v!( + "🚀 Hakorune MIR Interpreter - (parser=ny) Executing file: {} 🚀", + filename + ); runner.execute_mir_module(&module); return; } @@ -100,10 +113,14 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { }; let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); - process::exit(1); - } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); + process::exit(1); + } }; // Optional macro expansion dump (no-op expansion for now) let ast2 = if crate::r#macro::enabled() { @@ -120,16 +137,28 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { if runner.config.dump_expanded_ast_json { let code = match fs::read_to_string(filename) { Ok(content) => content, - Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); } + Err(e) => { + eprintln!("❌ Error reading file {}: {}", filename, e); + process::exit(1); + } }; let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { eprintln!("❌ Parse error in {}: {}", filename, e); process::exit(1); } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); + process::exit(1); + } }; let expanded = if crate::r#macro::enabled() { let a = crate::r#macro::maybe_expand_and_dump(&ast, false); crate::runner::modes::macro_child::normalize_core_pass(&a) - } else { ast }; + } else { + ast + }; let j = crate::r#macro::ast_json::ast_to_json(&expanded); println!("{}", j.to_string()); return; @@ -137,7 +166,10 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { // MIR dump/verify if groups.debug.dump_mir || groups.debug.verify_mir { - crate::cli_v!("🚀 Hakorune MIR Compiler - Processing file: {} 🚀", filename); + crate::cli_v!( + "🚀 Hakorune MIR Compiler - Processing file: {} 🚀", + filename + ); runner.execute_mir_mode(filename); return; } @@ -171,25 +203,36 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { // Backend selection match groups.backend.backend.as_str() { "mir" => { - crate::cli_v!("🚀 Hakorune MIR Interpreter - Executing file: {} 🚀", filename); + crate::cli_v!( + "🚀 Hakorune MIR Interpreter - Executing file: {} 🚀", + filename + ); runner.execute_mir_mode(filename); } "vm" => { crate::cli_v!("🚀 Hakorune VM Backend - Executing file: {} 🚀", filename); // Route to primary VM path by default. Fallback is a last resort and must be explicitly enabled. - let force_fallback = std::env::var("NYASH_VM_USE_FALLBACK").ok().as_deref() == Some("1"); + let force_fallback = + std::env::var("NYASH_VM_USE_FALLBACK").ok().as_deref() == Some("1"); let route_trace = std::env::var("NYASH_VM_ROUTE_TRACE").ok().as_deref() == Some("1"); if force_fallback { - if route_trace { eprintln!("[vm-route] choose=fallback reason=env:NYASH_VM_USE_FALLBACK=1"); } + if route_trace { + eprintln!("[vm-route] choose=fallback reason=env:NYASH_VM_USE_FALLBACK=1"); + } runner.execute_vm_fallback_interpreter(filename); } else { - if route_trace { eprintln!("[vm-route] choose=vm"); } + if route_trace { + eprintln!("[vm-route] choose=vm"); + } runner.execute_vm_mode(filename); } } #[cfg(feature = "cranelift-jit")] "jit-direct" => { - crate::cli_v!("⚡ Hakorune JIT-Direct Backend - Executing file: {} ⚡", filename); + crate::cli_v!( + "⚡ Hakorune JIT-Direct Backend - Executing file: {} ⚡", + filename + ); #[cfg(feature = "cranelift-jit")] { // Use independent JIT-direct runner method (no VM execute loop) @@ -269,10 +312,18 @@ impl NyashRunner { } } // Global fallbacks when signature is missing or imprecise - if let Some(ib) = result.as_any().downcast_ref::() { return to_rc(ib.value); } - if let Some(bb) = result.as_any().downcast_ref::() { return if bb.value { 1 } else { 0 }; } - if let Some(fb) = result.as_any().downcast_ref::() { return to_rc(fb.value as i64); } - if let Some(_sb) = result.as_any().downcast_ref::() { return 0; } + if let Some(ib) = result.as_any().downcast_ref::() { + return to_rc(ib.value); + } + if let Some(bb) = result.as_any().downcast_ref::() { + return if bb.value { 1 } else { 0 }; + } + if let Some(fb) = result.as_any().downcast_ref::() { + return to_rc(fb.value as i64); + } + if let Some(_sb) = result.as_any().downcast_ref::() { + return 0; + } 0 } Err(_) => 1, diff --git a/src/runner/hv1_inline.rs b/src/runner/hv1_inline.rs index 5bbc0a85..655c3c07 100644 --- a/src/runner/hv1_inline.rs +++ b/src/runner/hv1_inline.rs @@ -23,19 +23,31 @@ pub fn run_json_v1_inline(json: &str) -> i32 { _ => return 1, } // Fetch first function and build block map - let functions = match v.get("functions").and_then(Value::as_array) { Some(a) => a, None => return 1 }; - let func = match functions.get(0) { Some(f) => f, None => return 1 }; - let blocks = match func.get("blocks").and_then(Value::as_array) { Some(a) => a, None => return 1 }; + let functions = match v.get("functions").and_then(Value::as_array) { + Some(a) => a, + None => return 1, + }; + let func = match functions.get(0) { + Some(f) => f, + None => return 1, + }; + let blocks = match func.get("blocks").and_then(Value::as_array) { + Some(a) => a, + None => return 1, + }; let mut bmap: HashMap> = HashMap::new(); for b in blocks { - if let (Some(id), Some(insts)) = (b.get("id").and_then(Value::as_i64), b.get("instructions").and_then(Value::as_array)) { + if let (Some(id), Some(insts)) = ( + b.get("id").and_then(Value::as_i64), + b.get("instructions").and_then(Value::as_array), + ) { bmap.insert(id, insts); } } // Registers and simple method state let mut regs: HashMap = HashMap::new(); - let mut str_regs: HashMap = HashMap::new(); // Track string values loaded via const + let mut str_regs: HashMap = HashMap::new(); // Track string values loaded via const let sizestate = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE"); let per_recv = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV"); let value_state = crate::config::env::env_bool("HAKO_VM_MIRCALL_VALUESTATE"); @@ -60,323 +72,520 @@ pub fn run_json_v1_inline(json: &str) -> i32 { let mut prev: i64 = -1; let mut steps: i32 = 0; 'outer: loop { - if steps > 10000 { return 1; } + if steps > 10000 { + return 1; + } steps += 1; - let insts = match bmap.get(&curr) { Some(v) => *v, None => return 1 }; + let insts = match bmap.get(&curr) { + Some(v) => *v, + None => return 1, + }; let mut ip = 0usize; while ip < insts.len() { let inst = &insts[ip]; ip += 1; - let op = match inst.get("op").and_then(Value::as_str) { Some(s) => s, None => return 1 }; - match op { - "binop" => { - // Minimal integer binops - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let lhs = inst.get("lhs").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let rhs = inst.get("rhs").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let opn = inst.get("operation").and_then(Value::as_str).unwrap_or(""); - let out = match opn { - "+" => lhs.wrapping_add(rhs), - "-" => lhs.wrapping_sub(rhs), - "*" => lhs.wrapping_mul(rhs), - "/" => { - if rhs == 0 { 0 } else { lhs.wrapping_div(rhs) } - } - "%" => { - if rhs == 0 { 0 } else { lhs.wrapping_rem(rhs) } - } - "&" => lhs & rhs, - "|" => lhs | rhs, - "^" => lhs ^ rhs, - "<<" => lhs.wrapping_shl((rhs as u32).min(63)), - ">>" => ((lhs as i64) >> (rhs as u32).min(63)) as i64, - _ => 0, - }; - regs.insert(dst, out); - } - "unop" => { - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let src = inst.get("src").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let kind = inst.get("kind").and_then(Value::as_str).unwrap_or(""); - let out = match kind { - "neg" => src.wrapping_neg(), - "not" => if src == 0 { 1 } else { 0 }, - "bitnot" => !src, - _ => 0, - }; - regs.insert(dst, out); - } - "copy" => { - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let srcv = inst.get("src").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - regs.insert(dst, srcv); - } - "typeop" => { - // Minimal TypeOp support for PRIMARY reps - // Fields: operation ("check"|"is"|"cast"), src (vid), target_type (str), dst (vid) - let operation = inst - .get("operation") - .and_then(Value::as_str) - .unwrap_or("") - .to_lowercase(); - let src = match inst.get("src").and_then(Value::as_i64) { Some(s) => s, None => return 1 }; - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let target = inst - .get("target_type") - .and_then(Value::as_str) - .unwrap_or("") - .to_lowercase(); - - let sval = regs.get(&src).cloned(); - let is_integer = sval.is_some(); // hv1 inline stores i64 only → integer - if operation == "check" || operation == "is" { - let out: i64 = if target == "i64" || target == "int" || target == "integer" { - if is_integer { 1 } else { 0 } - } else if target == "bool" { - // Inline model uses integer registers; treat 0/1 as bool when present - if let Some(v) = sval { if v == 0 || v == 1 { 1 } else { 0 } } else { 0 } - } else if target == "string" { - 0 // no string registers in inline model - } else { - 0 + let op = match inst.get("op").and_then(Value::as_str) { + Some(s) => s, + None => return 1, + }; + match op { + "binop" => { + // Minimal integer binops + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let lhs = inst + .get("lhs") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let rhs = inst + .get("rhs") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let opn = inst.get("operation").and_then(Value::as_str).unwrap_or(""); + let out = match opn { + "+" => lhs.wrapping_add(rhs), + "-" => lhs.wrapping_sub(rhs), + "*" => lhs.wrapping_mul(rhs), + "/" => { + if rhs == 0 { + 0 + } else { + lhs.wrapping_div(rhs) + } + } + "%" => { + if rhs == 0 { + 0 + } else { + lhs.wrapping_rem(rhs) + } + } + "&" => lhs & rhs, + "|" => lhs | rhs, + "^" => lhs ^ rhs, + "<<" => lhs.wrapping_shl((rhs as u32).min(63)), + ">>" => ((lhs as i64) >> (rhs as u32).min(63)) as i64, + _ => 0, }; regs.insert(dst, out); - } else { - // cast/as: pass-through (MVP) - regs.insert(dst, sval.unwrap_or(0)); } - } - "const" => { - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - // Prefer i64 numeric constants; otherwise stub non-numeric to 0 for inline path - if let Some(n) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_i64) { - regs.insert(dst, n); - } else if let Some(s) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_str) { - // Store string value for Map key tracking - str_regs.insert(dst, s.to_string()); - regs.insert(dst, 0); // Also set numeric register to 0 for compatibility - } else { - regs.insert(dst, 0); + "unop" => { + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let src = inst + .get("src") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let kind = inst.get("kind").and_then(Value::as_str).unwrap_or(""); + let out = match kind { + "neg" => src.wrapping_neg(), + "not" => { + if src == 0 { + 1 + } else { + 0 + } + } + "bitnot" => !src, + _ => 0, + }; + regs.insert(dst, out); } - } - "compare" => { - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let lhs = inst.get("lhs").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let rhs = inst.get("rhs").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let cmp = inst.get("cmp").and_then(Value::as_str).unwrap_or(""); - let res = match cmp { - "Gt" => (lhs > rhs) as i64, - "Ge" => (lhs >= rhs) as i64, - "Lt" => (lhs < rhs) as i64, - "Le" => (lhs <= rhs) as i64, - "Eq" => (lhs == rhs) as i64, - "Ne" => (lhs != rhs) as i64, - _ => 0, - }; - regs.insert(dst, res); - } - "mir_call" => { - // Support both nested shape {"mir_call":{"callee":...}} and flat shape {"callee":...} - let mc = inst.get("mir_call"); - let callee = if let Some(m) = mc { m.get("callee") } else { inst.get("callee") }; - let Some(callee) = callee else { return 1 }; - let ctype = callee.get("type").and_then(Value::as_str).unwrap_or(""); - match ctype { - // Constructor: just create and optionally write dst=0 - "Constructor" => { - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } - continue; - } - // ArrayBox methods we model inline - "Method" => { - let bname = callee.get("box_name").and_then(Value::as_str).unwrap_or(""); - if bname == "ArrayBox" { - let method = callee.get("method").and_then(Value::as_str).unwrap_or(""); - let recv = callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1); - if method == "push" { - if sizestate { - if per_recv { - let e = len_by_recv_arr.entry(recv).or_insert(0); - *e += 1; - } else { - len_global_arr += 1; - } + "copy" => { + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let srcv = inst + .get("src") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + regs.insert(dst, srcv); + } + "typeop" => { + // Minimal TypeOp support for PRIMARY reps + // Fields: operation ("check"|"is"|"cast"), src (vid), target_type (str), dst (vid) + let operation = inst + .get("operation") + .and_then(Value::as_str) + .unwrap_or("") + .to_lowercase(); + let src = match inst.get("src").and_then(Value::as_i64) { + Some(s) => s, + None => return 1, + }; + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let target = inst + .get("target_type") + .and_then(Value::as_str) + .unwrap_or("") + .to_lowercase(); + + let sval = regs.get(&src).cloned(); + let is_integer = sval.is_some(); // hv1 inline stores i64 only → integer + if operation == "check" || operation == "is" { + let out: i64 = if target == "i64" || target == "int" || target == "integer" + { + if is_integer { + 1 + } else { + 0 + } + } else if target == "bool" { + // Inline model uses integer registers; treat 0/1 as bool when present + if let Some(v) = sval { + if v == 0 || v == 1 { + 1 + } else { + 0 } - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } - continue; + } else { + 0 } - if is_size_alias(method) { - let value = if sizestate { - if per_recv { *len_by_recv_arr.get(&recv).unwrap_or(&0) } else { len_global_arr } - } else { 0 }; - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, value); } - continue; + } else if target == "string" { + 0 // no string registers in inline model + } else { + 0 + }; + regs.insert(dst, out); + } else { + // cast/as: pass-through (MVP) + regs.insert(dst, sval.unwrap_or(0)); + } + } + "const" => { + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + // Prefer i64 numeric constants; otherwise stub non-numeric to 0 for inline path + if let Some(n) = inst + .get("value") + .and_then(|vv| vv.get("value")) + .and_then(Value::as_i64) + { + regs.insert(dst, n); + } else if let Some(s) = inst + .get("value") + .and_then(|vv| vv.get("value")) + .and_then(Value::as_str) + { + // Store string value for Map key tracking + str_regs.insert(dst, s.to_string()); + regs.insert(dst, 0); // Also set numeric register to 0 for compatibility + } else { + regs.insert(dst, 0); + } + } + "compare" => { + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let lhs = inst + .get("lhs") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let rhs = inst + .get("rhs") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let cmp = inst.get("cmp").and_then(Value::as_str).unwrap_or(""); + let res = match cmp { + "Gt" => (lhs > rhs) as i64, + "Ge" => (lhs >= rhs) as i64, + "Lt" => (lhs < rhs) as i64, + "Le" => (lhs <= rhs) as i64, + "Eq" => (lhs == rhs) as i64, + "Ne" => (lhs != rhs) as i64, + _ => 0, + }; + regs.insert(dst, res); + } + "mir_call" => { + // Support both nested shape {"mir_call":{"callee":...}} and flat shape {"callee":...} + let mc = inst.get("mir_call"); + let callee = if let Some(m) = mc { + m.get("callee") + } else { + inst.get("callee") + }; + let Some(callee) = callee else { return 1 }; + let ctype = callee.get("type").and_then(Value::as_str).unwrap_or(""); + match ctype { + // Constructor: just create and optionally write dst=0 + "Constructor" => { + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); } - // unsupported method on ArrayBox → stub 0 - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } continue; } - if bname == "MapBox" { - let method = callee.get("method").and_then(Value::as_str).unwrap_or(""); - let recv = callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1); - if method == "set" { - // Extract key from first arg (register containing string value) - let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") }; - let key_str = if let Some(args_arr) = args.and_then(Value::as_array) { - if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) { - // Look up string value from str_regs - str_regs.get(&key_reg).cloned().unwrap_or_default() - } else { String::new() } - } else { String::new() }; - - if sizestate && !key_str.is_empty() { - let is_new_key = if per_recv { - let keys = map_keys_by_recv.entry(recv).or_insert_with(std::collections::HashSet::new); - keys.insert(key_str.clone()) - } else { - map_keys_global.insert(key_str.clone()) - }; - - // Only increment size if this is a new key - if is_new_key { + // ArrayBox methods we model inline + "Method" => { + let bname = + callee.get("box_name").and_then(Value::as_str).unwrap_or(""); + if bname == "ArrayBox" { + let method = + callee.get("method").and_then(Value::as_str).unwrap_or(""); + let recv = + callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1); + if method == "push" { + if sizestate { if per_recv { - let e = len_by_recv_map.entry(recv).or_insert(0); + let e = len_by_recv_arr.entry(recv).or_insert(0); *e += 1; } else { - len_global_map += 1; + len_global_arr += 1; } } + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); + } + continue; } - - // value_state: store the value - if value_state && !key_str.is_empty() { - if let Some(args_arr) = args.and_then(Value::as_array) { - if let Some(val_reg) = args_arr.get(1).and_then(Value::as_i64) { - let val = *regs.get(&val_reg).unwrap_or(&0); - if per_recv { - let vals = map_vals_by_recv.entry(recv).or_insert_with(HashMap::new); - vals.insert(key_str.clone(), val); + if is_size_alias(method) { + let value = if sizestate { + if per_recv { + *len_by_recv_arr.get(&recv).unwrap_or(&0) + } else { + len_global_arr + } + } else { + 0 + }; + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, value); + } + continue; + } + // unsupported method on ArrayBox → stub 0 + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); + } + continue; + } + if bname == "MapBox" { + let method = + callee.get("method").and_then(Value::as_str).unwrap_or(""); + let recv = + callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1); + if method == "set" { + // Extract key from first arg (register containing string value) + let args = if let Some(mc) = mc { + mc.get("args") + } else { + inst.get("args") + }; + let key_str = + if let Some(args_arr) = args.and_then(Value::as_array) { + if let Some(key_reg) = + args_arr.get(0).and_then(Value::as_i64) + { + // Look up string value from str_regs + str_regs.get(&key_reg).cloned().unwrap_or_default() } else { - map_vals_global.insert(key_str.clone(), val); + String::new() + } + } else { + String::new() + }; + + if sizestate && !key_str.is_empty() { + let is_new_key = if per_recv { + let keys = map_keys_by_recv + .entry(recv) + .or_insert_with(std::collections::HashSet::new); + keys.insert(key_str.clone()) + } else { + map_keys_global.insert(key_str.clone()) + }; + + // Only increment size if this is a new key + if is_new_key { + if per_recv { + let e = len_by_recv_map.entry(recv).or_insert(0); + *e += 1; + } else { + len_global_map += 1; } } } - } - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } - continue; - } - if method == "get" && value_state { - // Extract key and retrieve value - let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") }; - let key_str = if let Some(args_arr) = args.and_then(Value::as_array) { - if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) { - str_regs.get(&key_reg).cloned().unwrap_or_default() - } else { String::new() } - } else { String::new() }; - - if !key_str.is_empty() { - let val = if per_recv { - map_vals_by_recv.get(&recv).and_then(|m| m.get(&key_str)).cloned().unwrap_or(0) - } else { - *map_vals_global.get(&key_str).unwrap_or(&0) - }; - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, val); } - } - continue; - } - if method == "has" && value_state { - // Check if key exists - let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") }; - let key_str = if let Some(args_arr) = args.and_then(Value::as_array) { - if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) { - str_regs.get(&key_reg).cloned().unwrap_or_default() - } else { String::new() } - } else { String::new() }; - - let has = if !key_str.is_empty() { - if per_recv { - map_vals_by_recv.get(&recv).map(|m| m.contains_key(&key_str)).unwrap_or(false) - } else { - map_vals_global.contains_key(&key_str) + // value_state: store the value + if value_state && !key_str.is_empty() { + if let Some(args_arr) = args.and_then(Value::as_array) { + if let Some(val_reg) = + args_arr.get(1).and_then(Value::as_i64) + { + let val = *regs.get(&val_reg).unwrap_or(&0); + if per_recv { + let vals = map_vals_by_recv + .entry(recv) + .or_insert_with(HashMap::new); + vals.insert(key_str.clone(), val); + } else { + map_vals_global.insert(key_str.clone(), val); + } + } + } } - } else { false }; + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); + } + continue; + } + if method == "get" && value_state { + // Extract key and retrieve value + let args = if let Some(mc) = mc { + mc.get("args") + } else { + inst.get("args") + }; + let key_str = + if let Some(args_arr) = args.and_then(Value::as_array) { + if let Some(key_reg) = + args_arr.get(0).and_then(Value::as_i64) + { + str_regs.get(&key_reg).cloned().unwrap_or_default() + } else { + String::new() + } + } else { + String::new() + }; + + if !key_str.is_empty() { + let val = if per_recv { + map_vals_by_recv + .get(&recv) + .and_then(|m| m.get(&key_str)) + .cloned() + .unwrap_or(0) + } else { + *map_vals_global.get(&key_str).unwrap_or(&0) + }; + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, val); + } + } + continue; + } + if method == "has" && value_state { + // Check if key exists + let args = if let Some(mc) = mc { + mc.get("args") + } else { + inst.get("args") + }; + let key_str = + if let Some(args_arr) = args.and_then(Value::as_array) { + if let Some(key_reg) = + args_arr.get(0).and_then(Value::as_i64) + { + str_regs.get(&key_reg).cloned().unwrap_or_default() + } else { + String::new() + } + } else { + String::new() + }; + + let has = if !key_str.is_empty() { + if per_recv { + map_vals_by_recv + .get(&recv) + .map(|m| m.contains_key(&key_str)) + .unwrap_or(false) + } else { + map_vals_global.contains_key(&key_str) + } + } else { + false + }; + + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, if has { 1 } else { 0 }); + } + continue; + } + if is_size_alias(method) { + let value = if sizestate { + if per_recv { + *len_by_recv_map.get(&recv).unwrap_or(&0) + } else { + len_global_map + } + } else { + 0 + }; + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, value); + } + continue; + } + // other methods stub 0 if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { - regs.insert(dst, if has { 1 } else { 0 }); + regs.insert(dst, 0); } continue; } - if is_size_alias(method) { - let value = if sizestate { - if per_recv { *len_by_recv_map.get(&recv).unwrap_or(&0) } else { len_global_map } - } else { 0 }; - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, value); } - continue; + // Other box methods are not modeled; stub if dst present + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); } - // other methods stub 0 - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } continue; } - // Other box methods are not modeled; stub if dst present - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } + // Extern calls: allow stub (return 0) when provider is enabled via env, else still stub to 0 + "Extern" => { + let _provider_on = + crate::config::env::env_bool("HAKO_V1_EXTERN_PROVIDER"); + // For now, always treat extern as stub → write 0 when dst present + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); + } + continue; + } + _ => {} + } + // Unsupported callee shape/type → treat as stub if possible, else error + if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { + regs.insert(dst, 0); continue; } - // Extern calls: allow stub (return 0) when provider is enabled via env, else still stub to 0 - "Extern" => { - let _provider_on = crate::config::env::env_bool("HAKO_V1_EXTERN_PROVIDER"); - // For now, always treat extern as stub → write 0 when dst present - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); } - continue; - } - _ => {} + return 1; } - // Unsupported callee shape/type → treat as stub if possible, else error - if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); continue; } - return 1; - } - "phi" => { - let dst = match inst.get("dst").and_then(Value::as_i64) { Some(d) => d, None => return 1 }; - let mut val: i64 = 0; - let mut matched = false; - if let Some(incomings) = inst.get("incoming").and_then(Value::as_array) { - for pair in incomings { - if let Some(arr) = pair.as_array() { - if arr.len() >= 2 { - // v1 schema (inline minimal): [value_reg, pred_block_id] - let r = arr[0].as_i64().unwrap_or(-1); - let b = arr[1].as_i64().unwrap_or(-1); - if b == prev { val = regs.get(&r).cloned().unwrap_or(0); matched = true; break; } + "phi" => { + let dst = match inst.get("dst").and_then(Value::as_i64) { + Some(d) => d, + None => return 1, + }; + let mut val: i64 = 0; + let mut matched = false; + if let Some(incomings) = inst.get("incoming").and_then(Value::as_array) { + for pair in incomings { + if let Some(arr) = pair.as_array() { + if arr.len() >= 2 { + // v1 schema (inline minimal): [value_reg, pred_block_id] + let r = arr[0].as_i64().unwrap_or(-1); + let b = arr[1].as_i64().unwrap_or(-1); + if b == prev { + val = regs.get(&r).cloned().unwrap_or(0); + matched = true; + break; + } + } } } } + if !matched { + return 1; + } + regs.insert(dst, val); } - if !matched { return 1; } - regs.insert(dst, val); + "branch" => { + let cond = inst + .get("cond") + .and_then(Value::as_i64) + .and_then(|r| regs.get(&r).cloned()) + .unwrap_or(0); + let then_b = inst.get("then").and_then(Value::as_i64).unwrap_or(curr); + let else_b = inst.get("else").and_then(Value::as_i64).unwrap_or(curr); + prev = curr; + curr = if cond != 0 { then_b } else { else_b }; + continue 'outer; // switch block + } + "jump" => { + let target = inst.get("target").and_then(Value::as_i64).unwrap_or(curr); + prev = curr; + curr = target; + continue 'outer; // switch block + } + "ret" => { + let vid = match inst.get("value").and_then(Value::as_i64) { + Some(v) => v, + None => return 0, + }; + let v = *regs.get(&vid).unwrap_or(&0); + return (v as i32) & 0xFF; + } + // ignore others for now (compare/branch not used in hv1 per‑recv canaries) + _ => {} } - "branch" => { - let cond = inst.get("cond").and_then(Value::as_i64).and_then(|r| regs.get(&r).cloned()).unwrap_or(0); - let then_b = inst.get("then").and_then(Value::as_i64).unwrap_or(curr); - let else_b = inst.get("else").and_then(Value::as_i64).unwrap_or(curr); - prev = curr; - curr = if cond != 0 { then_b } else { else_b }; - continue 'outer; // switch block - } - "jump" => { - let target = inst.get("target").and_then(Value::as_i64).unwrap_or(curr); - prev = curr; - curr = target; - continue 'outer; // switch block - } - "ret" => { - let vid = match inst.get("value").and_then(Value::as_i64) { Some(v) => v, None => return 0 }; - let v = *regs.get(&vid).unwrap_or(&0); - return (v as i32) & 0xFF; - } - // ignore others for now (compare/branch not used in hv1 per‑recv canaries) - _ => {} - } - // if we completed a block without control flow, stop + // if we completed a block without control flow, stop } return 0; } diff --git a/src/runner/json_v0_bridge/lowering.rs b/src/runner/json_v0_bridge/lowering.rs index 034bc723..fb4cddf1 100644 --- a/src/runner/json_v0_bridge/lowering.rs +++ b/src/runner/json_v0_bridge/lowering.rs @@ -1,23 +1,23 @@ -use super::ast::{ProgramV0, StmtV0, ExprV0}; +use super::ast::{ExprV0, ProgramV0, StmtV0}; use crate::mir::Callee; use crate::mir::{ - BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, - MirPrinter, MirType, ValueId, + BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, + MirModule, MirPrinter, MirType, ValueId, }; -use std::collections::HashMap; use std::cell::RefCell; +use std::collections::HashMap; // Split out merge/new_block helpers for readability (no behavior change) mod merge; use merge::{merge_var_maps, new_block}; // Feature splits (gradual extraction) +pub(super) mod expr; pub(super) mod if_else; pub(super) mod loop_; -pub(super) mod try_catch; -pub(super) mod expr; -pub(super) mod ternary; // placeholder (not wired) pub(super) mod match_expr; // placeholder (not wired) -pub(super) mod throw_ctx; // thread-local ctx for Result-mode throw routing +pub(super) mod ternary; // placeholder (not wired) +pub(super) mod throw_ctx; +pub(super) mod try_catch; // thread-local ctx for Result-mode throw routing #[derive(Clone, Copy)] pub(super) struct LoopContext { @@ -77,8 +77,15 @@ pub(super) fn detect_and_push_increment_hint(body: &[StmtV0]) { if vname == name { if let ExprV0::Int { value } = *rhs { if let Some(v) = value.as_i64() { - let s = match op.as_str() { "+" => v, "-" => -v, _ => 0 }; - if s != 0 { hint = Some((name.clone(), s)); break; } + let s = match op.as_str() { + "+" => v, + "-" => -v, + _ => 0, + }; + if s != 0 { + hint = Some((name.clone(), s)); + break; + } } } } @@ -149,16 +156,15 @@ impl FunctionDefBuilder { // Phase 25.1m で確立した規則: // - static box 内のメソッドで、box_name が Main 以外 → インスタンスメソッド // - Main.main は特別扱い(エントリポイント) - !self.def.box_name.is_empty() - && self.def.box_name != "Main" + !self.def.box_name.is_empty() && self.def.box_name != "Main" } /// パラメータ ValueId の生成(インスタンスメソッドなら %0 を me 用に予約) fn build_param_ids(&self) -> Vec { let offset = if self.is_instance_method() { - 0 // %0 = me, params start from %1 + 0 // %0 = me, params start from %1 } else { - 1 // params start from %1 (no implicit receiver) + 1 // params start from %1 (no implicit receiver) }; (0..self.def.params.len()) @@ -251,7 +257,6 @@ fn lower_continue_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, target_bb: Bas // ); } - pub(super) fn lower_stmt_with_vars( f: &mut MirFunction, cur_bb: BasicBlockId, @@ -322,16 +327,14 @@ pub(super) fn lower_stmt_with_vars( catches, finally, } => { - try_catch::lower_try_stmt( - f, cur_bb, try_body, catches, finally, vars, loop_stack, env, - ) + try_catch::lower_try_stmt(f, cur_bb, try_body, catches, finally, vars, loop_stack, env) + } + StmtV0::If { cond, then, r#else } => { + if_else::lower_if_stmt(f, cur_bb, cond, then, r#else, vars, loop_stack, env) + } + StmtV0::Loop { cond, body } => { + loop_::lower_loop_stmt(f, cur_bb, cond, body, vars, loop_stack, env) } - StmtV0::If { cond, then, r#else } => if_else::lower_if_stmt( - f, cur_bb, cond, then, r#else, vars, loop_stack, env, - ), - StmtV0::Loop { cond, body } => loop_::lower_loop_stmt( - f, cur_bb, cond, body, vars, loop_stack, env, - ), } } @@ -355,7 +358,10 @@ pub(super) fn lower_stmt_list_with_vars( Ok(cur) } -pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap) -> Result { +pub(super) fn lower_program( + prog: ProgramV0, + imports: std::collections::HashMap, +) -> Result { if prog.body.is_empty() { return Err("empty body".into()); } @@ -468,7 +474,11 @@ pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap< // Phase 21.6: Call resolution post-processing // Toggle: HAKO_MIR_BUILDER_CALL_RESOLVE=1 // Resolve Call instructions to use qualified function names (e.g., "add" -> "Main.add") - if std::env::var("HAKO_MIR_BUILDER_CALL_RESOLVE").ok().as_deref() == Some("1") { + if std::env::var("HAKO_MIR_BUILDER_CALL_RESOLVE") + .ok() + .as_deref() + == Some("1") + { if !func_map.is_empty() { for (_func_idx, func) in module.functions.iter_mut() { for (_block_id, block) in func.blocks.iter_mut() { @@ -476,7 +486,12 @@ pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap< // Find Call instructions and their associated Const values for inst in &block.instructions { - if let MirInstruction::Call { func: func_reg, args, .. } = inst { + if let MirInstruction::Call { + func: func_reg, + args, + .. + } = inst + { // Look for the Const instruction that defines func_reg for const_inst in &block.instructions { if let MirInstruction::Const { dst, value } = const_inst { @@ -486,12 +501,30 @@ pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap< if let Some(resolved) = func_map.get(name) { let mut new_name = resolved.clone(); // Avoid double suffix if already contains '/N' - if !resolved.rsplit('/').next().unwrap_or("").chars().all(|c| c.is_ascii_digit()) || !resolved.contains('/') { - new_name = format!("{}{}", resolved.clone(), format!("/{}", args.len())); + if !resolved + .rsplit('/') + .next() + .unwrap_or("") + .chars() + .all(|c| c.is_ascii_digit()) + || !resolved.contains('/') + { + new_name = format!( + "{}{}", + resolved.clone(), + format!("/{}", args.len()) + ); } const_replacements.push((*dst, new_name)); - if std::env::var("HAKO_MIR_BUILDER_DEBUG").ok().as_deref() == Some("1") { - eprintln!("[mirbuilder/call:resolve] {} => {}", name, resolved); + if std::env::var("HAKO_MIR_BUILDER_DEBUG") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[mirbuilder/call:resolve] {} => {}", + name, resolved + ); } } } @@ -512,15 +545,23 @@ pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap< } } // Build a map reg -> name after replacements - let mut reg_name: std::collections::HashMap = std::collections::HashMap::new(); + let mut reg_name: std::collections::HashMap = + std::collections::HashMap::new(); for inst in &block.instructions { if let MirInstruction::Const { dst, value } = inst { - if let ConstValue::String(s) = value { reg_name.insert(*dst, s.clone()); } + if let ConstValue::String(s) = value { + reg_name.insert(*dst, s.clone()); + } } } // Upgrade legacy calls to Global callee when name is known for inst in &mut block.instructions { - if let MirInstruction::Call { func: func_reg, callee, .. } = inst { + if let MirInstruction::Call { + func: func_reg, + callee, + .. + } = inst + { if let Some(name) = reg_name.get(func_reg).cloned() { *callee = Some(Callee::Global(name)); } diff --git a/src/runner/json_v0_bridge/lowering/expr.rs b/src/runner/json_v0_bridge/lowering/expr.rs index dc22ad64..2cac6caa 100644 --- a/src/runner/json_v0_bridge/lowering/expr.rs +++ b/src/runner/json_v0_bridge/lowering/expr.rs @@ -1,10 +1,8 @@ -use super::merge::new_block; -use super::BridgeEnv; -use super::ternary; use super::match_expr; -use crate::mir::{ - BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId, -}; +use super::merge::new_block; +use super::ternary; +use super::BridgeEnv; +use crate::mir::{BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId}; use std::collections::HashMap; use super::super::ast::ExprV0; @@ -140,7 +138,10 @@ fn lower_throw( } else { let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_bb) { - bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) }); + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::Integer(0), + }); } (dst, cur_bb) } @@ -264,8 +265,15 @@ pub(super) fn lower_expr_with_scope( // ); let cdst = f.next_value_id(); if let Some(bb) = f.get_block_mut(fall_bb) { - let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) }; - bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval }); + let cval = if is_and { + ConstValue::Bool(false) + } else { + ConstValue::Bool(true) + }; + bb.add_instruction(MirInstruction::Const { + dst: cdst, + value: cval, + }); } crate::mir::ssot::cf_common::set_jump(f, fall_bb, merge_bb); let (rval, rhs_end) = lower_expr_with_scope(env, f, rhs_bb, rhs, vars)?; @@ -277,7 +285,11 @@ pub(super) fn lower_expr_with_scope( let out = f.next_value_id(); // フェーズM.2: PHI統一処理(no_phi分岐削除) let mut inputs: Vec<(BasicBlockId, ValueId)> = vec![(fall_bb, cdst)]; - if rhs_end != fall_bb { inputs.push((rhs_end, rval)); } else { inputs.push((fall_bb, rval)); } + if rhs_end != fall_bb { + inputs.push((rhs_end, rval)); + } else { + inputs.push((fall_bb, rval)); + } crate::mir::ssot::cf_common::insert_phi_at_head(f, merge_bb, out, inputs); Ok((out, merge_bb)) } @@ -346,7 +358,10 @@ pub(super) fn lower_expr_with_scope( let (arg_ids, cur) = lower_args_with_scope(env, f, cur_bb, args, vars)?; let fun_val = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) }); + bb.add_instruction(MirInstruction::Const { + dst: fun_val, + value: ConstValue::String(name.clone()), + }); } let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { @@ -361,8 +376,10 @@ pub(super) fn lower_expr_with_scope( Ok((dst, cur)) } ExprV0::Method { recv, method, args } => { - let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); - if recv_is_console_new && (method == "println" || method == "print" || method == "log") { + let recv_is_console_new = + matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox"); + if recv_is_console_new && (method == "println" || method == "print" || method == "log") + { let (arg_ids, cur2) = lower_args_with_scope(env, f, cur_bb, args, vars)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur2) { @@ -378,11 +395,16 @@ pub(super) fn lower_expr_with_scope( } // Phase 25.1b: Handle env.box_introspect.kind(value) pattern // Pattern: Method { recv: Method { recv: Var("env"), method: "box_introspect" }, method: "kind", args } - if let ExprV0::Method { recv: inner_recv, method: inner_method, args: inner_args } = &**recv { + if let ExprV0::Method { + recv: inner_recv, + method: inner_method, + args: inner_args, + } = &**recv + { if matches!(&**inner_recv, ExprV0::Var { name } if name == "env") && inner_method == "box_introspect" - && inner_args.is_empty() { - + && inner_args.is_empty() + { // Lower args for the final method call let (arg_ids, cur2) = lower_args_with_scope(env, f, cur_bb, args, vars)?; let dst = f.next_value_id(); @@ -418,7 +440,11 @@ pub(super) fn lower_expr_with_scope( let (arg_ids, cur) = lower_args_with_scope(env, f, cur_bb, args, vars)?; let dst = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur) { - bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids }); + bb.add_instruction(MirInstruction::NewBox { + dst, + box_type: class.clone(), + args: arg_ids, + }); } Ok((dst, cur)) } @@ -430,10 +456,14 @@ pub(super) fn lower_expr_with_scope( let (exc, cur) = lower_expr_with_scope(env, f, cur_bb, expr, vars)?; Ok(lower_throw(env, f, cur, exc)) } - ExprV0::Ternary { cond, then, r#else } => - ternary::lower_ternary_expr_with_scope(env, f, cur_bb, cond, then, r#else, vars), - ExprV0::Match { scrutinee, arms, r#else } => - match_expr::lower_match_expr_with_scope(env, f, cur_bb, scrutinee, arms, r#else, vars), + ExprV0::Ternary { cond, then, r#else } => { + ternary::lower_ternary_expr_with_scope(env, f, cur_bb, cond, then, r#else, vars) + } + ExprV0::Match { + scrutinee, + arms, + r#else, + } => match_expr::lower_match_expr_with_scope(env, f, cur_bb, scrutinee, arms, r#else, vars), } } diff --git a/src/runner/json_v0_bridge/lowering/if_else.rs b/src/runner/json_v0_bridge/lowering/if_else.rs index 1a121cd3..49c4e010 100644 --- a/src/runner/json_v0_bridge/lowering/if_else.rs +++ b/src/runner/json_v0_bridge/lowering/if_else.rs @@ -1,8 +1,8 @@ +use super::super::ast::ExprV0; +use super::super::ast::StmtV0; use super::{lower_stmt_list_with_vars, merge_var_maps, new_block, BridgeEnv, LoopContext}; use crate::mir::{BasicBlockId, MirFunction, ValueId}; use std::collections::HashMap; -use super::super::ast::StmtV0; -use super::super::ast::ExprV0; pub(super) fn lower_if_stmt( f: &mut MirFunction, @@ -24,16 +24,22 @@ pub(super) fn lower_if_stmt( let tend = lower_stmt_list_with_vars(f, then_bb, then_body, &mut then_vars, loop_stack, env)?; let mut then_terminated = false; if let Some(bb) = f.get_block_mut(tend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); } - else { then_terminated = true; } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); + } else { + then_terminated = true; + } } let (else_end_pred, else_vars, else_terminated) = if let Some(elses) = else_body { let mut ev = base_vars.clone(); let eend = lower_stmt_list_with_vars(f, else_bb, elses, &mut ev, loop_stack, env)?; let mut term = false; if let Some(bb) = f.get_block_mut(eend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); } - else { term = true; } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); + } else { + term = true; + } } (eend, ev, term) } else { diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index 8a25f8b4..d573a041 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -20,14 +20,14 @@ * だけを行う。 */ -use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; -use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; -use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; -use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; -use crate::mir::phi_core::phi_input_collector::PhiInputCollector; -use std::collections::HashMap; -use super::super::ast::StmtV0; use super::super::ast::ExprV0; +use super::super::ast::StmtV0; +use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; +use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; +use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; +use crate::mir::phi_core::phi_input_collector::PhiInputCollector; +use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; +use std::collections::HashMap; /// LoopForm v2 用の JSON bridge 実装。 /// @@ -97,7 +97,9 @@ impl LoopFormOps for LoopFormJsonOps<'_> { fn is_parameter(&self, value_id: ValueId) -> bool { // Phase 26-A-4: ValueId から変数名を逆引き // vars マップを逆引きして変数名を取得 - let name = self.vars.iter() + let name = self + .vars + .iter() .find(|(_, &v)| v == value_id) .map(|(n, _)| n.as_str()); @@ -153,7 +155,11 @@ impl LoopFormOps for LoopFormJsonOps<'_> { ) -> Result<(), String> { if let Some(bb) = self.f.get_block_mut(block) { for inst in &mut bb.instructions { - if let MirInstruction::Phi { dst, inputs: phi_inputs } = inst { + if let MirInstruction::Phi { + dst, + inputs: phi_inputs, + } = inst + { if *dst == phi_id { *phi_inputs = inputs; return Ok(()); @@ -231,7 +237,10 @@ pub(super) fn lower_loop_stmt( // DEBUG: Log preheader snapshot if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[loop_/lower] === PREHEADER SNAPSHOT (bb={:?}) ===", preheader_bb); + eprintln!( + "[loop_/lower] === PREHEADER SNAPSHOT (bb={:?}) ===", + preheader_bb + ); eprintln!("[loop_/lower] Function: {}", f.signature.name); eprintln!("[loop_/lower] base_vars.len() = {}", base_vars.len()); let mut sorted: Vec<_> = base_vars.iter().collect(); @@ -253,8 +262,7 @@ pub(super) fn lower_loop_stmt( loopform.emit_header_phis(&mut ops)?; // 3) ループ条件を header ブロックで評価し、body/exit へ分岐 - let (cval, cend) = - super::expr::lower_expr_with_vars(env, ops.f, header_bb, cond, ops.vars)?; + let (cval, cend) = super::expr::lower_expr_with_vars(env, ops.f, header_bb, cond, ops.vars)?; crate::mir::ssot::cf_common::set_branch(ops.f, cend, cval, body_bb, exit_bb); // 4) ループ本体を lowering(break/continue スナップショットは lowering.rs 側が管理) @@ -266,8 +274,7 @@ pub(super) fn lower_loop_stmt( continue_merge_bb: Some(continue_merge_bb), }); super::detect_and_push_increment_hint(body); - let bend_res = - lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env); + let bend_res = lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env); loop_stack.pop(); let _ = super::pop_increment_hint(); let bend = bend_res?; @@ -293,10 +300,13 @@ pub(super) fn lower_loop_stmt( // DEBUG: Log writes collection if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - let func_name = ops.f.signature.name.clone(); // Clone before borrowing + let func_name = ops.f.signature.name.clone(); // Clone before borrowing eprintln!("[loop_/lower] === WRITES COLLECTION (Step 5-1) ==="); eprintln!("[loop_/lower] Function: {}", func_name); - eprintln!("[loop_/lower] {} variables modified in loop body", writes.len()); + eprintln!( + "[loop_/lower] {} variables modified in loop body", + writes.len() + ); let mut sorted_writes: Vec<_> = writes.iter().collect(); sorted_writes.sort(); for name in &sorted_writes { @@ -330,10 +340,7 @@ pub(super) fn lower_loop_stmt( let mut all_inputs: HashMap> = HashMap::new(); for (bb, snap) in &continue_snaps { for (name, &val) in snap { - all_inputs - .entry(name.clone()) - .or_default() - .push((*bb, val)); + all_inputs.entry(name.clone()).or_default().push((*bb, val)); } } diff --git a/src/runner/json_v0_bridge/lowering/match_expr.rs b/src/runner/json_v0_bridge/lowering/match_expr.rs index 7662b34b..87a2ed4e 100644 --- a/src/runner/json_v0_bridge/lowering/match_expr.rs +++ b/src/runner/json_v0_bridge/lowering/match_expr.rs @@ -1,10 +1,10 @@ //! Match/expr-block lowering for JSON v0 bridge. +use super::super::ast::{ExprV0, MatchArmV0}; +use super::expr::{lower_expr_with_scope, VarScope}; use super::merge::new_block; use super::BridgeEnv; use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId}; -use super::super::ast::{ExprV0, MatchArmV0}; -use super::expr::{lower_expr_with_scope, VarScope}; pub(super) fn lower_match_expr_with_scope( env: &BridgeEnv, @@ -21,7 +21,9 @@ pub(super) fn lower_match_expr_with_scope( // Set up blocks let dispatch_bb = new_block(f); if let Some(bb) = f.get_block_mut(start_bb) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, start_bb, dispatch_bb); } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, start_bb, dispatch_bb); + } } let else_bb = new_block(f); let merge_bb = new_block(f); @@ -31,7 +33,11 @@ pub(super) fn lower_match_expr_with_scope( let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); for (i, arm) in arms.iter().enumerate() { let then_bb = new_block(f); - let next_dispatch = if i + 1 < arms.len() { Some(new_block(f)) } else { None }; + let next_dispatch = if i + 1 < arms.len() { + Some(new_block(f)) + } else { + None + }; let fall_bb = next_dispatch.unwrap_or(else_bb); // Pre-allocate ids to avoid double borrow @@ -39,15 +45,27 @@ pub(super) fn lower_match_expr_with_scope( let cond = f.next_value_id(); if let Some(bb) = f.get_block_mut(cur_dispatch) { // compare scr_val == label - bb.add_instruction(MirInstruction::Const { dst: ldst, value: ConstValue::String(arm.label.clone()) }); + bb.add_instruction(MirInstruction::Const { + dst: ldst, + value: ConstValue::String(arm.label.clone()), + }); } - crate::mir::ssot::cf_common::emit_compare_func(f, cur_dispatch, cond, CompareOp::Eq, scr_val, ldst); + crate::mir::ssot::cf_common::emit_compare_func( + f, + cur_dispatch, + cond, + CompareOp::Eq, + scr_val, + ldst, + ); crate::mir::ssot::cf_common::set_branch(f, cur_dispatch, cond, then_bb, fall_bb); // Then arm body let (tval, tend) = lower_expr_with_scope(env, f, then_bb, &arm.expr, vars)?; if let Some(bb) = f.get_block_mut(tend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); + } } phi_inputs.push((tend, tval)); @@ -57,7 +75,9 @@ pub(super) fn lower_match_expr_with_scope( // Else body let (eval, eend) = lower_expr_with_scope(env, f, else_bb, else_expr, vars)?; if let Some(bb) = f.get_block_mut(eend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); + } } phi_inputs.push((eend, eval)); diff --git a/src/runner/json_v0_bridge/lowering/merge.rs b/src/runner/json_v0_bridge/lowering/merge.rs index 76709d53..8133d457 100644 --- a/src/runner/json_v0_bridge/lowering/merge.rs +++ b/src/runner/json_v0_bridge/lowering/merge.rs @@ -1,6 +1,4 @@ -use crate::mir::{ - BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId, -}; +use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId}; fn next_block_id(f: &MirFunction) -> BasicBlockId { let mut mx = 0u32; diff --git a/src/runner/json_v0_bridge/lowering/ternary.rs b/src/runner/json_v0_bridge/lowering/ternary.rs index 7cabe222..f45fd2cc 100644 --- a/src/runner/json_v0_bridge/lowering/ternary.rs +++ b/src/runner/json_v0_bridge/lowering/ternary.rs @@ -3,10 +3,10 @@ //! NOTE: This module is introduced as part of the helper split. //! It is not wired yet and should not alter behavior. +use super::super::ast::ExprV0; use super::merge::new_block; use super::BridgeEnv; use crate::mir::{BasicBlockId, MirFunction, ValueId}; -use super::super::ast::ExprV0; use super::expr::{lower_expr_with_scope, VarScope}; @@ -27,11 +27,15 @@ pub(super) fn lower_ternary_expr_with_scope( crate::mir::ssot::cf_common::set_branch(f, cur, cval, then_bb, else_bb); let (tval, tend) = lower_expr_with_scope(env, f, then_bb, then_e, vars)?; if let Some(bb) = f.get_block_mut(tend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, tend, merge_bb); + } } let (eval, eend) = lower_expr_with_scope(env, f, else_bb, else_e, vars)?; if let Some(bb) = f.get_block_mut(eend) { - if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); } + if !bb.is_terminated() { + crate::mir::ssot::cf_common::set_jump(f, eend, merge_bb); + } } let out = f.next_value_id(); // フェーズM.2: PHI統一処理(no_phi分岐削除) diff --git a/src/runner/json_v0_bridge/lowering/throw_ctx.rs b/src/runner/json_v0_bridge/lowering/throw_ctx.rs index d00f4653..1f2e371e 100644 --- a/src/runner/json_v0_bridge/lowering/throw_ctx.rs +++ b/src/runner/json_v0_bridge/lowering/throw_ctx.rs @@ -13,7 +13,10 @@ pub(super) struct ThrowCtx { impl ThrowCtx { fn new(catch_bb: BasicBlockId) -> Self { - Self { catch_bb, incoming: Vec::new() } + Self { + catch_bb, + incoming: Vec::new(), + } } } @@ -33,7 +36,11 @@ pub(super) fn is_active() -> bool { /// Record a throw from `from_bb` with value `exc_val`. Sets terminator Jump to catch and /// appends predecessor+value to the incoming list. Returns the catch block id if active. -pub(super) fn record_throw(f: &mut MirFunction, from_bb: BasicBlockId, exc_val: ValueId) -> Option { +pub(super) fn record_throw( + f: &mut MirFunction, + from_bb: BasicBlockId, + exc_val: ValueId, +) -> Option { THROW_CTX.with(|slot| { if let Some(ctx) = slot.borrow_mut().as_mut() { let target = ctx.catch_bb; diff --git a/src/runner/json_v0_bridge/lowering/try_catch.rs b/src/runner/json_v0_bridge/lowering/try_catch.rs index 8dae85a3..9ed190a1 100644 --- a/src/runner/json_v0_bridge/lowering/try_catch.rs +++ b/src/runner/json_v0_bridge/lowering/try_catch.rs @@ -1,9 +1,7 @@ -use super::{ - lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext, -}; +use super::super::ast::{CatchV0, StmtV0}; +use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; use std::collections::HashMap; -use super::super::ast::{StmtV0, CatchV0}; pub(super) fn lower_try_stmt( f: &mut MirFunction, @@ -23,9 +21,23 @@ pub(super) fn lower_try_stmt( if catches.len() > 1 { // Fallback to safe lowering (ignore catches) for multi-catch let mut tmp_vars = vars.clone(); - let mut next_bb = super::lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack, env)?; + let mut next_bb = super::lower_stmt_list_with_vars( + f, + cur_bb, + try_body, + &mut tmp_vars, + loop_stack, + env, + )?; if !finally.is_empty() { - next_bb = super::lower_stmt_list_with_vars(f, next_bb, finally, &mut tmp_vars, loop_stack, env)?; + next_bb = super::lower_stmt_list_with_vars( + f, + next_bb, + finally, + &mut tmp_vars, + loop_stack, + env, + )?; } *vars = tmp_vars; return Ok(next_bb); @@ -34,29 +46,47 @@ pub(super) fn lower_try_stmt( let base_vars = vars.clone(); let try_bb = new_block(f); let catch_bb_opt = if has_catch { Some(new_block(f)) } else { None }; - let finally_bb = if !finally.is_empty() { Some(new_block(f)) } else { None }; + let finally_bb = if !finally.is_empty() { + Some(new_block(f)) + } else { + None + }; let exit_bb = new_block(f); f.set_jump_terminator(cur_bb, try_bb)?; - if let Some(succ) = f.get_block_mut(try_bb) { succ.add_predecessor(cur_bb); } + if let Some(succ) = f.get_block_mut(try_bb) { + succ.add_predecessor(cur_bb); + } // Install thread-local throw context so nested throw expressions jump to catch_bb if has_catch { let catch_bb = catch_bb_opt.expect("catch_bb must exist when has_catch"); if crate::config::env::cli_verbose() { - eprintln!("[Bridge] try_result_mode: set ThrowCtx (catch_bb={:?})", catch_bb); + eprintln!( + "[Bridge] try_result_mode: set ThrowCtx (catch_bb={:?})", + catch_bb + ); } super::throw_ctx::set(catch_bb); } else if crate::config::env::cli_verbose() { eprintln!("[Bridge] try_result_mode: no catch present; ThrowCtx not set"); } let mut try_vars = base_vars.clone(); - let try_end = super::lower_stmt_list_with_vars(f, try_bb, try_body, &mut try_vars, loop_stack, env)?; + let try_end = + super::lower_stmt_list_with_vars(f, try_bb, try_body, &mut try_vars, loop_stack, env)?; // Take recorded incoming exceptions - let incoming_exc = if has_catch { super::throw_ctx::take().map(|c| c.incoming).unwrap_or_default() } else { Vec::new() }; + let incoming_exc = if has_catch { + super::throw_ctx::take() + .map(|c| c.incoming) + .unwrap_or_default() + } else { + Vec::new() + }; let target = finally_bb.unwrap_or(exit_bb); f.set_jump_terminator(try_end, target)?; - if let Some(succ) = f.get_block_mut(target) { succ.add_predecessor(try_end); } + if let Some(succ) = f.get_block_mut(target) { + succ.add_predecessor(try_end); + } let try_branch_vars = try_vars.clone(); // Lower catch block if present and reachable @@ -72,7 +102,9 @@ pub(super) fn lower_try_stmt( if let Some(_bb) = f.get_block_mut(catch_bb) { let mut inputs = incoming_exc.clone(); inputs.sort_by_key(|(bbid, _)| bbid.0); - crate::mir::ssot::cf_common::insert_phi_at_head(f, catch_bb, phi_dst, inputs); + crate::mir::ssot::cf_common::insert_phi_at_head( + f, catch_bb, phi_dst, inputs, + ); } catch_vars.insert(param.clone(), phi_dst); } @@ -87,7 +119,9 @@ pub(super) fn lower_try_stmt( )?; let target = finally_bb.unwrap_or(exit_bb); f.set_jump_terminator(end, target)?; - if let Some(succ) = f.get_block_mut(target) { succ.add_predecessor(end); } + if let Some(succ) = f.get_block_mut(target) { + succ.add_predecessor(end); + } (end, catch_vars) } else { (try_end, base_vars.clone()) @@ -98,21 +132,30 @@ pub(super) fn lower_try_stmt( if let Some(finally_block) = finally_bb { // Compute merged var map from try_end + catch_end (if has_catch) let branch_vars: Vec<(BasicBlockId, HashMap)> = if has_catch { - vec![(try_end, try_branch_vars.clone()), (catch_end, catch_branch_vars.clone())] + vec![ + (try_end, try_branch_vars.clone()), + (catch_end, catch_branch_vars.clone()), + ] } else { vec![(try_end, try_branch_vars.clone())] }; let mut names: HashSet = base_vars.keys().cloned().collect(); - for (_, map) in &branch_vars { names.extend(map.keys().cloned()); } + for (_, map) in &branch_vars { + names.extend(map.keys().cloned()); + } let mut merged_vars = base_vars.clone(); let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new(); for name in names { let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); for (bbid, map) in &branch_vars { - if let Some(&v) = map.get(&name) { inputs.push((*bbid, v)); } + if let Some(&v) = map.get(&name) { + inputs.push((*bbid, v)); + } } if inputs.is_empty() { - if let Some(&b) = base_vars.get(&name) { merged_vars.insert(name.clone(), b); } + if let Some(&b) = base_vars.get(&name) { + merged_vars.insert(name.clone(), b); + } continue; } let uniq: HashSet = inputs.iter().map(|(_, v)| *v).collect(); @@ -126,12 +169,23 @@ pub(super) fn lower_try_stmt( merged_vars.insert(name.clone(), dst); } if let Some(_bb) = f.get_block_mut(finally_block) { - for (dst, inputs) in phi_entries { crate::mir::ssot::cf_common::insert_phi_at_head(f, finally_block, dst, inputs); } + for (dst, inputs) in phi_entries { + crate::mir::ssot::cf_common::insert_phi_at_head(f, finally_block, dst, inputs); + } } let mut finally_vars = merged_vars.clone(); - let final_end = super::lower_stmt_list_with_vars(f, finally_block, finally, &mut finally_vars, loop_stack, env)?; + let final_end = super::lower_stmt_list_with_vars( + f, + finally_block, + finally, + &mut finally_vars, + loop_stack, + env, + )?; f.set_jump_terminator(final_end, exit_bb)?; - if let Some(succ) = f.get_block_mut(exit_bb) { succ.add_predecessor(final_end); } + if let Some(succ) = f.get_block_mut(exit_bb) { + succ.add_predecessor(final_end); + } *vars = finally_vars; return Ok(exit_bb); } else { @@ -142,38 +196,49 @@ pub(super) fn lower_try_stmt( vec![(try_end, try_branch_vars)] }; let mut names: HashSet = base_vars.keys().cloned().collect(); - for (_, map) in &branch_vars { names.extend(map.keys().cloned()); } + for (_, map) in &branch_vars { + names.extend(map.keys().cloned()); + } let mut merged_vars = base_vars.clone(); let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new(); for name in names { let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - for (bbid, map) in &branch_vars { if let Some(&v) = map.get(&name) { inputs.push((*bbid, v)); } } - if inputs.is_empty() { if let Some(&b) = base_vars.get(&name) { merged_vars.insert(name.clone(), b); } continue; } + for (bbid, map) in &branch_vars { + if let Some(&v) = map.get(&name) { + inputs.push((*bbid, v)); + } + } + if inputs.is_empty() { + if let Some(&b) = base_vars.get(&name) { + merged_vars.insert(name.clone(), b); + } + continue; + } let uniq: HashSet = inputs.iter().map(|(_, v)| *v).collect(); - if uniq.len() == 1 { merged_vars.insert(name.clone(), inputs[0].1); continue; } + if uniq.len() == 1 { + merged_vars.insert(name.clone(), inputs[0].1); + continue; + } let dst = f.next_value_id(); inputs.sort_by_key(|(bbid, _)| bbid.0); phi_entries.push((dst, inputs)); merged_vars.insert(name.clone(), dst); } if let Some(_bb) = f.get_block_mut(exit_bb) { - for (dst, inputs) in phi_entries { crate::mir::ssot::cf_common::insert_phi_at_head(f, exit_bb, dst, inputs); } + for (dst, inputs) in phi_entries { + crate::mir::ssot::cf_common::insert_phi_at_head(f, exit_bb, dst, inputs); + } } *vars = merged_vars; return Ok(exit_bb); } } else if !try_enabled || catches.is_empty() || catches.len() > 1 { let mut tmp_vars = vars.clone(); - let mut next_bb = lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack, env)?; + let mut next_bb = + lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack, env)?; if !finally.is_empty() { - next_bb = lower_stmt_list_with_vars( - f, - next_bb, - finally, - &mut tmp_vars, - loop_stack, - env, - )?; + next_bb = + lower_stmt_list_with_vars(f, next_bb, finally, &mut tmp_vars, loop_stack, env)?; } *vars = tmp_vars; return Ok(next_bb); @@ -260,7 +325,9 @@ pub(super) fn lower_try_stmt( } // フェーズM.2: PHI統一処理(no_phi分岐削除) if let Some(_bb) = f.get_block_mut(finally_block) { - for (dst, inputs) in phi_entries { crate::mir::ssot::cf_common::insert_phi_at_head(f, finally_block, dst, inputs); } + for (dst, inputs) in phi_entries { + crate::mir::ssot::cf_common::insert_phi_at_head(f, finally_block, dst, inputs); + } } let mut finally_vars = merged_vars.clone(); let final_end = lower_stmt_list_with_vars( @@ -309,7 +376,9 @@ pub(super) fn lower_try_stmt( } // フェーズM.2: PHI統一処理(no_phi分岐削除) if let Some(_bb) = f.get_block_mut(exit_bb) { - for (dst, inputs) in phi_entries { crate::mir::ssot::cf_common::insert_phi_at_head(f, exit_bb, dst, inputs); } + for (dst, inputs) in phi_entries { + crate::mir::ssot::cf_common::insert_phi_at_head(f, exit_bb, dst, inputs); + } } *vars = merged_vars; Ok(exit_bb) diff --git a/src/runner/json_v0_bridge/mod.rs b/src/runner/json_v0_bridge/mod.rs index 26d13c42..09ff6144 100644 --- a/src/runner/json_v0_bridge/mod.rs +++ b/src/runner/json_v0_bridge/mod.rs @@ -10,16 +10,26 @@ pub fn parse_json_v0_to_module(json: &str) -> Result) -> Result { +pub fn parse_json_v0_to_module_with_imports( + json: &str, + imports: HashMap, +) -> Result { let prog: ProgramV0 = serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?; if crate::config::env::cli_verbose() { let first = prog .body .get(1) - .map(|s| match s { StmtV0::Try { .. } => "Try", _ => "Other" }) + .map(|s| match s { + StmtV0::Try { .. } => "Try", + _ => "Other", + }) .unwrap_or(""); - eprintln!("[Bridge] JSON v0: body_len={} first_type={}", prog.body.len(), first); + eprintln!( + "[Bridge] JSON v0: body_len={} first_type={}", + prog.body.len(), + first + ); } if prog.version != 0 || prog.kind != "Program" { return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into()); diff --git a/src/runner/json_v1_bridge.rs b/src/runner/json_v1_bridge.rs index 8c72dd4f..4b53dedd 100644 --- a/src/runner/json_v1_bridge.rs +++ b/src/runner/json_v1_bridge.rs @@ -1,9 +1,9 @@ +use super::mir_json::common as mirjson_common; use crate::mir::{ function::{FunctionSignature, MirFunction, MirModule}, BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId, }; use serde_json::Value; -use super::mir_json::common as mirjson_common; fn parse_effects_from(node: &Value) -> EffectMask { if let Some(arr) = node.get("effects").and_then(Value::as_array) { @@ -11,10 +11,18 @@ fn parse_effects_from(node: &Value) -> EffectMask { for e in arr { if let Some(s) = e.as_str() { match s { - "write" | "mut" | "WriteHeap" => { m = m.union(EffectMask::WRITE); } - "read" | "ReadHeap" => { m = m.union(EffectMask::READ); } - "io" | "IO" | "ffi" | "FFI" | "debug" => { m = m.union(EffectMask::IO); } - "control" | "Control" => { m = m.union(EffectMask::CONTROL); } + "write" | "mut" | "WriteHeap" => { + m = m.union(EffectMask::WRITE); + } + "read" | "ReadHeap" => { + m = m.union(EffectMask::READ); + } + "io" | "IO" | "ffi" | "FFI" | "debug" => { + m = m.union(EffectMask::IO); + } + "control" | "Control" => { + m = m.union(EffectMask::CONTROL); + } _ => {} } } @@ -39,12 +47,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { let schema = match value.get("schema_version") { Some(Value::String(s)) => s.clone(), - Some(other) => { - return Err(format!( - "expected schema_version string, found {}", - other - )) - } + Some(other) => return Err(format!("expected schema_version string, found {}", other)), None => return Ok(None), }; @@ -98,7 +101,8 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { let block_id = block .get("id") .and_then(|id| id.as_u64()) - .ok_or_else(|| format!("function '{}' block missing id", func_name))? as u32; + .ok_or_else(|| format!("function '{}' block missing id", func_name))? + as u32; let bb_id = BasicBlockId::new(block_id); if mir_fn.get_block(bb_id).is_none() { mir_fn.add_block(BasicBlock::new(bb_id)); @@ -118,34 +122,23 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { })?; for inst in instructions { - let op = inst - .get("op") - .and_then(|o| o.as_str()) - .ok_or_else(|| { - format!( - "function '{}' block {} missing op field", - func_name, block_id - ) - })?; + let op = inst.get("op").and_then(|o| o.as_str()).ok_or_else(|| { + format!( + "function '{}' block {} missing op field", + func_name, block_id + ) + })?; match op { "const" => { - let dst = inst - .get("dst") - .and_then(|d| d.as_u64()) - .ok_or_else(|| { - format!( - "const instruction missing dst in function '{}'", - func_name - ) - })? as u32; - let value_obj = inst - .get("value") - .ok_or_else(|| { - format!( - "const instruction missing value in function '{}'", - func_name - ) - })?; + let dst = inst.get("dst").and_then(|d| d.as_u64()).ok_or_else(|| { + format!("const instruction missing dst in function '{}'", func_name) + })? as u32; + let value_obj = inst.get("value").ok_or_else(|| { + format!( + "const instruction missing value in function '{}'", + func_name + ) + })?; let const_val = mirjson_common::parse_const_value_generic(value_obj)?; block_ref.add_instruction(MirInstruction::Const { dst: ValueId::new(dst), @@ -156,24 +149,12 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { } } "copy" => { - let dst = inst - .get("dst") - .and_then(|d| d.as_u64()) - .ok_or_else(|| { - format!( - "copy instruction missing dst in function '{}'", - func_name - ) - })? as u32; - let src = inst - .get("src") - .and_then(|d| d.as_u64()) - .ok_or_else(|| { - format!( - "copy instruction missing src in function '{}'", - func_name - ) - })? as u32; + let dst = inst.get("dst").and_then(|d| d.as_u64()).ok_or_else(|| { + format!("copy instruction missing dst in function '{}'", func_name) + })? as u32; + let src = inst.get("src").and_then(|d| d.as_u64()).ok_or_else(|| { + format!("copy instruction missing src in function '{}'", func_name) + })? as u32; block_ref.add_instruction(MirInstruction::Copy { dst: ValueId::new(dst), src: ValueId::new(src), @@ -186,10 +167,12 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { let dst = require_u64(inst, "dst", "binop dst")? as u32; let lhs = require_u64(inst, "lhs", "binop lhs")? as u32; let rhs = require_u64(inst, "rhs", "binop rhs")? as u32; - let operation = inst - .get("operation") - .and_then(Value::as_str) - .ok_or_else(|| format!("binop operation missing in function '{}'", func_name))?; + let operation = + inst.get("operation") + .and_then(Value::as_str) + .ok_or_else(|| { + format!("binop operation missing in function '{}'", func_name) + })?; let bop = parse_binop(operation)?; block_ref.add_instruction(MirInstruction::BinOp { dst: ValueId::new(dst), @@ -206,7 +189,10 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { // Accept both JSON shapes: // - operation: symbolic string ("<", ">=", "==", ...) // - cmp: spelled enum name ("Lt", "Le", "Gt", "Ge", "Eq", "Ne") - let op_sym_opt = inst.get("operation").and_then(Value::as_str).map(|s| s.to_string()); + let op_sym_opt = inst + .get("operation") + .and_then(Value::as_str) + .map(|s| s.to_string()); let op_sym = if let Some(sym) = op_sym_opt { sym } else if let Some(name) = inst.get("cmp").and_then(Value::as_str) { @@ -257,25 +243,31 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { } "phi" => { let dst = require_u64(inst, "dst", "phi dst")? as u32; - let incoming = inst - .get("incoming") - .and_then(Value::as_array) - .ok_or_else(|| format!("phi incoming missing in function '{}'", func_name))?; + let incoming = + inst.get("incoming") + .and_then(Value::as_array) + .ok_or_else(|| { + format!("phi incoming missing in function '{}'", func_name) + })?; let mut pairs = Vec::with_capacity(incoming.len()); for entry in incoming { - let pair = entry - .as_array() - .ok_or_else(|| format!("phi incoming entry must be array in function '{}'", func_name))?; + let pair = entry.as_array().ok_or_else(|| { + format!( + "phi incoming entry must be array in function '{}'", + func_name + ) + })?; if pair.len() != 2 { return Err("phi incoming entry must have 2 elements".into()); } // JSON shape: [pred_block_id, value_id] - let pred_bb = pair[0] - .as_u64() - .ok_or_else(|| "phi incoming predecessor block must be integer".to_string())? as u32; + let pred_bb = pair[0].as_u64().ok_or_else(|| { + "phi incoming predecessor block must be integer".to_string() + })? as u32; let val = pair[1] .as_u64() - .ok_or_else(|| "phi incoming value must be integer".to_string())? as u32; + .ok_or_else(|| "phi incoming value must be integer".to_string())? + as u32; pairs.push((BasicBlockId::new(pred_bb), ValueId::new(val))); } block_ref.add_instruction(MirInstruction::Phi { @@ -303,20 +295,30 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { // - flat: { op:"mir_call", callee:{...}, args:[...], effects:[] } // - nested: { op:"mir_call", mir_call:{ callee:{...}, args:[...], effects:[] } } // dst remains at the instruction root level in both forms. - let dst_opt = inst.get("dst").and_then(|d| d.as_u64()).map(|v| ValueId::new(v as u32)); - let effects = if let Some(sub) = inst.get("mir_call") { parse_effects_from(sub) } else { parse_effects_from(inst) }; + let dst_opt = inst + .get("dst") + .and_then(|d| d.as_u64()) + .map(|v| ValueId::new(v as u32)); + let effects = if let Some(sub) = inst.get("mir_call") { + parse_effects_from(sub) + } else { + parse_effects_from(inst) + }; // args: support both flat/nested placement let mut argv: Vec = Vec::new(); - if let Some(arr) = inst - .get("args") - .and_then(|a| a.as_array()) - .or_else(|| inst.get("mir_call").and_then(|m| m.get("args").and_then(|a| a.as_array()))) + if let Some(arr) = + inst.get("args").and_then(|a| a.as_array()).or_else(|| { + inst.get("mir_call") + .and_then(|m| m.get("args").and_then(|a| a.as_array())) + }) { for a in arr { - let id = a.as_u64().ok_or_else(|| format!( - "mir_call arg must be integer value id in function '{}'", - func_name - ))? as u32; + let id = a.as_u64().ok_or_else(|| { + format!( + "mir_call arg must be integer value id in function '{}'", + func_name + ) + })? as u32; argv.push(ValueId::new(id)); } } @@ -325,30 +327,37 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { .get("callee") .or_else(|| inst.get("mir_call").and_then(|m| m.get("callee"))) .ok_or_else(|| { - format!("mir_call missing callee in function '{}'", func_name) - })?; - let ctype = callee_obj - .get("type") - .and_then(Value::as_str) - .ok_or_else(|| format!("mir_call callee.type missing in function '{}'", func_name))?; + format!("mir_call missing callee in function '{}'", func_name) + })?; + let ctype = + callee_obj + .get("type") + .and_then(Value::as_str) + .ok_or_else(|| { + format!( + "mir_call callee.type missing in function '{}'", + func_name + ) + })?; match ctype { "Global" => { let raw_name = callee_obj .get("name") .and_then(Value::as_str) - .ok_or_else(|| format!( - "mir_call callee Global missing name in function '{}'", - func_name - ))?; + .ok_or_else(|| { + format!( + "mir_call callee Global missing name in function '{}'", + func_name + ) + })?; // Map known console aliases to interpreter-accepted names let mapped = match raw_name { "print" => "print".to_string(), "nyash.builtin.print" => "nyash.builtin.print".to_string(), "nyash.console.log" => "nyash.console.log".to_string(), // Accept env.console.* as nyash.console.log (numeric only) - "env.console.log" | "env.console.warn" | "env.console.error" => { - "nyash.console.log".to_string() - } + "env.console.log" | "env.console.warn" + | "env.console.error" => "nyash.console.log".to_string(), other => { return Err(format!( "unsupported Global callee '{}' in mir_call (Gate-C v1 bridge)", @@ -363,7 +372,9 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { args: argv, effects, }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } + if let Some(d) = dst_opt { + max_value_id = max_value_id.max(d.as_u32() + 1); + } } "Constructor" => { // new box instance: box_type required @@ -375,10 +386,12 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { func_name ))?; // dst required for Constructor - let dst = dst_opt.ok_or_else(|| format!( - "mir_call Constructor requires dst in function '{}'", - func_name - ))?; + let dst = dst_opt.ok_or_else(|| { + format!( + "mir_call Constructor requires dst in function '{}'", + func_name + ) + })?; block_ref.add_instruction(MirInstruction::NewBox { dst, box_type: bt.to_string(), @@ -391,18 +404,22 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { let method = callee_obj .get("method") .and_then(Value::as_str) - .ok_or_else(|| format!( + .ok_or_else(|| { + format!( "mir_call callee Method missing method in function '{}'", func_name - ))? + ) + })? .to_string(); let recv_id = callee_obj .get("receiver") .and_then(Value::as_u64) - .ok_or_else(|| format!( + .ok_or_else(|| { + format!( "mir_call callee Method missing receiver in function '{}'", func_name - ))? as u32; + ) + })? as u32; let box_name = callee_obj .get("box_name") .and_then(Value::as_str) @@ -422,7 +439,9 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { args: argv, effects, }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } + if let Some(d) = dst_opt { + max_value_id = max_value_id.max(d.as_u32() + 1); + } } "Closure" => { // Two shapes are seen in the wild: @@ -433,13 +452,17 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { || callee_obj.get("me_capture").is_some(); if has_new_fields { // Closure creation (NewClosure equivalent) - let dst = dst_opt.ok_or_else(|| format!( - "mir_call Closure requires dst in function '{}'", - func_name - ))?; + let dst = dst_opt.ok_or_else(|| { + format!( + "mir_call Closure requires dst in function '{}'", + func_name + ) + })?; // params: array of strings (optional) let mut params: Vec = Vec::new(); - if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) { + if let Some(arr) = + callee_obj.get("params").and_then(Value::as_array) + { for p in arr { let s = p.as_str().ok_or_else(|| format!( "mir_call Closure params must be strings in function '{}'", @@ -450,7 +473,9 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { } // captures: array of [name, id] let mut captures: Vec<(String, ValueId)> = Vec::new(); - if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) { + if let Some(arr) = + callee_obj.get("captures").and_then(Value::as_array) + { for e in arr { let pair = e.as_array().ok_or_else(|| format!( "mir_call Closure capture entry must be array in function '{}'", @@ -460,11 +485,14 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { return Err("mir_call Closure capture entry must have 2 elements".into()); } let name = pair[0].as_str().ok_or_else(|| { - "mir_call Closure capture[0] must be string".to_string() + "mir_call Closure capture[0] must be string" + .to_string() })?; let id = pair[1].as_u64().ok_or_else(|| { - "mir_call Closure capture[1] must be integer".to_string() - })? as u32; + "mir_call Closure capture[1] must be integer" + .to_string() + })? + as u32; captures.push((name.to_string(), ValueId::new(id))); } } @@ -487,12 +515,16 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { let fid = callee_obj .get("func") .and_then(Value::as_u64) - .ok_or_else(|| format!( + .ok_or_else(|| { + format!( "mir_call callee Closure missing func in function '{}'", func_name - ))? as u32; + ) + })? as u32; // Captures array (if present) are appended to argv for minimal parity - if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) { + if let Some(caps) = + callee_obj.get("captures").and_then(Value::as_array) + { for c in caps { let id = c.as_u64().ok_or_else(|| format!( "mir_call Closure capture must be integer in function '{}'", @@ -504,21 +536,27 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { block_ref.add_instruction(MirInstruction::Call { dst: dst_opt, func: ValueId::new(0), - callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))), + callee: Some(crate::mir::definitions::Callee::Value( + ValueId::new(fid), + )), args: argv, effects, }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } + if let Some(d) = dst_opt { + max_value_id = max_value_id.max(d.as_u32() + 1); + } } } "Extern" => { let name = callee_obj .get("name") .and_then(Value::as_str) - .ok_or_else(|| format!( - "mir_call callee Extern missing name in function '{}'", - func_name - ))? + .ok_or_else(|| { + format!( + "mir_call callee Extern missing name in function '{}'", + func_name + ) + })? .to_string(); block_ref.add_instruction(MirInstruction::Call { dst: dst_opt, @@ -527,25 +565,33 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { args: argv, effects: EffectMask::IO, }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } + if let Some(d) = dst_opt { + max_value_id = max_value_id.max(d.as_u32() + 1); + } } "Value" => { // dynamic function value id: field 'func' (u64) let fid = callee_obj .get("func") .and_then(Value::as_u64) - .ok_or_else(|| format!( - "mir_call callee Value missing func in function '{}'", - func_name - ))? as u32; + .ok_or_else(|| { + format!( + "mir_call callee Value missing func in function '{}'", + func_name + ) + })? as u32; block_ref.add_instruction(MirInstruction::Call { dst: dst_opt, func: ValueId::new(0), - callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))), + callee: Some(crate::mir::definitions::Callee::Value( + ValueId::new(fid), + )), args: argv, effects, }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } + if let Some(d) = dst_opt { + max_value_id = max_value_id.max(d.as_u32() + 1); + } } // (no duplicate Closure arm; handled above) other => { @@ -646,9 +692,9 @@ fn parse_const_value(value_obj: &Value) -> Result { match box_type.as_str() { // StringBox handle is serialized with raw string payload "StringBox" => { - let s = raw_val - .as_str() - .ok_or_else(|| "StringBox const expects string value".to_string())?; + let s = raw_val.as_str().ok_or_else(|| { + "StringBox const expects string value".to_string() + })?; return Ok(ConstValue::String(s.to_string())); } // Other handle kinds are not yet supported in the bridge @@ -667,7 +713,9 @@ fn parse_const_value(value_obj: &Value) -> Result { // No explicit type: heuristics match raw_val { - Value::Number(n) => Ok(ConstValue::Integer(n.as_i64().ok_or_else(|| "integer expected".to_string())?)), + Value::Number(n) => Ok(ConstValue::Integer( + n.as_i64().ok_or_else(|| "integer expected".to_string())?, + )), Value::Bool(b) => Ok(ConstValue::Bool(b)), Value::String(s) => Ok(ConstValue::String(s)), _ => Err("const value has unsupported type descriptor".to_string()), diff --git a/src/runner/mir_json/common.rs b/src/runner/mir_json/common.rs index a8c46b5f..6b809c5e 100644 --- a/src/runner/mir_json/common.rs +++ b/src/runner/mir_json/common.rs @@ -22,17 +22,29 @@ pub fn parse_const_value_generic(value_obj: &Value) -> Result raw_val.as_i64().map(ConstValue::Integer).ok_or_else(|| "const value expected integer".to_string()), - "f64" | "float" => raw_val.as_f64().map(ConstValue::Float).ok_or_else(|| "const value expected float".to_string()), + "i64" | "int" => raw_val + .as_i64() + .map(ConstValue::Integer) + .ok_or_else(|| "const value expected integer".to_string()), + "f64" | "float" => raw_val + .as_f64() + .map(ConstValue::Float) + .ok_or_else(|| "const value expected float".to_string()), "i1" | "bool" => Ok(match raw_val { Value::Bool(b) => ConstValue::Bool(b), Value::Number(n) => ConstValue::Bool(n.as_i64().unwrap_or(0) != 0), Value::String(ref s) => ConstValue::Bool(s == "true" || s == "1"), _ => ConstValue::Bool(false), }), - "string" | "String" => raw_val.as_str().map(|s| ConstValue::String(s.to_string())).ok_or_else(|| "const value expected string".to_string()), + "string" | "String" => raw_val + .as_str() + .map(|s| ConstValue::String(s.to_string())) + .ok_or_else(|| "const value expected string".to_string()), "void" => Ok(ConstValue::Void), - other => Err(format!("unsupported const type '{}' in MIR JSON bridge", other)), + other => Err(format!( + "unsupported const type '{}' in MIR JSON bridge", + other + )), }; } @@ -45,7 +57,10 @@ pub fn parse_const_value_generic(value_obj: &Value) -> Result Err(format!("unsupported const handle type '{}' in MIR JSON bridge", other)), + other => Err(format!( + "unsupported const handle type '{}' in MIR JSON bridge", + other + )), }; } } @@ -54,10 +69,12 @@ pub fn parse_const_value_generic(value_obj: &Value) -> Result n.as_i64().map(ConstValue::Integer).ok_or_else(|| "integer expected".to_string()), + Value::Number(n) => n + .as_i64() + .map(ConstValue::Integer) + .ok_or_else(|| "integer expected".to_string()), Value::Bool(b) => Ok(ConstValue::Bool(b)), Value::String(s) => Ok(ConstValue::String(s)), _ => Err("const value has unsupported type descriptor".to_string()), } } - diff --git a/src/runner/mir_json_emit.rs b/src/runner/mir_json_emit.rs index b428bde6..d2512636 100644 --- a/src/runner/mir_json_emit.rs +++ b/src/runner/mir_json_emit.rs @@ -1,5 +1,5 @@ -use serde_json::json; use crate::mir::definitions::call_unified::Callee; +use serde_json::json; /// Emit MIR JSON for Python harness/PyVM. /// The JSON schema matches tools/llvmlite_harness.py expectations and is @@ -64,7 +64,10 @@ fn has_numeric_core_boxcall(root: &serde_json::Value) -> bool { /// - Strict: if NYASH_AOT_NUMERIC_CORE_STRICT=1, return Err to fail fast. #[allow(dead_code)] fn check_numeric_core_invariants(root: &serde_json::Value) -> Result<(), String> { - let numeric_on = matches!(std::env::var("NYASH_AOT_NUMERIC_CORE").ok().as_deref(), Some("1")); + let numeric_on = matches!( + std::env::var("NYASH_AOT_NUMERIC_CORE").ok().as_deref(), + Some("1") + ); if !numeric_on { return Ok(()); } @@ -74,7 +77,9 @@ fn check_numeric_core_invariants(root: &serde_json::Value) -> Result<(), String> } let strict = matches!( - std::env::var("NYASH_AOT_NUMERIC_CORE_STRICT").ok().as_deref(), + std::env::var("NYASH_AOT_NUMERIC_CORE_STRICT") + .ok() + .as_deref(), Some("1") ); @@ -118,7 +123,13 @@ fn emit_unified_mir_call( "name": name }); } - Callee::Method { box_name, method, receiver, certainty, .. } => { + Callee::Method { + box_name, + method, + receiver, + certainty, + .. + } => { call_obj["mir_call"]["callee"] = json!({ "type": "Method", "box_name": box_name, @@ -133,8 +144,13 @@ fn emit_unified_mir_call( "box_type": box_type }); } - Callee::Closure { params, captures, me_capture } => { - let captures_json: Vec<_> = captures.iter() + Callee::Closure { + params, + captures, + me_capture, + } => { + let captures_json: Vec<_> = captures + .iter() .map(|(name, vid)| json!([name, vid.as_u32()])) .collect(); call_obj["mir_call"]["callee"] = json!({ @@ -175,10 +191,11 @@ pub fn emit_mir_json_for_harness( if let Some(bb) = f.blocks.get(&bid) { let mut insts = Vec::new(); // Pre-scan: collect values defined anywhere in this block (to delay use-before-def copies) - let mut block_defines: std::collections::HashSet = std::collections::HashSet::new(); + let mut block_defines: std::collections::HashSet = + std::collections::HashSet::new(); for inst in &bb.instructions { match inst { - | I::UnaryOp { dst, .. } + I::UnaryOp { dst, .. } | I::Const { dst, .. } | I::BinOp { dst, .. } | I::Compare { dst, .. } @@ -193,7 +210,8 @@ pub fn emit_mir_json_for_harness( } } // Track which values have been emitted (to order copies after their sources) - let mut emitted_defs: std::collections::HashSet = std::collections::HashSet::new(); + let mut emitted_defs: std::collections::HashSet = + std::collections::HashSet::new(); // PHI first(オプション) for inst in &bb.instructions { if let I::Copy { dst, src } = inst { @@ -202,7 +220,9 @@ pub fn emit_mir_json_for_harness( if block_defines.contains(&s) && !emitted_defs.contains(&s) { // delayed; will be emitted after non-PHI pass } else { - insts.push(json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()})); + insts.push( + json!({"op":"copy","dst": dst.as_u32(), "src": src.as_u32()}), + ); emitted_defs.insert(dst.as_u32()); } continue; @@ -371,17 +391,27 @@ pub fn emit_mir_json_for_harness( insts.push(obj); } I::Call { - dst, func, callee, args, effects, .. + dst, + func, + callee, + args, + effects, + .. } => { // Phase 15.5: Unified Call support with environment variable control - let use_unified = match std::env::var("NYASH_MIR_UNIFIED_CALL").ok().as_deref().map(|s| s.to_ascii_lowercase()) { + let use_unified = match std::env::var("NYASH_MIR_UNIFIED_CALL") + .ok() + .as_deref() + .map(|s| s.to_ascii_lowercase()) + { Some(s) if s == "0" || s == "false" || s == "off" => false, _ => true, }; if use_unified && callee.is_some() { // v1: Unified mir_call format - let effects_str: Vec<&str> = if effects.is_io() { vec!["IO"] } else { vec![] }; + let effects_str: Vec<&str> = + if effects.is_io() { vec!["IO"] } else { vec![] }; let args_u32: Vec = args.iter().map(|v| v.as_u32()).collect(); let unified_call = emit_unified_mir_call( dst.map(|v| v.as_u32()), @@ -394,10 +424,13 @@ pub fn emit_mir_json_for_harness( // v0: When unified is OFF but callee exists, emit proper v0 format use nyash_rust::mir::definitions::Callee; match callee.as_ref().unwrap() { - Callee::Method { method, receiver, .. } => { + Callee::Method { + method, receiver, .. + } => { // Emit as boxcall for compatibility let box_val = receiver.unwrap_or(*func); - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); let mut obj = json!({ "op":"boxcall", "box": box_val.as_u32(), @@ -423,17 +456,21 @@ pub fn emit_mir_json_for_harness( obj["dst_type"] = t; } insts.push(obj); - if let Some(d) = dst.map(|v| v.as_u32()) { emitted_defs.insert(d); } + if let Some(d) = dst.map(|v| v.as_u32()) { + emitted_defs.insert(d); + } } _ => { // Other callee types: emit generic call - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); insts.push(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())})); } } } else { // v0: Legacy call format (no callee info) - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); insts.push(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())})); } } @@ -494,7 +531,9 @@ pub fn emit_mir_json_for_harness( obj["dst_type"] = t; } insts.push(obj); - if let Some(d) = dst.map(|v| v.as_u32()) { emitted_defs.insert(d); } + if let Some(d) = dst.map(|v| v.as_u32()) { + emitted_defs.insert(d); + } } I::NewBox { dst, @@ -543,15 +582,19 @@ pub fn emit_mir_json_for_harness( // Phase 15.5: JSON v1 schema with environment variable control let use_v1_schema = std::env::var("NYASH_JSON_SCHEMA_V1").unwrap_or_default() == "1" - || match std::env::var("NYASH_MIR_UNIFIED_CALL").ok().as_deref().map(|s| s.to_ascii_lowercase()) { - Some(s) if s == "0" || s == "false" || s == "off" => false, - _ => true, - }; + || match std::env::var("NYASH_MIR_UNIFIED_CALL") + .ok() + .as_deref() + .map(|s| s.to_ascii_lowercase()) + { + Some(s) if s == "0" || s == "false" || s == "off" => false, + _ => true, + }; let root = if use_v1_schema { create_json_v1_root(json!(funs)) } else { - json!({"functions": funs}) // v0 legacy format + json!({"functions": funs}) // v0 legacy format }; // NOTE: numeric_core strict validation is applied on the AotPrep output @@ -578,7 +621,8 @@ pub fn emit_mir_json_for_harness_bin( if let Some(bb) = f.blocks.get(&bid) { let mut insts = Vec::new(); // Pre-scan to collect values defined in this block - let mut block_defines: std::collections::HashSet = std::collections::HashSet::new(); + let mut block_defines: std::collections::HashSet = + std::collections::HashSet::new(); for inst in &bb.instructions { match inst { I::Copy { dst, .. } @@ -589,11 +633,14 @@ pub fn emit_mir_json_for_harness_bin( | I::ExternCall { dst: Some(dst), .. } | I::BoxCall { dst: Some(dst), .. } | I::NewBox { dst, .. } - | I::Phi { dst, .. } => { block_defines.insert(dst.as_u32()); } + | I::Phi { dst, .. } => { + block_defines.insert(dst.as_u32()); + } _ => {} } } - let mut emitted_defs: std::collections::HashSet = std::collections::HashSet::new(); + let mut emitted_defs: std::collections::HashSet = + std::collections::HashSet::new(); for inst in &bb.instructions { if let I::Phi { dst, inputs } = inst { let incoming: Vec<_> = inputs @@ -625,7 +672,8 @@ pub fn emit_mir_json_for_harness_bin( for inst in &bb.instructions { match inst { I::Copy { dst, src } => { - let d = dst.as_u32(); let s = src.as_u32(); + let d = dst.as_u32(); + let s = src.as_u32(); if block_defines.contains(&s) && !emitted_defs.contains(&s) { delayed_copies.push((d, s)); } else { @@ -708,17 +756,27 @@ pub fn emit_mir_json_for_harness_bin( emitted_defs.insert(dst.as_u32()); } I::Call { - dst, func, callee, args, effects, .. + dst, + func, + callee, + args, + effects, + .. } => { // Phase 15.5: Unified Call support with environment variable control - let use_unified = match std::env::var("NYASH_MIR_UNIFIED_CALL").ok().as_deref().map(|s| s.to_ascii_lowercase()) { + let use_unified = match std::env::var("NYASH_MIR_UNIFIED_CALL") + .ok() + .as_deref() + .map(|s| s.to_ascii_lowercase()) + { Some(s) if s == "0" || s == "false" || s == "off" => false, _ => true, }; if use_unified && callee.is_some() { // v1: Unified mir_call format - let effects_str: Vec<&str> = if effects.is_io() { vec!["IO"] } else { vec![] }; + let effects_str: Vec<&str> = + if effects.is_io() { vec!["IO"] } else { vec![] }; let args_u32: Vec = args.iter().map(|v| v.as_u32()).collect(); let unified_call = emit_unified_mir_call( dst.map(|v| v.as_u32()), @@ -731,10 +789,13 @@ pub fn emit_mir_json_for_harness_bin( // v0: When unified is OFF but callee exists, emit proper v0 format use Callee; match callee.as_ref().unwrap() { - Callee::Method { method, receiver, .. } => { + Callee::Method { + method, receiver, .. + } => { // Emit as boxcall for compatibility let box_val = receiver.unwrap_or(*func); - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); let mut obj = json!({ "op":"boxcall", "box": box_val.as_u32(), @@ -760,17 +821,21 @@ pub fn emit_mir_json_for_harness_bin( obj["dst_type"] = t; } insts.push(obj); - if let Some(d) = dst.map(|v| v.as_u32()) { emitted_defs.insert(d); } + if let Some(d) = dst.map(|v| v.as_u32()) { + emitted_defs.insert(d); + } } _ => { // Other callee types: emit generic call - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); insts.push(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())})); } } } else { // v0: Legacy call format (no callee info) - let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect(); + let args_a: Vec<_> = + args.iter().map(|v| json!(v.as_u32())).collect(); insts.push(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())})); } } @@ -792,7 +857,9 @@ pub fn emit_mir_json_for_harness_bin( } } insts.push(obj); - if let Some(d) = dst.map(|v| v.as_u32()) { emitted_defs.insert(d); } + if let Some(d) = dst.map(|v| v.as_u32()) { + emitted_defs.insert(d); + } } I::BoxCall { dst, @@ -822,7 +889,9 @@ pub fn emit_mir_json_for_harness_bin( obj["dst_type"] = t; } insts.push(obj); - if let Some(d) = dst.map(|v| v.as_u32()) { emitted_defs.insert(d); } + if let Some(d) = dst.map(|v| v.as_u32()) { + emitted_defs.insert(d); + } } I::NewBox { dst, @@ -850,7 +919,9 @@ pub fn emit_mir_json_for_harness_bin( } } // Append delayed copies after their sources - for (d, s) in delayed_copies { insts.push(json!({"op":"copy","dst": d, "src": s})); } + for (d, s) in delayed_copies { + insts.push(json!({"op":"copy","dst": d, "src": s})); + } if let Some(term) = &bb.terminator { match term { I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})), diff --git a/src/runner/mir_json_v0.rs b/src/runner/mir_json_v0.rs index c772d586..44869828 100644 --- a/src/runner/mir_json_v0.rs +++ b/src/runner/mir_json_v0.rs @@ -1,9 +1,9 @@ +use super::mir_json::common as mirjson_common; use crate::mir::{ function::{FunctionSignature, MirFunction, MirModule}, BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId, }; use serde_json::Value; -use super::mir_json::common as mirjson_common; /// Parse minimal MIR JSON v0 (no schema_version, root has `functions` and each function has `blocks`). /// Supported ops (minimal): const(i64), copy, compare(op/lhs/rhs), branch(cond/then/else), jump(target), phi(dst,incoming), ret(value?). @@ -36,7 +36,8 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { .get(0) .and_then(|b| b.get("id")) .and_then(|id| id.as_u64()) - .ok_or_else(|| format!("function '{}' entry block missing id", func_name))? as u32; + .ok_or_else(|| format!("function '{}' entry block missing id", func_name))? + as u32; let entry_bb = BasicBlockId::new(entry_id); let mut signature = FunctionSignature { @@ -52,7 +53,8 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { let block_id = block .get("id") .and_then(|id| id.as_u64()) - .ok_or_else(|| format!("function '{}' block missing id", func_name))? as u32; + .ok_or_else(|| format!("function '{}' block missing id", func_name))? + as u32; let bb_id = BasicBlockId::new(block_id); if mir_fn.get_block(bb_id).is_none() { mir_fn.add_block(BasicBlock::new(bb_id)); @@ -64,95 +66,185 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { let instructions = block .get("instructions") .and_then(|insts| insts.as_array()) - .ok_or_else(|| format!("function '{}' block {} missing instructions array", func_name, block_id))?; + .ok_or_else(|| { + format!( + "function '{}' block {} missing instructions array", + func_name, block_id + ) + })?; for inst in instructions { - let op = inst - .get("op") - .and_then(|o| o.as_str()) - .ok_or_else(|| format!("function '{}' block {} missing op field", func_name, block_id))?; + let op = inst.get("op").and_then(|o| o.as_str()).ok_or_else(|| { + format!( + "function '{}' block {} missing op field", + func_name, block_id + ) + })?; match op { "const" => { let dst = require_u64(inst, "dst", "const dst")? as u32; - let vobj = inst.get("value").ok_or_else(|| "const missing value".to_string())?; + let vobj = inst + .get("value") + .ok_or_else(|| "const missing value".to_string())?; let val = parse_const_value(vobj)?; - block_ref.add_instruction(MirInstruction::Const { dst: ValueId::new(dst), value: val }); + block_ref.add_instruction(MirInstruction::Const { + dst: ValueId::new(dst), + value: val, + }); max_value_id = max_value_id.max(dst + 1); } "copy" => { let dst = require_u64(inst, "dst", "copy dst")? as u32; let src = require_u64(inst, "src", "copy src")? as u32; - block_ref.add_instruction(MirInstruction::Copy { dst: ValueId::new(dst), src: ValueId::new(src) }); + block_ref.add_instruction(MirInstruction::Copy { + dst: ValueId::new(dst), + src: ValueId::new(src), + }); max_value_id = max_value_id.max(dst + 1); } "binop" => { let dst = require_u64(inst, "dst", "binop dst")? as u32; let lhs = require_u64(inst, "lhs", "binop lhs")? as u32; let rhs = require_u64(inst, "rhs", "binop rhs")? as u32; - let operation = inst.get("operation").and_then(Value::as_str).ok_or_else(|| "binop missing operation".to_string())?; + let operation = inst + .get("operation") + .and_then(Value::as_str) + .ok_or_else(|| "binop missing operation".to_string())?; let bop = parse_binop(operation)?; - block_ref.add_instruction(MirInstruction::BinOp { dst: ValueId::new(dst), op: bop, lhs: ValueId::new(lhs), rhs: ValueId::new(rhs) }); + block_ref.add_instruction(MirInstruction::BinOp { + dst: ValueId::new(dst), + op: bop, + lhs: ValueId::new(lhs), + rhs: ValueId::new(rhs), + }); max_value_id = max_value_id.max(dst + 1); } "compare" => { let dst = require_u64(inst, "dst", "compare dst")? as u32; let lhs = require_u64(inst, "lhs", "compare lhs")? as u32; let rhs = require_u64(inst, "rhs", "compare rhs")? as u32; - let operation = inst.get("operation").and_then(Value::as_str).unwrap_or("=="); + let operation = inst + .get("operation") + .and_then(Value::as_str) + .unwrap_or("=="); let cop = parse_compare(operation)?; - block_ref.add_instruction(MirInstruction::Compare { dst: ValueId::new(dst), op: cop, lhs: ValueId::new(lhs), rhs: ValueId::new(rhs) }); + block_ref.add_instruction(MirInstruction::Compare { + dst: ValueId::new(dst), + op: cop, + lhs: ValueId::new(lhs), + rhs: ValueId::new(rhs), + }); max_value_id = max_value_id.max(dst + 1); } "branch" => { let cond = require_u64(inst, "cond", "branch cond")? as u32; let then_bb = require_u64(inst, "then", "branch then")? as u32; let else_bb = require_u64(inst, "else", "branch else")? as u32; - block_ref.add_instruction(MirInstruction::Branch { condition: ValueId::new(cond), then_bb: BasicBlockId::new(then_bb), else_bb: BasicBlockId::new(else_bb) }); + block_ref.add_instruction(MirInstruction::Branch { + condition: ValueId::new(cond), + then_bb: BasicBlockId::new(then_bb), + else_bb: BasicBlockId::new(else_bb), + }); } "jump" => { let target = require_u64(inst, "target", "jump target")? as u32; - block_ref.add_instruction(MirInstruction::Jump { target: BasicBlockId::new(target) }); + block_ref.add_instruction(MirInstruction::Jump { + target: BasicBlockId::new(target), + }); } "phi" => { let dst = require_u64(inst, "dst", "phi dst")? as u32; - let incoming = inst.get("incoming").and_then(Value::as_array).ok_or_else(|| "phi incoming missing".to_string())?; + let incoming = inst + .get("incoming") + .and_then(Value::as_array) + .ok_or_else(|| "phi incoming missing".to_string())?; let mut pairs = Vec::with_capacity(incoming.len()); for entry in incoming { - let pair = entry.as_array().ok_or_else(|| "phi incoming entry must be array".to_string())?; - if pair.len() != 2 { return Err("phi incoming entry must have 2 elements".into()); } - let val = pair[0].as_u64().ok_or_else(|| "phi incoming value must be integer".to_string())? as u32; - let bb = pair[1].as_u64().ok_or_else(|| "phi incoming block must be integer".to_string())? as u32; + let pair = entry + .as_array() + .ok_or_else(|| "phi incoming entry must be array".to_string())?; + if pair.len() != 2 { + return Err("phi incoming entry must have 2 elements".into()); + } + let val = pair[0] + .as_u64() + .ok_or_else(|| "phi incoming value must be integer".to_string())? + as u32; + let bb = pair[1] + .as_u64() + .ok_or_else(|| "phi incoming block must be integer".to_string())? + as u32; pairs.push((BasicBlockId::new(bb), ValueId::new(val))); } - block_ref.add_instruction(MirInstruction::Phi { dst: ValueId::new(dst), inputs: pairs }); + block_ref.add_instruction(MirInstruction::Phi { + dst: ValueId::new(dst), + inputs: pairs, + }); max_value_id = max_value_id.max(dst + 1); } "ret" => { - let value = inst.get("value").and_then(|v| v.as_u64()).map(|v| ValueId::new(v as u32)); + let value = inst + .get("value") + .and_then(|v| v.as_u64()) + .map(|v| ValueId::new(v as u32)); block_ref.add_instruction(MirInstruction::Return { value }); - if let Some(val) = value { signature.return_type = MirType::Integer; max_value_id = max_value_id.max(val.as_u32() + 1); } else { signature.return_type = MirType::Void; } + if let Some(val) = value { + signature.return_type = MirType::Integer; + max_value_id = max_value_id.max(val.as_u32() + 1); + } else { + signature.return_type = MirType::Void; + } } "newbox" => { let dst = require_u64(inst, "dst", "newbox dst")? as u32; - let ty = inst.get("type").and_then(Value::as_str).ok_or_else(|| "newbox missing type".to_string())?.to_string(); - let args_v = inst.get("args").and_then(Value::as_array).cloned().unwrap_or_default(); + let ty = inst + .get("type") + .and_then(Value::as_str) + .ok_or_else(|| "newbox missing type".to_string())? + .to_string(); + let args_v = inst + .get("args") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default(); let mut args: Vec = Vec::with_capacity(args_v.len()); for a in args_v { - let id = a.as_u64().ok_or_else(|| "newbox arg must be integer".to_string())? as u32; + let id = a + .as_u64() + .ok_or_else(|| "newbox arg must be integer".to_string())? + as u32; args.push(ValueId::new(id)); } - block_ref.add_instruction(MirInstruction::NewBox { dst: ValueId::new(dst), box_type: ty, args }); + block_ref.add_instruction(MirInstruction::NewBox { + dst: ValueId::new(dst), + box_type: ty, + args, + }); max_value_id = max_value_id.max(dst + 1); } "boxcall" => { // { op:"boxcall", box:, method:"name", args:[vid...], dst?: } let box_id = require_u64(inst, "box", "boxcall box")? as u32; - let method = inst.get("method").and_then(Value::as_str).ok_or_else(|| "boxcall missing method".to_string())?.to_string(); - let dst_opt = inst.get("dst").and_then(Value::as_u64).map(|v| ValueId::new(v as u32)); - let args_v = inst.get("args").and_then(Value::as_array).cloned().unwrap_or_default(); + let method = inst + .get("method") + .and_then(Value::as_str) + .ok_or_else(|| "boxcall missing method".to_string())? + .to_string(); + let dst_opt = inst + .get("dst") + .and_then(Value::as_u64) + .map(|v| ValueId::new(v as u32)); + let args_v = inst + .get("args") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default(); let mut args: Vec = Vec::with_capacity(args_v.len()); for a in args_v { - let id = a.as_u64().ok_or_else(|| "boxcall arg must be integer".to_string())? as u32; + let id = a + .as_u64() + .ok_or_else(|| "boxcall arg must be integer".to_string())? + as u32; args.push(ValueId::new(id)); } block_ref.add_instruction(MirInstruction::BoxCall { @@ -163,7 +255,9 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { args, effects: EffectMask::READ, }); - if let Some(dv) = dst_opt { max_value_id = max_value_id.max(dv.as_u32() + 1); } + if let Some(dv) = dst_opt { + max_value_id = max_value_id.max(dv.as_u32() + 1); + } } other => { return Err(format!("unsupported op '{}' in mir_json_v0 loader", other)); @@ -181,7 +275,9 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result { } fn require_u64(node: &Value, key: &str, context: &str) -> Result { - node.get(key).and_then(Value::as_u64).ok_or_else(|| format!("{} missing field '{}'", context, key)) + node.get(key) + .and_then(Value::as_u64) + .ok_or_else(|| format!("{} missing field '{}'", context, key)) } fn parse_const_value(value_obj: &Value) -> Result { diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 2c6bda3b..8e836593 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -23,18 +23,21 @@ mod demos; mod dispatch; pub mod json_v0_bridge; mod json_v1_bridge; -pub mod mir_json { pub mod common; } -mod mir_json_v0; +pub mod mir_json { + pub mod common; +} +pub mod core_executor; +pub mod hv1_inline; pub mod mir_json_emit; +mod mir_json_v0; pub mod modes; mod pipe_io; -pub mod core_executor; mod pipeline; +mod plugins; mod selfhost; +mod stage1_bridge; mod tasks; mod trace; -mod plugins; -pub mod hv1_inline; // v2 plugin system imports use nyash_rust::runner_plugin_init; @@ -89,27 +92,43 @@ impl NyashRunner { return; } let groups = self.config.as_groups(); + if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) { + std::process::exit(code); + } // Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec. if let Some(path) = groups.parser.mir_json_file.as_ref() { match std::fs::read_to_string(path) { - Ok(text) => { - match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { - Ok(Some(module)) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } - Ok(None) => { - if text.contains("\"functions\"") && text.contains("\"blocks\"") { - match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) { - Ok(module) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } - Err(e) => { eprintln!("❌ MIR JSON v0 parse error: {}", e); std::process::exit(1); } - } - } else { - eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path); - std::process::exit(1); - } - } - Err(e) => { eprintln!("❌ MIR JSON parse error (v1): {}", e); std::process::exit(1); } + Ok(text) => match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { + Ok(Some(module)) => { + let rc = self.execute_mir_module_quiet_exit(&module); + std::process::exit(rc); } + Ok(None) => { + if text.contains("\"functions\"") && text.contains("\"blocks\"") { + match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) { + Ok(module) => { + let rc = self.execute_mir_module_quiet_exit(&module); + std::process::exit(rc); + } + Err(e) => { + eprintln!("❌ MIR JSON v0 parse error: {}", e); + std::process::exit(1); + } + } + } else { + eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path); + std::process::exit(1); + } + } + Err(e) => { + eprintln!("❌ MIR JSON parse error (v1): {}", e); + std::process::exit(1); + } + }, + Err(e) => { + eprintln!("❌ Error reading MIR JSON {}: {}", path, e); + std::process::exit(1); } - Err(e) => { eprintln!("❌ Error reading MIR JSON {}: {}", path, e); std::process::exit(1); } } } // Early: build @@ -123,7 +142,9 @@ impl NyashRunner { // Preprocess usings and directives (includes dep-tree log) self.preprocess_usings_and_directives(&groups); // JSON v0 bridge - if self.try_run_json_v0_pipe() { return; } + if self.try_run_json_v0_pipe() { + return; + } // Named task if let Some(task) = groups.run_task.clone() { if let Err(e) = run_named_task(&task) { @@ -139,7 +160,9 @@ impl NyashRunner { self.configure_backend(&groups); self.enforce_runtime_jit_policy(&groups); // Benchmark - if self.maybe_run_benchmark(&groups) { return; } + if self.maybe_run_benchmark(&groups) { + return; + } // Dispatch if std::env::var("NYASH_VM_ROUTE_TRACE").ok().as_deref() == Some("1") { let backend = &groups.backend.backend; @@ -159,15 +182,26 @@ impl NyashRunner { let mut pending_using: Vec<(String, Option)> = Vec::new(); for spec in &groups.input.cli_usings { let s = spec.trim(); - if s.is_empty() { continue; } + if s.is_empty() { + continue; + } let (target, alias) = if let Some(pos) = s.find(" as ") { - (s[..pos].trim().to_string(), Some(s[pos + 4..].trim().to_string())) - } else { (s.to_string(), None) }; + ( + s[..pos].trim().to_string(), + Some(s[pos + 4..].trim().to_string()), + ) + } else { + (s.to_string(), None) + }; let is_path = crate::runner::modes::common_util::resolve::path_util::is_using_target_path_original(&target); if is_path { let path = target.trim_matches('"').to_string(); let name = alias.clone().unwrap_or_else(|| { - std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string() + std::path::Path::new(&path) + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("module") + .to_string() }); pending_using.push((name, Some(path))); } else { @@ -190,29 +224,51 @@ impl NyashRunner { root_info = format!(" root='{}'", r); } } - crate::cli_v!("[deps] loaded {} bytes from{} {}", bytes, if root_info.is_empty() { "" } else { ":" }, root_info); + crate::cli_v!( + "[deps] loaded {} bytes from{} {}", + bytes, + if root_info.is_empty() { "" } else { ":" }, + root_info + ); + } + Err(e) => { + crate::cli_v!("[deps] read error: {}", e); } - Err(e) => { crate::cli_v!("[deps] read error: {}", e); } } } // If a file is provided, apply script-level directives and late using/env merges if let Some(ref filename) = groups.input.file { if let Ok(code) = fs::read_to_string(filename) { // Apply directives and lint - let strict_fields = std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1"); - if let Err(e) = cli_directives::apply_cli_directives_from_source(&code, strict_fields, groups.debug.cli_verbose) { + let strict_fields = + std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1"); + if let Err(e) = cli_directives::apply_cli_directives_from_source( + &code, + strict_fields, + groups.debug.cli_verbose, + ) { eprintln!("❌ Lint/Directive error: {}", e); std::process::exit(1); } // Late env overrides (paths/modules) if let Ok(paths) = std::env::var("NYASH_USING_PATH") { - for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_ctx.using_paths.push(p.to_string()); } } + for p in paths.split(':') { + let p = p.trim(); + if !p.is_empty() { + using_ctx.using_paths.push(p.to_string()); + } + } } if let Ok(mods) = std::env::var("NYASH_MODULES") { for ent in mods.split(',') { if let Some((k, v)) = ent.split_once('=') { - let k = k.trim(); let v = v.trim(); - if !k.is_empty() && !v.is_empty() { using_ctx.pending_modules.push((k.to_string(), v.to_string())); } + let k = k.trim(); + let v = v.trim(); + if !k.is_empty() && !v.is_empty() { + using_ctx + .pending_modules + .push((k.to_string(), v.to_string())); + } } } } @@ -226,9 +282,22 @@ impl NyashRunner { let verbose = crate::config::env::cli_verbose(); let ctx = std::path::Path::new(filename).parent(); for (ns, alias) in pending_using.iter() { - let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, &using_ctx.packages, ctx, strict, verbose) { + let value = match resolve_using_target( + ns, + false, + &using_ctx.pending_modules, + &using_ctx.using_paths, + &using_ctx.aliases, + &using_ctx.packages, + ctx, + strict, + verbose, + ) { Ok(v) => v, - Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); } + Err(e) => { + eprintln!("❌ using: {}", e); + std::process::exit(1); + } }; let sb = nyash_rust::box_trait::StringBox::new(value.clone()); nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb)); @@ -244,8 +313,14 @@ impl NyashRunner { /// Apply early environment toggles that affect CLI behavior and VM stats. /// Side effects: sets `NYASH_CLI_VERBOSE`, `NYASH_GC_MODE` when specified by CLI groups. fn apply_common_env(&self, groups: &crate::cli::CliGroups) { - if groups.debug.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); } - if let Some(ref m) = groups.gc_mode { if !m.trim().is_empty() { std::env::set_var("NYASH_GC_MODE", m); } } + if groups.debug.cli_verbose { + std::env::set_var("NYASH_CLI_VERBOSE", "1"); + } + if let Some(ref m) = groups.gc_mode { + if !m.trim().is_empty() { + std::env::set_var("NYASH_GC_MODE", m); + } + } } // init_runtime_and_plugins moved to runner/plugins.rs @@ -253,8 +328,12 @@ impl NyashRunner { /// Configure backend knobs (VM) from CLI flags and env. /// JIT configuration removed with JIT/Cranelift archival. fn configure_backend(&self, groups: &crate::cli::CliGroups) { - if groups.backend.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); } - if groups.backend.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); } + if groups.backend.vm_stats { + std::env::set_var("NYASH_VM_STATS", "1"); + } + if groups.backend.vm_stats_json { + std::env::set_var("NYASH_VM_STATS_JSON", "1"); + } // JIT configuration removed - archived to archive/jit-cranelift/ } @@ -272,7 +351,9 @@ impl NyashRunner { println!("Running {} iterations per test...", groups.iterations); println!(); // VM-legacy removed - benchmark mode no longer available - eprintln!("❌ Benchmark mode removed with vm-legacy. Use regular execution modes instead."); + eprintln!( + "❌ Benchmark mode removed with vm-legacy. Use regular execution modes instead." + ); std::process::exit(1); } false @@ -281,7 +362,10 @@ impl NyashRunner { /// Final dispatch to selected execution mode (file/JIT-direct or demos). fn dispatch_entry(&self, groups: &crate::cli::CliGroups) { if let Some(ref filename) = groups.input.file { - if groups.backend.jit.direct { self.run_file_jit_direct(filename); return; } + if groups.backend.jit.direct { + self.run_file_jit_direct(filename); + return; + } // Optional route trace before delegating to backend dispatcher if std::env::var("NYASH_VM_ROUTE_TRACE").ok().as_deref() == Some("1") { eprintln!( @@ -290,7 +374,9 @@ impl NyashRunner { ); } self.run_file(filename); - } else { demos::run_all_demos(); } + } else { + demos::run_all_demos(); + } } } diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index 921c0fa8..fbe10f7a 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -114,7 +114,11 @@ impl NyashRunner { ast } Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + code_ref, + &e, + ); process::exit(1); } }; diff --git a/src/runner/modes/common_util/core_bridge.rs b/src/runner/modes/common_util/core_bridge.rs index 92417cae..4747eef7 100644 --- a/src/runner/modes/common_util/core_bridge.rs +++ b/src/runner/modes/common_util/core_bridge.rs @@ -122,15 +122,22 @@ fn methodize_calls(root: &mut Value) -> Result { for inst in insts.iter() { if let Some(obj) = inst.as_object() { if obj.get("op").and_then(Value::as_str) == Some("const") { - if let (Some(dst), Some(val)) = (obj.get("dst").and_then(Value::as_i64), obj.get("value")) { + if let (Some(dst), Some(val)) = + (obj.get("dst").and_then(Value::as_i64), obj.get("value")) + { let mut s: Option = None; - if let Some(st) = val.as_str() { s = Some(st.to_string()); } - else if let Some(vobj) = val.as_object() { - if let Some(Value::String(st)) = vobj.get("value") { s = Some(st.clone()); } + if let Some(st) = val.as_str() { + s = Some(st.to_string()); + } else if let Some(vobj) = val.as_object() { + if let Some(Value::String(st)) = vobj.get("value") { + s = Some(st.clone()); + } } if let Some(name) = s { // Accept only names with dot separator - if name.contains('.') { reg_name.insert(dst, name); } + if name.contains('.') { + reg_name.insert(dst, name); + } } } } @@ -138,14 +145,28 @@ fn methodize_calls(root: &mut Value) -> Result { } // Second pass: rewrite calls for inst in insts.iter_mut() { - let Some(obj) = inst.as_object_mut() else { continue }; - if obj.get("op").and_then(Value::as_str) != Some("call") { continue; } - let Some(func_reg) = obj.get("func").and_then(Value::as_i64) else { continue }; - let Some(name) = reg_name.get(&func_reg).cloned() else { continue }; + let Some(obj) = inst.as_object_mut() else { + continue; + }; + if obj.get("op").and_then(Value::as_str) != Some("call") { + continue; + } + let Some(func_reg) = obj.get("func").and_then(Value::as_i64) else { + continue; + }; + let Some(name) = reg_name.get(&func_reg).cloned() else { + continue; + }; // Split Box.method[/N] let mut parts = name.split('.'); - let box_name = match parts.next() { Some(x) => x, None => continue }; - let rest = match parts.next() { Some(x) => x, None => continue }; + let box_name = match parts.next() { + Some(x) => x, + None => continue, + }; + let rest = match parts.next() { + Some(x) => x, + None => continue, + }; let method = rest.split('/').next().unwrap_or(rest); // Build mir_call object @@ -163,7 +184,9 @@ fn methodize_calls(root: &mut Value) -> Result { mir_call.insert("effects".to_string(), Value::Array(vec![])); obj.insert("op".to_string(), Value::String("mir_call".into())); - if let Some(d) = dst { obj.insert("dst".to_string(), d); } + if let Some(d) = dst { + obj.insert("dst".to_string(), d); + } obj.remove("func"); obj.insert("mir_call".to_string(), Value::Object(mir_call)); changed = true; diff --git a/src/runner/modes/common_util/diag.rs b/src/runner/modes/common_util/diag.rs index 450e389d..8a3c3101 100644 --- a/src/runner/modes/common_util/diag.rs +++ b/src/runner/modes/common_util/diag.rs @@ -1,5 +1,8 @@ //! Common diagnostics helpers (concise, centralized) +use crate::parser::ParseError; +use crate::tokenizer::TokenizeError; + /// Whether provider logs should be emitted under current policy. /// quiet_pipe usually reflects NYASH_JSON_ONLY; allowing override with HAKO_PROVIDER_TRACE=1. pub fn provider_log_enabled(quiet_pipe: bool) -> bool { @@ -15,14 +18,102 @@ pub fn provider_log_info(msg: &str) { pub fn provider_log_select(box_name: &str, ring: &str, source: &str, caps: Option<&str>) { match caps { Some(c) if !c.is_empty() => { - eprintln!("[provider/select:{} ring={} src={} caps={}]", box_name, ring, source, c); + eprintln!( + "[provider/select:{} ring={} src={} caps={}]", + box_name, ring, source, c + ); } _ => { - eprintln!("[provider/select:{} ring={} src={}]", box_name, ring, source); + eprintln!( + "[provider/select:{} ring={} src={}]", + box_name, ring, source + ); } } } /// Emit a Fail-Fast tag for provider fallback/selection errors. -pub fn failfast_provider(reason: &str) { eprintln!("[failfast/provider/{}]", reason); } +pub fn failfast_provider(reason: &str) { + eprintln!("[failfast/provider/{}]", reason); +} +/// Print a parse error with enriched context (source excerpt + caret + origin mapping). +pub fn print_parse_error_with_context(filename: &str, src: &str, err: &ParseError) { + eprintln!("❌ Parse error in {}: {}", filename, err); + + let (line_opt, col_opt) = extract_line_col(err); + if let Some(line) = line_opt { + print_source_snippet(filename, src, line, col_opt); + + if let Some((of, ol)) = + crate::runner::modes::common_util::resolve::map_merged_line_to_origin(line) + { + if of != filename { + eprintln!( + "[parse/context] merged origin: {}:{} (from merged line {})", + of, ol, line + ); + } + } + } +} + +fn extract_line_col(err: &ParseError) -> (Option, Option) { + match err { + ParseError::UnexpectedToken { line, .. } => (Some(*line), None), + ParseError::UnexpectedEOF => (None, None), + ParseError::InvalidExpression { line } => (Some(*line), None), + ParseError::InvalidStatement { line } => (Some(*line), None), + ParseError::UnsupportedIdentifier { line, .. } => (Some(*line), None), + ParseError::CircularDependency { .. } => (None, None), + ParseError::InfiniteLoop { line, .. } => (Some(*line), None), + ParseError::TransparencySystemRemoved { line, .. } => (Some(*line), None), + ParseError::UnsupportedNamespace { line, .. } => (Some(*line), None), + ParseError::ExpectedIdentifier { line } => (Some(*line), None), + ParseError::TokenizeError(te) => match te { + TokenizeError::UnexpectedCharacter { line, column, .. } => { + (Some(*line), Some(*column)) + } + TokenizeError::UnterminatedString { line } + | TokenizeError::InvalidNumber { line } + | TokenizeError::UnterminatedComment { line } => (Some(*line), None), + }, + } +} + +fn print_source_snippet(filename: &str, src: &str, line: usize, col: Option) { + if src.is_empty() { + return; + } + let lines: Vec<&str> = src.lines().collect(); + if line == 0 || line > lines.len() { + return; + } + let start = line.saturating_sub(2).max(1); + let end = (line + 1).min(lines.len()); + + eprintln!("[parse/context] in {}", filename); + for ln in start..=end { + let text = lines[ln - 1]; + let marker = if ln == line { ">" } else { " " }; + eprintln!("{} {:6} | {}", marker, ln, text); + } + + if let Some(col) = col { + if line <= lines.len() { + let text = lines[line - 1]; + let mut underline = String::new(); + let mut idx = 0usize; + for (i, ch) in text.chars().enumerate() { + if i + 1 >= col { + break; + } + // Preserve tabs visually; spaces elsewhere + underline.push(if ch == '\t' { '\t' } else { ' ' }); + idx = i; + } + let pad = " "; // align under " LNNNNN |" + eprintln!(" {}{}^", pad, underline); + } + } +} diff --git a/src/runner/modes/common_util/exec.rs b/src/runner/modes/common_util/exec.rs index ce4d70f3..78fc753f 100644 --- a/src/runner/modes/common_util/exec.rs +++ b/src/runner/modes/common_util/exec.rs @@ -52,11 +52,7 @@ pub fn llvmlite_emit_object( // Verify output match std::fs::metadata(out_path) { Ok(meta) if meta.len() > 0 => { - crate::cli_v!( - "[LLVM] object emitted: {} ({} bytes)", - out_path, - meta.len() - ); + crate::cli_v!("[LLVM] object emitted: {} ({} bytes)", out_path, meta.len()); Ok(()) } _ => Err(format!("harness output not found or empty: {}", out_path)), @@ -67,7 +63,13 @@ pub fn llvmlite_emit_object( fn resolve_ny_llvmc() -> std::path::PathBuf { std::env::var("NYASH_NY_LLVM_COMPILER") .ok() - .and_then(|s| if !s.is_empty() { Some(std::path::PathBuf::from(s)) } else { None }) + .and_then(|s| { + if !s.is_empty() { + Some(std::path::PathBuf::from(s)) + } else { + None + } + }) .or_else(|| which::which("ny-llvmc").ok()) .unwrap_or_else(|| std::path::PathBuf::from("target/release/ny-llvmc")) } @@ -103,9 +105,24 @@ pub fn ny_llvmc_emit_exe_lib( .arg("exe") .arg("--out") .arg(exe_out); - let default_nyrt = std::env::var("NYASH_EMIT_EXE_NYRT") .ok() .or_else(|| std::env::var("NYASH_ROOT").ok().map(|r| format!("{}/target/release", r))) .unwrap_or_else(|| "target/release".to_string()); - if let Some(dir) = nyrt_dir { cmd.arg("--nyrt").arg(dir); } else { cmd.arg("--nyrt").arg(default_nyrt); } - if let Some(flags) = extra_libs { if !flags.trim().is_empty() { cmd.arg("--libs").arg(flags); } } + let default_nyrt = std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .or_else(|| { + std::env::var("NYASH_ROOT") + .ok() + .map(|r| format!("{}/target/release", r)) + }) + .unwrap_or_else(|| "target/release".to_string()); + if let Some(dir) = nyrt_dir { + cmd.arg("--nyrt").arg(dir); + } else { + cmd.arg("--nyrt").arg(default_nyrt); + } + if let Some(flags) = extra_libs { + if !flags.trim().is_empty() { + cmd.arg("--libs").arg(flags); + } + } let status = cmd.status().map_err(|e| { let prog_path = std::path::Path::new(cmd.get_program()); format!( @@ -147,9 +164,24 @@ pub fn ny_llvmc_emit_exe_bin( .arg("exe") .arg("--out") .arg(exe_out); - let default_nyrt = std::env::var("NYASH_EMIT_EXE_NYRT") .ok() .or_else(|| std::env::var("NYASH_ROOT").ok().map(|r| format!("{}/target/release", r))) .unwrap_or_else(|| "target/release".to_string()); - if let Some(dir) = nyrt_dir { cmd.arg("--nyrt").arg(dir); } else { cmd.arg("--nyrt").arg(default_nyrt); } - if let Some(flags) = extra_libs { if !flags.trim().is_empty() { cmd.arg("--libs").arg(flags); } } + let default_nyrt = std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .or_else(|| { + std::env::var("NYASH_ROOT") + .ok() + .map(|r| format!("{}/target/release", r)) + }) + .unwrap_or_else(|| "target/release".to_string()); + if let Some(dir) = nyrt_dir { + cmd.arg("--nyrt").arg(dir); + } else { + cmd.arg("--nyrt").arg(default_nyrt); + } + if let Some(flags) = extra_libs { + if !flags.trim().is_empty() { + cmd.arg("--libs").arg(flags); + } + } let status = cmd.status().map_err(|e| { let prog_path = std::path::Path::new(cmd.get_program()); format!( @@ -176,9 +208,11 @@ pub fn run_executable( timeout_ms: u64, ) -> Result<(i32, bool, String), String> { let mut cmd = std::process::Command::new(exe_path); - for a in args { cmd.arg(a); } - let out = super::io::spawn_with_timeout(cmd, timeout_ms) - .map_err(|e| format!("spawn exe: {}", e))?; + for a in args { + cmd.arg(a); + } + let out = + super::io::spawn_with_timeout(cmd, timeout_ms).map_err(|e| format!("spawn exe: {}", e))?; let code = out.exit_code.unwrap_or(1); let stdout_text = String::from_utf8_lossy(&out.stdout).into_owned(); Ok((code, out.timed_out, stdout_text)) diff --git a/src/runner/modes/common_util/hako.rs b/src/runner/modes/common_util/hako.rs index f40ffba5..5ac2140c 100644 --- a/src/runner/modes/common_util/hako.rs +++ b/src/runner/modes/common_util/hako.rs @@ -22,13 +22,15 @@ pub fn strip_local_decl(s: &str) -> String { for line in s.lines() { let bytes = line.as_bytes(); let mut i = 0; - while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { i += 1; } + while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { + i += 1; + } let mut stripped = false; // Only strip `local ` if it's at the very beginning (i == 0) // Keep `local ` inside blocks (i > 0) to preserve variable declarations - if i == 0 && i + 6 <= bytes.len() && &bytes[i..i+6] == b"local " { + if i == 0 && i + 6 <= bytes.len() && &bytes[i..i + 6] == b"local " { out.push_str(&line[..i]); - out.push_str(&line[i+6..]); + out.push_str(&line[i + 6..]); out.push('\n'); stripped = true; } @@ -45,7 +47,10 @@ pub fn strip_local_decl(s: &str) -> String { pub fn fail_fast_on_hako() -> bool { // Default: OFF(仕様不変=拡張子だけで拒否しない)。 // 明示時のみ ON(bring-up やデバッグ用途)。 - match std::env::var("HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM").ok().as_deref() { + match std::env::var("HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM") + .ok() + .as_deref() + { Some("1") | Some("true") | Some("on") => true, _ => false, } diff --git a/src/runner/modes/common_util/io.rs b/src/runner/modes/common_util/io.rs index ff8afcc8..ddcb95ce 100644 --- a/src/runner/modes/common_util/io.rs +++ b/src/runner/modes/common_util/io.rs @@ -24,7 +24,10 @@ pub fn spawn_with_timeout(mut cmd: Command, timeout_ms: u64) -> std::io::Result< let mut exit_status: Option = None; loop { match child.try_wait()? { - Some(status) => { exit_status = Some(status); break }, + Some(status) => { + exit_status = Some(status); + break; + } None => { if start.elapsed() >= Duration::from_millis(timeout_ms) { let _ = child.kill(); @@ -46,7 +49,9 @@ pub fn spawn_with_timeout(mut cmd: Command, timeout_ms: u64) -> std::io::Result< } let (status_ok, exit_code) = if let Some(st) = exit_status { (st.success(), st.code()) - } else { (false, None) }; + } else { + (false, None) + }; Ok(ChildOutput { stdout: out_buf, stderr: err_buf, diff --git a/src/runner/modes/common_util/mod.rs b/src/runner/modes/common_util/mod.rs index 22d5957e..bc6355c9 100644 --- a/src/runner/modes/common_util/mod.rs +++ b/src/runner/modes/common_util/mod.rs @@ -4,14 +4,14 @@ * Minimal extraction to reduce duplication and prepare for full split. */ -pub mod pyvm; -pub mod selfhost_exe; -pub mod io; -pub mod selfhost; -pub mod resolve; -pub mod exec; pub mod core_bridge; +pub mod diag; +pub mod exec; pub mod hako; +pub mod io; pub mod plugin_guard; pub mod provider_registry; -pub mod diag; +pub mod pyvm; +pub mod resolve; +pub mod selfhost; +pub mod selfhost_exe; diff --git a/src/runner/modes/common_util/provider_registry.rs b/src/runner/modes/common_util/provider_registry.rs index ca746815..20983222 100644 --- a/src/runner/modes/common_util/provider_registry.rs +++ b/src/runner/modes/common_util/provider_registry.rs @@ -31,8 +31,7 @@ static PROVIDER_FACTORIES: OnceLock) { - let registry = PROVIDER_FACTORIES - .get_or_init(|| Mutex::new(HashMap::new())); + let registry = PROVIDER_FACTORIES.get_or_init(|| Mutex::new(HashMap::new())); let mut guard = registry.lock().unwrap(); let key = factory.box_name().to_string(); guard.entry(key).or_default().push(factory); @@ -42,16 +41,23 @@ pub fn register_provider_factory(factory: Arc) { struct CoreRoFileProviderFactory; impl ProviderFactory for CoreRoFileProviderFactory { - fn box_name(&self) -> &str { "FileBox" } - fn create_provider(&self) -> Arc { Arc::new(CoreRoFileIo::new()) } - fn is_available(&self) -> bool { true } - fn priority(&self) -> i32 { -100 } // ring‑1: lower than any plugin/provider + fn box_name(&self) -> &str { + "FileBox" + } + fn create_provider(&self) -> Arc { + Arc::new(CoreRoFileIo::new()) + } + fn is_available(&self) -> bool { + true + } + fn priority(&self) -> i32 { + -100 + } // ring‑1: lower than any plugin/provider } /// Ensure ring‑1 (core‑ro) provider is present in the registry fn ensure_builtin_file_provider_registered() { - let reg = PROVIDER_FACTORIES - .get_or_init(|| Mutex::new(HashMap::new())); + let reg = PROVIDER_FACTORIES.get_or_init(|| Mutex::new(HashMap::new())); let mut guard = reg.lock().unwrap(); let list = guard.entry("FileBox".to_string()).or_default(); // keep ring‑1 present for safety; avoid duplicates by checking any core‑ro present by priority @@ -63,7 +69,9 @@ fn ensure_builtin_file_provider_registered() { /// Backward-compat public readers for existing callers (if any) #[allow(dead_code)] -pub fn read_filebox_mode_from_env() -> FileBoxMode { provider_env::filebox_mode_from_env() } +pub fn read_filebox_mode_from_env() -> FileBoxMode { + provider_env::filebox_mode_from_env() +} /// Select provider based on mode and registered factories (SSOT) #[allow(dead_code)] @@ -93,7 +101,10 @@ pub fn select_file_provider(mode: FileBoxMode) -> Arc { ProviderPolicy::StrictPluginFirst => { if let Some(factory) = factories.first() { if diag::provider_log_enabled(quiet_pipe) { - diag::provider_log_info(&format!("FileBox: using registered provider (priority={})", factory.priority())); + diag::provider_log_info(&format!( + "FileBox: using registered provider (priority={})", + factory.priority() + )); diag::provider_log_select("FileBox", "plugin", "dynamic", None); } return factory.create_provider(); @@ -111,7 +122,10 @@ pub fn select_file_provider(mode: FileBoxMode) -> Arc { // Fallback to first available (plugin) if let Some(factory) = factories.first() { if diag::provider_log_enabled(quiet_pipe) { - diag::provider_log_info(&format!("FileBox: using registered provider (priority={})", factory.priority())); + diag::provider_log_info(&format!( + "FileBox: using registered provider (priority={})", + factory.priority() + )); diag::provider_log_select("FileBox", "plugin", "dynamic", None); } return factory.create_provider(); @@ -155,7 +169,10 @@ pub fn select_file_provider(mode: FileBoxMode) -> Arc { if let Some(factory) = factories.first() { if diag::provider_log_enabled(quiet_pipe) { - diag::provider_log_info(&format!("FileBox: using plugin-only provider (priority={})", factory.priority())); + diag::provider_log_info(&format!( + "FileBox: using plugin-only provider (priority={})", + factory.priority() + )); diag::provider_log_select("FileBox", "plugin", "dynamic", None); } return factory.create_provider(); @@ -179,8 +196,8 @@ pub fn select_file_provider(mode: FileBoxMode) -> Arc { #[derive(Clone, Debug)] struct ProviderDescriptor { box_name: &'static str, - ring: &'static str, // "0" | "1" | "plugin" - source: &'static str, // "static" | "dynamic" + ring: &'static str, // "0" | "1" | "plugin" + source: &'static str, // "static" | "dynamic" capabilities: &'static [&'static str], // e.g., ["read"] priority: i32, } diff --git a/src/runner/modes/common_util/pyvm.rs b/src/runner/modes/common_util/pyvm.rs index f8caf972..97079ca5 100644 --- a/src/runner/modes/common_util/pyvm.rs +++ b/src/runner/modes/common_util/pyvm.rs @@ -9,18 +9,26 @@ pub fn run_pyvm_harness(module: &crate::mir::MirModule, tag: &str) -> Result R if !runner_buf.exists() { if let Ok(root) = std::env::var("NYASH_ROOT") { let alt = std::path::Path::new(&root).join("tools/pyvm_runner.py"); - if alt.exists() { runner_buf = alt; } + if alt.exists() { + runner_buf = alt; + } } } if !runner_buf.exists() { - return Err(format!("PyVM runner not found: tools/pyvm_runner.py (cwd) or $NYASH_ROOT/tools/pyvm_runner.py")); + return Err(format!( + "PyVM runner not found: tools/pyvm_runner.py (cwd) or $NYASH_ROOT/tools/pyvm_runner.py" + )); } let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); crate::runner::mir_json_emit::emit_mir_json_for_harness(module, &mir_json_path) .map_err(|e| format!("PyVM MIR JSON emit error: {}", e))?; - crate::cli_v!("[Runner] using PyVM ({} ) → {}", tag, mir_json_path.display()); + crate::cli_v!( + "[Runner] using PyVM ({} ) → {}", + tag, + mir_json_path.display() + ); // Determine entry function (prefer Main.main; top-level main only if allowed) let allow_top = crate::config::env::entry_allow_toplevel_main(); let entry = if module.functions.contains_key("Main.main") { diff --git a/src/runner/modes/common_util/resolve/context.rs b/src/runner/modes/common_util/resolve/context.rs index ad93ddec..6edb3d4a 100644 --- a/src/runner/modes/common_util/resolve/context.rs +++ b/src/runner/modes/common_util/resolve/context.rs @@ -1,8 +1,19 @@ //! Resolve context — capture per-thread prelude merge context for enriched diagnostics. use std::cell::RefCell; +/// Line span mapping for merged prelude+main sources. +/// Represents that lines [start_line, start_line + line_count) in the merged +/// text originate from `file` at local lines [1, line_count]. +#[derive(Clone, Debug)] +pub struct LineSpan { + pub file: String, + pub start_line: usize, + pub line_count: usize, +} + thread_local! { static LAST_MERGED_PRELUDES: RefCell> = RefCell::new(Vec::new()); + static LAST_TEXT_MERGE_LINE_SPANS: RefCell> = RefCell::new(Vec::new()); } /// Record the list of prelude file paths used for the last text merge in this thread. @@ -23,3 +34,25 @@ pub fn take_last_merged_preludes() -> Vec { LAST_MERGED_PRELUDES.with(|c| std::mem::take(&mut *c.borrow_mut())) } +/// Record the line-span mapping for the last text merge in this thread. +pub fn set_last_text_merge_line_spans(spans: Vec) { + LAST_TEXT_MERGE_LINE_SPANS.with(|c| { + *c.borrow_mut() = spans; + }); +} + +/// Try to map a merged (global) line number back to its origin file and local line. +pub fn map_merged_line_to_origin(line: usize) -> Option<(String, usize)> { + if line == 0 { + return None; + } + LAST_TEXT_MERGE_LINE_SPANS.with(|c| { + for span in c.borrow().iter() { + if line >= span.start_line && line < span.start_line + span.line_count { + let local = line - span.start_line + 1; + return Some((span.file.clone(), local)); + } + } + None + }) +} diff --git a/src/runner/modes/common_util/resolve/mod.rs b/src/runner/modes/common_util/resolve/mod.rs index 8cefbc54..2e909aed 100644 --- a/src/runner/modes/common_util/resolve/mod.rs +++ b/src/runner/modes/common_util/resolve/mod.rs @@ -1,6 +1,6 @@ /*! * Using resolver utilities — static resolution line (SSOT + AST) 📦 - * + * * 箱化モジュール化で綺麗綺麗になったにゃ!🎉 * * Separation of concerns: @@ -19,45 +19,29 @@ * - seam: seam logging and optional boundary markers (for diagnostics). */ -pub mod strip; -pub mod seam; -pub mod using_resolution; -pub mod prelude_manager; -pub mod selfhost_pipeline; -pub mod path_util; pub mod context; +pub mod path_util; +pub mod prelude_manager; +pub mod seam; +pub mod selfhost_pipeline; +pub mod strip; +pub mod using_resolution; // 📦 箱化モジュールの公開にゃ! -pub use using_resolution::{ - UsingResolutionBox, - UsingTarget, - UsingConfig, -}; +pub use using_resolution::{UsingConfig, UsingResolutionBox, UsingTarget}; -pub use prelude_manager::{ - PreludeManagerBox, - MergeStrategy, - MergeResult, -}; +pub use prelude_manager::{MergeResult, MergeStrategy, PreludeManagerBox}; -pub use selfhost_pipeline::{ - SelfhostPipelineBox, - CompilationResult, - PipelineConfig, -}; +pub use selfhost_pipeline::{CompilationResult, PipelineConfig, SelfhostPipelineBox}; // 🔧 Legacy functions (preserved for compatibility) pub use strip::{ - preexpand_at_local, - collect_using_and_strip, - resolve_prelude_paths_profiled, - parse_preludes_to_asts, - merge_prelude_asts_with_main, - merge_prelude_text, + collect_using_and_strip, merge_prelude_asts_with_main, merge_prelude_text, + parse_preludes_to_asts, preexpand_at_local, resolve_prelude_paths_profiled, }; // Expose context helpers for enhanced diagnostics pub use context::{ - set_last_merged_preludes, - clone_last_merged_preludes, + clone_last_merged_preludes, map_merged_line_to_origin, set_last_merged_preludes, + set_last_text_merge_line_spans, LineSpan, }; diff --git a/src/runner/modes/common_util/resolve/path_util.rs b/src/runner/modes/common_util/resolve/path_util.rs index d8f4ed1e..d9b80622 100644 --- a/src/runner/modes/common_util/resolve/path_util.rs +++ b/src/runner/modes/common_util/resolve/path_util.rs @@ -17,4 +17,3 @@ pub fn is_using_target_path_unquoted(target_unquoted: &str) -> bool { || target_unquoted.ends_with(".hako") || target_unquoted.ends_with(".nyash") } - diff --git a/src/runner/modes/common_util/resolve/prelude_manager.rs b/src/runner/modes/common_util/resolve/prelude_manager.rs index bed6f3fa..781f59e9 100644 --- a/src/runner/modes/common_util/resolve/prelude_manager.rs +++ b/src/runner/modes/common_util/resolve/prelude_manager.rs @@ -1,13 +1,13 @@ //! Prelude Manager Box - 綺麗綺麗なプレリュード統合専門家!📦 -//! +//! //! テキストマージとASTマージを分離して、 //! 保守性とテスト容易性を向上させるにゃ! -use crate::runner::NyashRunner; use crate::runner::modes::common_util::resolve::using_resolution::UsingResolutionBox; +use crate::runner::NyashRunner; /// 📦 PreludeManagerBox - プレリュード統合の専門家! -/// +/// /// テキストベースとASTベースの両方の統合を /// 統一インターフェースで提供する箱にゃ! pub struct PreludeManagerBox<'a> { @@ -111,11 +111,13 @@ impl<'a> PreludeManagerBox<'a> { fn build_text_merged( &self, source: &str, - _filename: &str, + filename: &str, prelude_paths: &[String], trace: bool, ) -> Result { let mut merged = String::new(); + let mut spans: Vec = Vec::new(); + let mut current_line: usize = 1; // プレリュードをDFS順に追加 for (idx, path) in prelude_paths.iter().enumerate() { @@ -138,16 +140,44 @@ impl<'a> PreludeManagerBox<'a> { merged.push_str(&cleaned); merged.push('\n'); + let added = cleaned.lines().count(); + if added > 0 { + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: path.clone(), + start_line: current_line, + line_count: added, + }); + current_line += added + 1; // +1 for the extra '\n' + } else { + current_line += 1; + } } // デバッグモードなら境界マーカーを追加 if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") { merged.push_str("\n/* --- using prelude/main boundary --- */\n\n"); + // boundary line(s) are attributed to a synthetic "" pseudo-file + let boundary_lines = 3usize; + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: "".to_string(), + start_line: current_line, + line_count: boundary_lines, + }); + current_line += boundary_lines; } // メインソースを正規化して追加 let cleaned_main = self.normalize_text_for_inline(source); merged.push_str(&cleaned_main); + let main_lines = cleaned_main.lines().count(); + if main_lines > 0 { + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: filename.to_string(), + start_line: current_line, + line_count: main_lines, + }); + current_line += main_lines; + } if trace { crate::runner::trace::log(format!( @@ -158,6 +188,8 @@ impl<'a> PreludeManagerBox<'a> { )); } + crate::runner::modes::common_util::resolve::set_last_text_merge_line_spans(spans); + Ok(self.normalize_text_for_inline(&merged)) } @@ -169,34 +201,35 @@ impl<'a> PreludeManagerBox<'a> { ) -> Result<(String, Vec), String> { // 既存のcollect_using_and_strip関数を呼び出す // TODO: 将来的にはUsingResolutionBox経由に置き換える - let (cleaned, prelude_paths, _imports) = crate::runner::modes::common_util::resolve::strip::collect_using_and_strip( - &self.runner, - code, - filename, - )?; + let (cleaned, prelude_paths, _imports) = + crate::runner::modes::common_util::resolve::strip::collect_using_and_strip( + &self.runner, + code, + filename, + )?; Ok((cleaned, prelude_paths)) } /// 🔧 テキストを正規化するにゃ! fn normalize_text_for_inline(&self, s: &str) -> String { let mut out = s.replace("\r\n", "\n").replace("\r", "\n"); - + // `}` の前の `;` を除去(複数回パス) for _ in 0..2 { let mut tmp = String::with_capacity(out.len()); let bytes = out.as_bytes(); let mut i = 0usize; - + while i < bytes.len() { if bytes[i] == b';' { // 先読みしてスペース/改行をスキップ let mut j = i + 1; while j < bytes.len() { let c = bytes[j]; - if c == b' ' || c == b'\t' || c == b'\n' { - j += 1; - } else { - break; + if c == b' ' || c == b'\t' || c == b'\n' { + j += 1; + } else { + break; } } if j < bytes.len() && bytes[j] == b'}' { @@ -210,12 +243,12 @@ impl<'a> PreludeManagerBox<'a> { } out = tmp; } - + // ファイル末尾に改行を追加 - if !out.ends_with('\n') { - out.push('\n'); + if !out.ends_with('\n') { + out.push('\n'); } - + out } @@ -243,7 +276,7 @@ impl<'a> PreludeManagerBox<'a> { prelude_paths: &[String], ) -> Result { let strategy = self.select_strategy(prelude_paths.len()); - + match strategy { MergeStrategy::Text => self.merge_text(source, filename, prelude_paths), MergeStrategy::Ast => self.merge_ast(source, filename, prelude_paths), diff --git a/src/runner/modes/common_util/resolve/seam.rs b/src/runner/modes/common_util/resolve/seam.rs index bf7145cd..7de69459 100644 --- a/src/runner/modes/common_util/resolve/seam.rs +++ b/src/runner/modes/common_util/resolve/seam.rs @@ -1,6 +1,8 @@ /// Log tail of inlined prelude chunk for seam inspection. pub fn log_inlined_tail(path_key: &str, inlined_text: &str, seam_dbg: bool) { - if !seam_dbg { return; } + if !seam_dbg { + return; + } let tail = inlined_text .chars() .rev() @@ -18,7 +20,9 @@ pub fn log_inlined_tail(path_key: &str, inlined_text: &str, seam_dbg: bool) { /// Log the seam between prelude and body for quick visual diff. pub fn log_prelude_body_seam(prelude_clean: &str, body: &str, seam_dbg: bool) { - if !seam_dbg { return; } + if !seam_dbg { + return; + } let tail = prelude_clean .chars() .rev() @@ -28,8 +32,14 @@ pub fn log_prelude_body_seam(prelude_clean: &str, body: &str, seam_dbg: bool) { .rev() .collect::(); let head = body.chars().take(160).collect::(); - eprintln!("[using][seam] prelude_tail=<<<{}>>>", tail.replace('\n', "\\n")); - eprintln!("[using][seam] body_head =<<<{}>>>", head.replace('\n', "\\n")); + eprintln!( + "[using][seam] prelude_tail=<<<{}>>>", + tail.replace('\n', "\\n") + ); + eprintln!( + "[using][seam] body_head =<<<{}>>>", + head.replace('\n', "\\n") + ); } // Legacy brace fix function removed (Phase 15 cleanup) diff --git a/src/runner/modes/common_util/resolve/selfhost_pipeline.rs b/src/runner/modes/common_util/resolve/selfhost_pipeline.rs index 9f1e2477..4c2f9b1a 100644 --- a/src/runner/modes/common_util/resolve/selfhost_pipeline.rs +++ b/src/runner/modes/common_util/resolve/selfhost_pipeline.rs @@ -1,13 +1,15 @@ //! Selfhost Pipeline Box - 綺麗綺麗なセルフホストパイプライン専門家!📦 -//! +//! //! セルフホストコンパイルの複雑な処理を箱に閉じ込めて、 //! 保守性とテスト容易性を向上させるにゃ! +use crate::runner::modes::common_util::resolve::prelude_manager::{ + MergeStrategy, PreludeManagerBox, +}; use crate::runner::NyashRunner; -use crate::runner::modes::common_util::resolve::prelude_manager::{PreludeManagerBox, MergeStrategy}; /// 📦 SelfhostPipelineBox - セルフホストパイプラインの専門家! -/// +/// /// コンパイラーパイプライン全体を管理する箱にゃ! pub struct SelfhostPipelineBox<'a> { runner: &'a NyashRunner, @@ -37,7 +39,7 @@ impl<'a> SelfhostPipelineBox<'a> { /// 🌟 新しいSelfhostPipelineBoxを作るにゃ! pub fn new(runner: &'a NyashRunner) -> Self { let prelude_manager = PreludeManagerBox::new(runner); - + Self { runner, prelude_manager, @@ -66,12 +68,14 @@ impl<'a> SelfhostPipelineBox<'a> { // 第1フェーズ:using文解析とプレリュードパス収集 let (cleaned_main, prelude_paths) = self.collect_and_resolve_using(code, filename)?; - + // 第2フェーズ:プレリュード統合 let merge_result = if config.enable_ast_merge { - self.prelude_manager.merge_ast(&cleaned_main, filename, &prelude_paths)? + self.prelude_manager + .merge_ast(&cleaned_main, filename, &prelude_paths)? } else { - self.prelude_manager.merge_text(&cleaned_main, filename, &prelude_paths)? + self.prelude_manager + .merge_text(&cleaned_main, filename, &prelude_paths)? }; let processing_time = start_time.elapsed().as_millis() as u64; @@ -118,9 +122,7 @@ impl<'a> SelfhostPipelineBox<'a> { eprintln!( "[selfhost-pipeline] ✅ Completed in {}ms (strategy: {}, preludes: {})", - result.processing_time_ms, - strategy_str, - result.prelude_count + result.processing_time_ms, strategy_str, result.prelude_count ); } @@ -146,14 +148,15 @@ impl<'a> SelfhostPipelineBox<'a> { /// 🧪 パイプラインを検証するにゃ!(テスト用) pub fn validate_pipeline(&self, code: &str, filename: &str) -> Result, String> { let mut issues = Vec::new(); - + // usingシステムの検証 if crate::config::env::enable_using() { // using文があるかチェック - let using_count = code.lines() + let using_count = code + .lines() .filter(|line| line.trim().starts_with("using ")) .count(); - + if using_count > 0 { // プレリュード解決を試みる match crate::runner::modes::common_util::resolve::strip::resolve_prelude_paths_profiled( @@ -177,11 +180,7 @@ impl<'a> SelfhostPipelineBox<'a> { } /// 📊 パフォーマンスプロファイリングするにゃ! - pub fn profile_pipeline( - &mut self, - _code: &str, - _filename: &str, - ) -> Result { + pub fn profile_pipeline(&mut self, _code: &str, _filename: &str) -> Result { // プロファイル機能を実装(別途) // TODO: プロファイル機能を追加 Err("Profiling not yet implemented".to_string()) diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index 70e5730e..fd82fabb 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -14,14 +14,26 @@ pub fn collect_using_and_strip( runner: &NyashRunner, code: &str, filename: &str, -) -> Result<(String, Vec, std::collections::HashMap), String> { +) -> Result< + ( + String, + Vec, + std::collections::HashMap, + ), + String, +> { if !crate::config::env::enable_using() { - return Ok((code.to_string(), Vec::new(), std::collections::HashMap::new())); + return Ok(( + code.to_string(), + Vec::new(), + std::collections::HashMap::new(), + )); } let using_ctx = runner.init_using_context(); let prod = crate::config::env::using_is_prod(); let strict = crate::config::env::env_bool("NYASH_USING_STRICT"); - let verbose = crate::config::env::cli_verbose() || crate::config::env::env_bool("NYASH_RESOLVE_TRACE"); + let verbose = + crate::config::env::cli_verbose() || crate::config::env::env_bool("NYASH_RESOLVE_TRACE"); let ctx_dir = std::path::Path::new(filename).parent(); let mut out = String::with_capacity(code.len()); @@ -30,8 +42,8 @@ pub fn collect_using_and_strip( use std::collections::HashMap; let mut seen_paths: HashMap = HashMap::new(); // canon_path -> (alias/label, first_line) let mut seen_aliases: HashMap = HashMap::new(); // alias -> (canon_path, first_line) - // Determine if this file is inside a declared package root; if so, allow - // internal file-using within the package even when file-using is globally disallowed. + // Determine if this file is inside a declared package root; if so, allow + // internal file-using within the package even when file-using is globally disallowed. let filename_canon = std::fs::canonicalize(filename).ok(); let mut inside_pkg = false; if let Some(ref fc) = filename_canon { @@ -66,7 +78,10 @@ pub fn collect_using_and_strip( // Check if this is a known alias or module FIRST before treating as file path let is_known_alias_or_module = using_ctx.aliases.contains_key(&target_unquoted) - || using_ctx.pending_modules.iter().any(|(k, _)| k == &target_unquoted) + || using_ctx + .pending_modules + .iter() + .any(|(k, _)| k == &target_unquoted) || using_ctx.packages.contains_key(&target_unquoted); let is_path = if is_known_alias_or_module { @@ -74,7 +89,9 @@ pub fn collect_using_and_strip( false } else { // SSOT: delegate path pattern check - crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(&target_unquoted) + crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted( + &target_unquoted, + ) }; if is_path { // SSOT: Disallow file-using at top-level; allow only for sources located @@ -145,7 +162,13 @@ pub fn collect_using_and_strip( prev_line )); } else { - seen_paths.insert(canon.clone(), (alias_name.clone().unwrap_or_else(|| "".into()), line_no)); + seen_paths.insert( + canon.clone(), + ( + alias_name.clone().unwrap_or_else(|| "".into()), + line_no, + ), + ); } if let Some(alias) = alias_name.clone() { if let Some((prev_path, prev_line)) = seen_aliases.get(&alias) { @@ -184,7 +207,9 @@ pub fn collect_using_and_strip( strict, verbose, ) { - if resolved.starts_with("dylib:") { continue; } + if resolved.starts_with("dylib:") { + continue; + } let canon = std::fs::canonicalize(&resolved) .ok() .map(|pb| pb.to_string_lossy().to_string()) @@ -197,7 +222,10 @@ pub fn collect_using_and_strip( } else { seen_paths.insert( canon.clone(), - (alias_name.clone().unwrap_or_else(|| "".into()), line_no), + ( + alias_name.clone().unwrap_or_else(|| "".into()), + line_no, + ), ); } if let Some(alias) = alias_name.clone() { @@ -217,10 +245,8 @@ pub fn collect_using_and_strip( } // 1) modules mapping (name -> path) - if let Some((_, mod_path)) = using_ctx - .pending_modules - .iter() - .find(|(n, _)| n == &name) + if let Some((_, mod_path)) = + using_ctx.pending_modules.iter().find(|(n, _)| n == &name) { let out_path = mod_path.clone(); // Duplicate detection (same semantics as packages below) @@ -236,7 +262,10 @@ pub fn collect_using_and_strip( } else { seen_paths.insert( canon.clone(), - (alias_name.clone().unwrap_or_else(|| "".into()), line_no), + ( + alias_name.clone().unwrap_or_else(|| "".into()), + line_no, + ), ); } if let Some(alias) = alias_name.clone() { @@ -263,21 +292,25 @@ pub fn collect_using_and_strip( PackageKind::Package => { let base = std::path::Path::new(&pkg.path); let out = if let Some(m) = &pkg.main { - if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { pkg.path.clone() } else { base.join(m).to_string_lossy().to_string() } - } else if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + } else if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { pkg.path.clone() } else { - let leaf = base - .file_name() - .and_then(|s| s.to_str()) - .unwrap_or(&name); - base.join(format!("{}.hako", leaf)) - .to_string_lossy() - .to_string() + let leaf = + base.file_name().and_then(|s| s.to_str()).unwrap_or(&name); + base.join(format!("{}.hako", leaf)) + .to_string_lossy() + .to_string() }; // Duplicate detection for prod package alias resolution let canon = std::fs::canonicalize(&out) @@ -294,7 +327,13 @@ pub fn collect_using_and_strip( prev_line )); } else { - seen_paths.insert(canon.clone(), (alias_name.clone().unwrap_or_else(|| "".into()), line_no)); + seen_paths.insert( + canon.clone(), + ( + alias_name.clone().unwrap_or_else(|| "".into()), + line_no, + ), + ); } if let Some(alias) = alias_name.clone() { if let Some((prev_path, prev_line)) = seen_aliases.get(&alias) { @@ -476,7 +515,8 @@ pub fn resolve_prelude_paths_profiled( } let src = std::fs::read_to_string(&real_path) .map_err(|e| format!("using: failed to read '{}': {}", real_path, e))?; - let (_cleaned, nested, _nested_imports) = collect_using_and_strip(runner, &src, &real_path)?; + let (_cleaned, nested, _nested_imports) = + collect_using_and_strip(runner, &src, &real_path)?; for n in nested.iter() { dfs(runner, n, out, seen)?; } @@ -551,7 +591,10 @@ pub fn parse_preludes_to_asts( ) -> Result, String> { let debug = crate::config::env::env_bool("NYASH_STRIP_DEBUG"); if debug { - eprintln!("[strip-debug] parse_preludes_to_asts: {} files total", prelude_paths.len()); + eprintln!( + "[strip-debug] parse_preludes_to_asts: {} files total", + prelude_paths.len() + ); for (idx, p) in prelude_paths.iter().enumerate() { eprintln!("[strip-debug] [{}] {}", idx, p); } @@ -559,18 +602,27 @@ pub fn parse_preludes_to_asts( let mut out: Vec = Vec::with_capacity(prelude_paths.len()); for (idx, prelude_path) in prelude_paths.iter().enumerate() { if debug { - eprintln!("[strip-debug] [{}/{}] Processing: {}", idx + 1, prelude_paths.len(), prelude_path); + eprintln!( + "[strip-debug] [{}/{}] Processing: {}", + idx + 1, + prelude_paths.len(), + prelude_path + ); } let src = std::fs::read_to_string(prelude_path) .map_err(|e| format!("using: error reading {}: {}", prelude_path, e))?; - let (clean_src, _nested, _nested_imports) = collect_using_and_strip(runner, &src, prelude_path)?; + let (clean_src, _nested, _nested_imports) = + collect_using_and_strip(runner, &src, prelude_path)?; // IMPORTANT: Do not attempt to AST-parse .hako preludes here. // .hako is Hakorune surface, not Nyash AST. VM/VM-fallback paths // will route to text-merge when any prelude is .hako. if prelude_path.ends_with(".hako") { if debug { - eprintln!("[strip-debug] skip AST parse for .hako prelude: {}", prelude_path); + eprintln!( + "[strip-debug] skip AST parse for .hako prelude: {}", + prelude_path + ); } continue; } @@ -579,22 +631,37 @@ pub fn parse_preludes_to_asts( // Debug: dump clean_src if NYASH_STRIP_DEBUG=1 if debug { - eprintln!("[strip-debug] [{}/{}] About to parse: {}", idx + 1, prelude_paths.len(), prelude_path); - eprintln!("[strip-debug] clean_src first 500 chars:\n{}\n---", - &clean_src.chars().take(500).collect::()); + eprintln!( + "[strip-debug] [{}/{}] About to parse: {}", + idx + 1, + prelude_paths.len(), + prelude_path + ); + eprintln!( + "[strip-debug] clean_src first 500 chars:\n{}\n---", + &clean_src.chars().take(500).collect::() + ); } match crate::parser::NyashParser::parse_from_string(&clean_src) { Ok(ast) => { if debug { - eprintln!("[strip-debug] [{}/{}] ✅ Parse SUCCESS: {}", idx + 1, prelude_paths.len(), prelude_path); + eprintln!( + "[strip-debug] [{}/{}] ✅ Parse SUCCESS: {}", + idx + 1, + prelude_paths.len(), + prelude_path + ); } out.push(ast) } Err(e) => { // Always output debug info on parse failure if NYASH_STRIP_DEBUG=1 let debug = crate::config::env::env_bool("NYASH_STRIP_DEBUG"); - eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug); + eprintln!( + "[strip-debug] Parse FAILED for: {} (debug={})", + prelude_path, debug + ); if debug { eprintln!("[strip-debug] Error: {}", e); let es = format!("{}", e); @@ -605,19 +672,31 @@ pub fn parse_preludes_to_asts( if let Some(pos) = es.rfind("line ") { let mut j = pos + 5; // after "line " let bytes = es.as_bytes(); - let mut n: usize = 0; let mut had = false; + let mut n: usize = 0; + let mut had = false; while j < bytes.len() { let c = bytes[j]; - if c >= b'0' && c <= b'9' { n = n * 10 + (c - b'0') as usize; j += 1; had = true; } else { break; } + if c >= b'0' && c <= b'9' { + n = n * 10 + (c - b'0') as usize; + j += 1; + had = true; + } else { + break; + } } if had { let ln = if n == 0 { 1 } else { n }; let from = ln.saturating_sub(3); let to = std::cmp::min(lines.len(), ln + 3); - eprintln!("[strip-debug] Context around line {} ({}..={}):", ln, from.max(1), to); + eprintln!( + "[strip-debug] Context around line {} ({}..={}):", + ln, + from.max(1), + to + ); for i in from.max(1)..=to { let mark = if i == ln { ">>" } else { " " }; - if let Some(line) = lines.get(i-1) { + if let Some(line) = lines.get(i - 1) { eprintln!("{} {:4}: {}", mark, i, line); } } @@ -640,7 +719,10 @@ pub fn parse_preludes_to_asts( } } if debug { - eprintln!("[strip-debug] parse_preludes_to_asts: ✅ All {} files parsed successfully", out.len()); + eprintln!( + "[strip-debug] parse_preludes_to_asts: ✅ All {} files parsed successfully", + out.len() + ); } Ok(out) } @@ -663,7 +745,10 @@ pub fn merge_prelude_asts_with_main( if let ASTNode::Program { statements, .. } = main_ast.clone() { let mut all = combined; all.extend(statements); - ASTNode::Program { statements: all, span: Span::unknown() } + ASTNode::Program { + statements: all, + span: Span::unknown(), + } } else { // Defensive: unexpected shape; preserve main AST unchanged. main_ast.clone() @@ -802,6 +887,8 @@ pub fn merge_prelude_text( // Build merged text: preludes first, then main source let mut merged = String::new(); + let mut spans: Vec = Vec::new(); + let mut current_line: usize = 1; // Add preludes in DFS order for (idx, path) in prelude_paths.iter().enumerate() { @@ -809,7 +896,8 @@ pub fn merge_prelude_text( .map_err(|e| format!("using: failed to read '{}': {}", path, e))?; // Strip using lines from prelude and normalize - let (cleaned_raw, _nested, _nested_imports) = collect_using_and_strip(runner, &content, path)?; + let (cleaned_raw, _nested, _nested_imports) = + collect_using_and_strip(runner, &content, path)?; let mut cleaned = normalize_text_for_inline(&cleaned_raw); // Hako-friendly normalize for preludes: always strip leading `local ` at line head // when the prelude is a .hako (or looks like Hako code). This prevents top-level @@ -831,11 +919,30 @@ pub fn merge_prelude_text( merged.push_str(&cleaned); merged.push('\n'); + + let added = cleaned.lines().count(); + if added > 0 { + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: path.clone(), + start_line: current_line, + line_count: added, + }); + current_line += added + 1; // +1 for extra '\n' + } else { + current_line += 1; + } } // Add boundary marker if debug mode if crate::config::env::env_bool("NYASH_RESOLVE_SEAM_DEBUG") { merged.push_str("\n/* --- using prelude/main boundary --- */\n\n"); + let boundary_lines = 3usize; + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: "".to_string(), + start_line: current_line, + line_count: boundary_lines, + }); + current_line += boundary_lines; } // Add main source (already cleaned of using lines) and normalize @@ -845,9 +952,19 @@ pub fn merge_prelude_text( if filename.ends_with(".hako") || crate::runner::modes::common_util::hako::looks_like_hako_code(&cleaned_main_norm) { - cleaned_main_norm = crate::runner::modes::common_util::hako::strip_local_decl(&cleaned_main_norm); + cleaned_main_norm = + crate::runner::modes::common_util::hako::strip_local_decl(&cleaned_main_norm); } merged.push_str(&cleaned_main_norm); + let main_lines = cleaned_main_norm.lines().count(); + if main_lines > 0 { + spans.push(crate::runner::modes::common_util::resolve::LineSpan { + file: filename.to_string(), + start_line: current_line, + line_count: main_lines, + }); + current_line += main_lines; + } if trace { crate::runner::trace::log(format!( @@ -865,6 +982,8 @@ pub fn merge_prelude_text( } } + crate::runner::modes::common_util::resolve::set_last_text_merge_line_spans(spans); + Ok(normalize_text_for_inline(&merged)) } @@ -887,7 +1006,11 @@ fn normalize_text_for_inline(s: &str) -> String { let mut j = i + 1; while j < bytes.len() { let c = bytes[j]; - if c == b' ' || c == b'\t' || c == b'\n' { j += 1; } else { break; } + if c == b' ' || c == b'\t' || c == b'\n' { + j += 1; + } else { + break; + } } if j < bytes.len() && bytes[j] == b'}' { // drop ';' (do not advance j here) @@ -900,6 +1023,8 @@ fn normalize_text_for_inline(s: &str) -> String { } out = tmp; } - if !out.ends_with('\n') { out.push('\n'); } + if !out.ends_with('\n') { + out.push('\n'); + } out } diff --git a/src/runner/modes/common_util/resolve/using_resolution.rs b/src/runner/modes/common_util/resolve/using_resolution.rs index d1aa9674..d552b4a1 100644 --- a/src/runner/modes/common_util/resolve/using_resolution.rs +++ b/src/runner/modes/common_util/resolve/using_resolution.rs @@ -1,5 +1,5 @@ //! Using Resolution Box - 綺麗綺麗なusing文解決専門家!📦 -//! +//! //! 巨大な `collect_using_and_strip` 関数を箱に分解して、 //! 責務を明確にしてテストしやすくするにゃ! @@ -8,7 +8,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; /// 📦 UsingResolutionBox - using文解決の専門家! -/// +/// /// using文の解析、パス解決、重複チェックを一手に引き受ける箱にゃ! pub struct UsingResolutionBox<'a> { runner: &'a NyashRunner, @@ -52,9 +52,9 @@ impl<'a> UsingResolutionBox<'a> { || crate::config::env::env_bool("NYASH_RESOLVE_TRACE"), allow_file_using: crate::config::env::allow_using_file(), }; - + let ctx_dir = Path::new(filename).parent().map(|p| p.to_path_buf()); - + // ファイルがパッケージ内にあるかチェック let filename_canon = std::fs::canonicalize(filename).ok(); let mut inside_pkg = false; @@ -89,11 +89,11 @@ impl<'a> UsingResolutionBox<'a> { } crate::cli_v!("[using] stripped line: {}", line); - + let rest0 = t.strip_prefix("using ").unwrap().trim(); let rest0 = rest0.split('#').next().unwrap_or(rest0).trim(); let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim(); - + let (target, alias) = if let Some(pos) = rest0.find(" as ") { ( rest0[..pos].trim().to_string(), @@ -105,16 +105,21 @@ impl<'a> UsingResolutionBox<'a> { let target_unquoted = target.trim_matches('"').to_string(); let using_ctx = self.runner.init_using_context(); - + // 既知のエイリアスかモジュールかチェック let is_known_alias_or_module = using_ctx.aliases.contains_key(&target_unquoted) - || using_ctx.pending_modules.iter().any(|(k, _)| k == &target_unquoted) + || using_ctx + .pending_modules + .iter() + .any(|(k, _)| k == &target_unquoted) || using_ctx.packages.contains_key(&target_unquoted); let is_path = if is_known_alias_or_module { false } else { - crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(&target_unquoted) + crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted( + &target_unquoted, + ) }; Some(UsingTarget { @@ -145,7 +150,7 @@ impl<'a> UsingResolutionBox<'a> { let path = target.target.trim_matches('"').to_string(); let mut p = PathBuf::from(&path); - + // 相対パス解決 if p.is_relative() { if let Some(dir) = &self.ctx_dir { @@ -154,7 +159,7 @@ impl<'a> UsingResolutionBox<'a> { p = cand; } } - + // NYASH_ROOTも試す if p.is_relative() { if let Ok(root) = std::env::var("NYASH_ROOT") { @@ -172,9 +177,13 @@ impl<'a> UsingResolutionBox<'a> { } /// 🛡️ 重複チェックするにゃ! - pub fn check_duplicates(&mut self, target: &UsingTarget, resolved_path: &str) -> Result<(), String> { - let canon_path = std::fs::canonicalize(resolved_path) - .unwrap_or_else(|_| PathBuf::from(resolved_path)); + pub fn check_duplicates( + &mut self, + target: &UsingTarget, + resolved_path: &str, + ) -> Result<(), String> { + let canon_path = + std::fs::canonicalize(resolved_path).unwrap_or_else(|_| PathBuf::from(resolved_path)); let canon_str = canon_path.to_string_lossy(); // パスの重複チェック @@ -204,10 +213,14 @@ impl<'a> UsingResolutionBox<'a> { // 記録 let alias_label = target.alias.as_ref().unwrap_or(&target.target).clone(); - self.seen_paths.insert(canon_str.to_string(), (alias_label.clone(), target.line_no)); - + self.seen_paths + .insert(canon_str.to_string(), (alias_label.clone(), target.line_no)); + if let Some(ref alias_name) = target.alias { - self.seen_aliases.insert(alias_name.clone(), (resolved_path.to_string(), target.line_no)); + self.seen_aliases.insert( + alias_name.clone(), + (resolved_path.to_string(), target.line_no), + ); } Ok(()) diff --git a/src/runner/modes/common_util/selfhost/child.rs b/src/runner/modes/common_util/selfhost/child.rs index e171126f..8a212d48 100644 --- a/src/runner/modes/common_util/selfhost/child.rs +++ b/src/runner/modes/common_util/selfhost/child.rs @@ -37,7 +37,10 @@ pub fn run_ny_program_capture_json( } }; if out.timed_out { - let head = String::from_utf8_lossy(&out.stdout).chars().take(200).collect::(); + let head = String::from_utf8_lossy(&out.stdout) + .chars() + .take(200) + .collect::(); eprintln!( "[selfhost-child] timeout after {} ms; stdout(head)='{}'", timeout_ms, diff --git a/src/runner/modes/common_util/selfhost/json.rs b/src/runner/modes/common_util/selfhost/json.rs index db03b043..f90dabd1 100644 --- a/src/runner/modes/common_util/selfhost/json.rs +++ b/src/runner/modes/common_util/selfhost/json.rs @@ -32,12 +32,18 @@ pub fn run_pyvm_module(module: &MirModule, label: &str) -> Option { let tmp_dir = Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); - if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, &mir_json_path) { + if let Err(e) = + crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, &mir_json_path) + { eprintln!("❌ PyVM MIR JSON emit error: {}", e); return None; } if crate::config::env::cli_verbose() { - eprintln!("[Bridge] using PyVM ({}) → {}", label, mir_json_path.display()); + eprintln!( + "[Bridge] using PyVM ({}) → {}", + label, + mir_json_path.display() + ); } // Select entry (prefer Main.main; top-level main only if allowed) let allow_top = crate::config::env::entry_allow_toplevel_main(); diff --git a/src/runner/modes/common_util/selfhost/mod.rs b/src/runner/modes/common_util/selfhost/mod.rs index 8edddff7..274fd8e3 100644 --- a/src/runner/modes/common_util/selfhost/mod.rs +++ b/src/runner/modes/common_util/selfhost/mod.rs @@ -4,4 +4,3 @@ pub mod child; pub mod json; - diff --git a/src/runner/modes/common_util/selfhost_exe.rs b/src/runner/modes/common_util/selfhost_exe.rs index aaedfb78..16c85b88 100644 --- a/src/runner/modes/common_util/selfhost_exe.rs +++ b/src/runner/modes/common_util/selfhost_exe.rs @@ -97,13 +97,19 @@ pub fn exe_try_parse_json_v0(filename: &str, timeout_ms: u64) -> Option s, Err(_) => String::new() }; + let stdout = match String::from_utf8(out_buf) { + Ok(s) => s, + Err(_) => String::new(), + }; let json_line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) .unwrap_or_default(); if json_line.is_empty() { if crate::config::env::cli_verbose() { let head: String = stdout.chars().take(200).collect(); - let errh: String = String::from_utf8_lossy(&err_buf).chars().take(200).collect(); + let errh: String = String::from_utf8_lossy(&err_buf) + .chars() + .take(200) + .collect(); crate::cli_v!( "[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'", head.replace('\n', "\\n"), diff --git a/src/runner/modes/llvm.rs b/src/runner/modes/llvm.rs index 0cd519e5..a53675cc 100644 --- a/src/runner/modes/llvm.rs +++ b/src/runner/modes/llvm.rs @@ -45,9 +45,14 @@ impl NyashRunner { std::process::exit(1); } if use_ast && !paths.is_empty() { - match crate::runner::modes::common_util::resolve::parse_preludes_to_asts(self, &paths) { + match crate::runner::modes::common_util::resolve::parse_preludes_to_asts( + self, &paths, + ) { Ok(v) => prelude_asts = v, - Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } + Err(e) => { + eprintln!("❌ {}", e); + std::process::exit(1); + } } } } @@ -58,16 +63,22 @@ impl NyashRunner { } } // Pre-expand '@name[:T] = expr' sugar at line-head (same as common path) - let preexpanded_owned = crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref); + let preexpanded_owned = + crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref); code_ref = &preexpanded_owned; // Parse to AST (main) let main_ast = match NyashParser::parse_from_string(code_ref) { Ok(ast) => ast, Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + code_ref, + &e, + ); // Enhanced context: list merged prelude files if any (from text-merge path) - let preludes = crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); + let preludes = + crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); if !preludes.is_empty() { eprintln!("[parse/context] merged prelude files ({}):", preludes.len()); let show = std::cmp::min(16, preludes.len()); @@ -83,8 +94,13 @@ impl NyashRunner { }; // Merge preludes + main when enabled let ast = if use_ast && !prelude_asts.is_empty() { - crate::runner::modes::common_util::resolve::merge_prelude_asts_with_main(prelude_asts, &main_ast) - } else { main_ast }; + crate::runner::modes::common_util::resolve::merge_prelude_asts_with_main( + prelude_asts, + &main_ast, + ) + } else { + main_ast + }; // Macro expansion (env-gated) after merge let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast); @@ -106,13 +122,20 @@ impl NyashRunner { #[allow(unused_mut)] let mut module = compile_result.module.clone(); let injected = inject_method_ids(&mut module); - if injected > 0 { crate::cli_v!("[LLVM] method_id injected: {} places", injected); } + if injected > 0 { + crate::cli_v!("[LLVM] method_id injected: {} places", injected); + } // Dev/Test helper: allow executing via PyVM harness when requested if std::env::var("SMOKES_USE_PYVM").ok().as_deref() == Some("1") { match super::common_util::pyvm::run_pyvm_harness_lib(&module, "llvm-ast") { - Ok(code) => { std::process::exit(code); } - Err(e) => { eprintln!("❌ PyVM harness error: {}", e); std::process::exit(1); } + Ok(code) => { + std::process::exit(code); + } + Err(e) => { + eprintln!("❌ PyVM harness error: {}", e); + std::process::exit(1); + } } } @@ -122,7 +145,9 @@ impl NyashRunner { { // Harness path (optional): if NYASH_LLVM_USE_HARNESS=1, try Python/llvmlite first. if crate::config::env::llvm_use_harness() { - if let Err(e) = crate::runner::modes::common_util::exec::llvmlite_emit_object(&module, &_out_path, 20_000) { + if let Err(e) = crate::runner::modes::common_util::exec::llvmlite_emit_object( + &module, &_out_path, 20_000, + ) { eprintln!("❌ {}", e); process::exit(1); } @@ -207,10 +232,16 @@ impl NyashRunner { libs.as_deref(), ) { Ok(()) => { - match crate::runner::modes::common_util::exec::run_executable(exe_out, &[], 20_000) { + match crate::runner::modes::common_util::exec::run_executable( + exe_out, + &[], + 20_000, + ) { Ok((code, _timed_out, stdout_text)) => { // Forward program stdout so parity tests can compare outputs - if !stdout_text.is_empty() { print!("{}", stdout_text); } + if !stdout_text.is_empty() { + print!("{}", stdout_text); + } println!("✅ LLVM (harness) execution completed (exit={})", code); std::process::exit(code); } @@ -222,7 +253,9 @@ impl NyashRunner { } Err(e) => { eprintln!("❌ ny-llvmc emit-exe error: {}", e); - eprintln!(" Hint: build ny-llvmc: cargo build -p nyash-llvm-compiler --release"); + eprintln!( + " Hint: build ny-llvmc: cargo build -p nyash-llvm-compiler --release" + ); std::process::exit(1); } } diff --git a/src/runner/modes/macro_child/entry.rs b/src/runner/modes/macro_child/entry.rs index aa3f7b90..7e73ff14 100644 --- a/src/runner/modes/macro_child/entry.rs +++ b/src/runner/modes/macro_child/entry.rs @@ -10,11 +10,17 @@ pub fn run_macro_child(macro_file: &str) { } let value: serde_json::Value = match serde_json::from_str(&input) { Ok(v) => v, - Err(_) => { eprintln!("[macro-child] invalid AST JSON v0"); std::process::exit(3); } + Err(_) => { + eprintln!("[macro-child] invalid AST JSON v0"); + std::process::exit(3); + } }; let ast: nyash_rust::ASTNode = match crate::r#macro::ast_json::json_to_ast(&value) { Some(a) => a, - None => { eprintln!("[macro-child] unsupported AST JSON v0"); std::process::exit(4); } + None => { + eprintln!("[macro-child] unsupported AST JSON v0"); + std::process::exit(4); + } }; // Analyze macro behavior (PoC) @@ -29,40 +35,148 @@ pub fn run_macro_child(macro_file: &str) { let m = crate::r#macro::macro_box::UppercasePrintMacro; crate::r#macro::macro_box::MacroBox::expand(&m, &ast) } - crate::r#macro::macro_box_ny::MacroBehavior::ArrayPrependZero => transform_array_prepend_zero(&ast), + crate::r#macro::macro_box_ny::MacroBehavior::ArrayPrependZero => { + transform_array_prepend_zero(&ast) + } crate::r#macro::macro_box_ny::MacroBehavior::MapInsertTag => transform_map_insert_tag(&ast), - crate::r#macro::macro_box_ny::MacroBehavior::LoopNormalize => transform_loop_normalize(&ast), - crate::r#macro::macro_box_ny::MacroBehavior::IfMatchNormalize => transform_peek_match_literal(&ast), - crate::r#macro::macro_box_ny::MacroBehavior::ForForeachNormalize => transform_for_foreach(&ast), + crate::r#macro::macro_box_ny::MacroBehavior::LoopNormalize => { + transform_loop_normalize(&ast) + } + crate::r#macro::macro_box_ny::MacroBehavior::IfMatchNormalize => { + transform_peek_match_literal(&ast) + } + crate::r#macro::macro_box_ny::MacroBehavior::ForForeachNormalize => { + transform_for_foreach(&ast) + } crate::r#macro::macro_box_ny::MacroBehavior::EnvTagString => { fn tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::ASTNode as A; match ast.clone() { - A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => { - if s == "hello" { A::Literal { value: nyash_rust::ast::LiteralValue::String("hello [ENV]".to_string()), span: nyash_rust::ast::Span::unknown() } } else { ast.clone() } + A::Literal { + value: nyash_rust::ast::LiteralValue::String(s), + .. + } => { + if s == "hello" { + A::Literal { + value: nyash_rust::ast::LiteralValue::String( + "hello [ENV]".to_string(), + ), + span: nyash_rust::ast::Span::unknown(), + } + } else { + ast.clone() + } } - A::Program { statements, span } => A::Program { statements: statements.iter().map(|n| tag(n)).collect(), span }, - A::Print { expression, span } => A::Print { expression: Box::new(tag(&expression)), span }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(tag(v))), span }, - A::Assignment { target, value, span } => A::Assignment { target: Box::new(tag(&target)), value: Box::new(tag(&value)), span }, - A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(tag(&condition)), then_body: then_body.iter().map(|n| tag(n)).collect(), else_body: else_body.map(|v| v.iter().map(|n| tag(n)).collect()), span }, - A::Loop { condition, body, span } => A::Loop { condition: Box::new(tag(&condition)), body: body.iter().map(|n| tag(n)).collect(), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(tag(&left)), right: Box::new(tag(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(tag(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(tag(&object)), method, arguments: arguments.iter().map(|a| tag(a)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| tag(a)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| tag(e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), tag(v))).collect(), span }, + A::Program { statements, span } => A::Program { + statements: statements.iter().map(|n| tag(n)).collect(), + span, + }, + A::Print { expression, span } => A::Print { + expression: Box::new(tag(&expression)), + span, + }, + A::Return { value, span } => A::Return { + value: value.as_ref().map(|v| Box::new(tag(v))), + span, + }, + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(tag(&target)), + value: Box::new(tag(&value)), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { + condition: Box::new(tag(&condition)), + then_body: then_body.iter().map(|n| tag(n)).collect(), + else_body: else_body.map(|v| v.iter().map(|n| tag(n)).collect()), + span, + }, + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(tag(&condition)), + body: body.iter().map(|n| tag(n)).collect(), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(tag(&left)), + right: Box::new(tag(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(tag(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(tag(&object)), + method, + arguments: arguments.iter().map(|a| tag(a)).collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments.iter().map(|a| tag(a)).collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements.iter().map(|e| tag(e)).collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries.iter().map(|(k, v)| (k.clone(), tag(v))).collect(), + span, + }, other => other, } } - let mut env_on = std::env::var("NYASH_MACRO_CAP_ENV").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false); + let mut env_on = std::env::var("NYASH_MACRO_CAP_ENV") + .ok() + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false); if let Ok(ctxs) = std::env::var("NYASH_MACRO_CTX_JSON") { if let Ok(v) = serde_json::from_str::(&ctxs) { - env_on = v.get("caps").and_then(|c| c.get("env")).and_then(|b| b.as_bool()).unwrap_or(env_on); + env_on = v + .get("caps") + .and_then(|c| c.get("env")) + .and_then(|b| b.as_bool()) + .unwrap_or(env_on); } } - if env_on { tag(&ast) } else { ast.clone() } + if env_on { + tag(&ast) + } else { + ast.clone() + } } }; let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast); diff --git a/src/runner/modes/macro_child/mod.rs b/src/runner/modes/macro_child/mod.rs index cbf3c788..783d57e9 100644 --- a/src/runner/modes/macro_child/mod.rs +++ b/src/runner/modes/macro_child/mod.rs @@ -2,9 +2,8 @@ * Macro child mode (split modules) */ -mod transforms; mod entry; +mod transforms; pub use entry::run_macro_child; pub use transforms::normalize_core_pass; - diff --git a/src/runner/modes/macro_child/transforms/array.rs b/src/runner/modes/macro_child/transforms/array.rs index 5f7251b6..335b980d 100644 --- a/src/runner/modes/macro_child/transforms/array.rs +++ b/src/runner/modes/macro_child/transforms/array.rs @@ -5,32 +5,110 @@ pub(super) fn transform_array_prepend_zero(ast: &nyash_rust::ASTNode) -> nyash_r let mut new_elems: Vec = Vec::with_capacity(elements.len() + 1); let already_zero = elements .get(0) - .and_then(|n| match n { A::Literal { value: LiteralValue::Integer(0), .. } => Some(()), _ => None }) + .and_then(|n| match n { + A::Literal { + value: LiteralValue::Integer(0), + .. + } => Some(()), + _ => None, + }) .is_some(); if already_zero { - for e in elements { new_elems.push(transform_array_prepend_zero(e)); } + for e in elements { + new_elems.push(transform_array_prepend_zero(e)); + } } else { - new_elems.push(A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }); - for e in elements { new_elems.push(transform_array_prepend_zero(e)); } + new_elems.push(A::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }); + for e in elements { + new_elems.push(transform_array_prepend_zero(e)); + } + } + A::ArrayLiteral { + elements: new_elems, + span: Span::unknown(), } - A::ArrayLiteral { elements: new_elems, span: Span::unknown() } } - A::Program { statements, .. } => A::Program { statements: statements.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() }, - A::Print { expression, .. } => A::Print { expression: Box::new(transform_array_prepend_zero(expression)), span: Span::unknown() }, - A::Return { value, .. } => A::Return { value: value.as_ref().map(|v| Box::new(transform_array_prepend_zero(v))), span: Span::unknown() }, - A::Assignment { target, value, .. } => A::Assignment { target: Box::new(transform_array_prepend_zero(target)), value: Box::new(transform_array_prepend_zero(value)), span: Span::unknown() }, - A::If { condition, then_body, else_body, .. } => A::If { - condition: Box::new(transform_array_prepend_zero(condition)), - then_body: then_body.iter().map(transform_array_prepend_zero).collect(), - else_body: else_body.as_ref().map(|v| v.iter().map(transform_array_prepend_zero).collect()), + A::Program { statements, .. } => A::Program { + statements: statements + .iter() + .map(transform_array_prepend_zero) + .collect(), + span: Span::unknown(), + }, + A::Print { expression, .. } => A::Print { + expression: Box::new(transform_array_prepend_zero(expression)), + span: Span::unknown(), + }, + A::Return { value, .. } => A::Return { + value: value + .as_ref() + .map(|v| Box::new(transform_array_prepend_zero(v))), + span: Span::unknown(), + }, + A::Assignment { target, value, .. } => A::Assignment { + target: Box::new(transform_array_prepend_zero(target)), + value: Box::new(transform_array_prepend_zero(value)), + span: Span::unknown(), + }, + A::If { + condition, + then_body, + else_body, + .. + } => A::If { + condition: Box::new(transform_array_prepend_zero(condition)), + then_body: then_body.iter().map(transform_array_prepend_zero).collect(), + else_body: else_body + .as_ref() + .map(|v| v.iter().map(transform_array_prepend_zero).collect()), + span: Span::unknown(), + }, + A::BinaryOp { + operator, + left, + right, + .. + } => A::BinaryOp { + operator: operator.clone(), + left: Box::new(transform_array_prepend_zero(left)), + right: Box::new(transform_array_prepend_zero(right)), + span: Span::unknown(), + }, + A::UnaryOp { + operator, operand, .. + } => A::UnaryOp { + operator: operator.clone(), + operand: Box::new(transform_array_prepend_zero(operand)), + span: Span::unknown(), + }, + A::MethodCall { + object, + method, + arguments, + .. + } => A::MethodCall { + object: Box::new(transform_array_prepend_zero(object)), + method: method.clone(), + arguments: arguments.iter().map(transform_array_prepend_zero).collect(), + span: Span::unknown(), + }, + A::FunctionCall { + name, arguments, .. + } => A::FunctionCall { + name: name.clone(), + arguments: arguments.iter().map(transform_array_prepend_zero).collect(), + span: Span::unknown(), + }, + A::MapLiteral { entries, .. } => A::MapLiteral { + entries: entries + .iter() + .map(|(k, v)| (k.clone(), transform_array_prepend_zero(v))) + .collect(), span: Span::unknown(), }, - A::BinaryOp { operator, left, right, .. } => A::BinaryOp { operator: operator.clone(), left: Box::new(transform_array_prepend_zero(left)), right: Box::new(transform_array_prepend_zero(right)), span: Span::unknown() }, - A::UnaryOp { operator, operand, .. } => A::UnaryOp { operator: operator.clone(), operand: Box::new(transform_array_prepend_zero(operand)), span: Span::unknown() }, - A::MethodCall { object, method, arguments, .. } => A::MethodCall { object: Box::new(transform_array_prepend_zero(object)), method: method.clone(), arguments: arguments.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() }, - A::FunctionCall { name, arguments, .. } => A::FunctionCall { name: name.clone(), arguments: arguments.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() }, - A::MapLiteral { entries, .. } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), transform_array_prepend_zero(v))).collect(), span: Span::unknown() }, other => other.clone(), } } - diff --git a/src/runner/modes/macro_child/transforms/foreach.rs b/src/runner/modes/macro_child/transforms/foreach.rs index 049171dd..0a7d3b34 100644 --- a/src/runner/modes/macro_child/transforms/foreach.rs +++ b/src/runner/modes/macro_child/transforms/foreach.rs @@ -1,23 +1,112 @@ -fn subst_var(node: &nyash_rust::ASTNode, name: &str, replacement: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { +fn subst_var( + node: &nyash_rust::ASTNode, + name: &str, + replacement: &nyash_rust::ASTNode, +) -> nyash_rust::ASTNode { use nyash_rust::ast::ASTNode as A; match node.clone() { A::Variable { name: n, .. } if n == name => replacement.clone(), - A::Program { statements, span } => A::Program { statements: statements.iter().map(|s| subst_var(s, name, replacement)).collect(), span }, - A::Print { expression, span } => A::Print { expression: Box::new(subst_var(&expression, name, replacement)), span }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(subst_var(v, name, replacement))), span }, - A::Assignment { target, value, span } => A::Assignment { target: Box::new(subst_var(&target, name, replacement)), value: Box::new(subst_var(&value, name, replacement)), span }, - A::If { condition, then_body, else_body, span } => A::If { - condition: Box::new(subst_var(&condition, name, replacement)), - then_body: then_body.iter().map(|s| subst_var(s, name, replacement)).collect(), - else_body: else_body.map(|v| v.iter().map(|s| subst_var(s, name, replacement)).collect()), + A::Program { statements, span } => A::Program { + statements: statements + .iter() + .map(|s| subst_var(s, name, replacement)) + .collect(), + span, + }, + A::Print { expression, span } => A::Print { + expression: Box::new(subst_var(&expression, name, replacement)), + span, + }, + A::Return { value, span } => A::Return { + value: value + .as_ref() + .map(|v| Box::new(subst_var(v, name, replacement))), + span, + }, + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(subst_var(&target, name, replacement)), + value: Box::new(subst_var(&value, name, replacement)), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { + condition: Box::new(subst_var(&condition, name, replacement)), + then_body: then_body + .iter() + .map(|s| subst_var(s, name, replacement)) + .collect(), + else_body: else_body + .map(|v| v.iter().map(|s| subst_var(s, name, replacement)).collect()), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(subst_var(&left, name, replacement)), + right: Box::new(subst_var(&right, name, replacement)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(subst_var(&operand, name, replacement)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(subst_var(&object, name, replacement)), + method, + arguments: arguments + .iter() + .map(|a| subst_var(a, name, replacement)) + .collect(), + span, + }, + A::FunctionCall { + name: fn_name, + arguments, + span, + } => A::FunctionCall { + name: fn_name, + arguments: arguments + .iter() + .map(|a| subst_var(a, name, replacement)) + .collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .iter() + .map(|e| subst_var(e, name, replacement)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .iter() + .map(|(k, v)| (k.clone(), subst_var(v, name, replacement))) + .collect(), span, }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(subst_var(&left, name, replacement)), right: Box::new(subst_var(&right, name, replacement)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(subst_var(&operand, name, replacement)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(subst_var(&object, name, replacement)), method, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span }, - A::FunctionCall { name: fn_name, arguments, span } => A::FunctionCall { name: fn_name, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| subst_var(e, name, replacement)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), subst_var(v, name, replacement))).collect(), span }, other => other, } } @@ -28,7 +117,9 @@ pub(super) fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::AS let mut out: Vec = Vec::new(); for st in list.into_iter() { match st.clone() { - A::FunctionCall { name, arguments, .. } if (name == "ny_for" || name == "for") && arguments.len() == 4 => { + A::FunctionCall { + name, arguments, .. + } if (name == "ny_for" || name == "for") && arguments.len() == 4 => { let init = arguments[0].clone(); let cond = arguments[1].clone(); let step = arguments[2].clone(); @@ -37,38 +128,117 @@ pub(super) fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::AS if params.is_empty() { match init.clone() { A::Assignment { .. } | A::Local { .. } => out.push(init), - A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => { for s in b2 { out.push(transform_for_foreach(&s)); } } + A::Lambda { + params: p2, + body: b2, + .. + } if p2.is_empty() => { + for s in b2 { + out.push(transform_for_foreach(&s)); + } + } _ => {} } - let mut loop_body: Vec = body.into_iter().map(|n| transform_for_foreach(&n)).collect(); + let mut loop_body: Vec = body + .into_iter() + .map(|n| transform_for_foreach(&n)) + .collect(); match step.clone() { A::Assignment { .. } => loop_body.push(step), - A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => { for s in b2 { loop_body.push(transform_for_foreach(&s)); } } + A::Lambda { + params: p2, + body: b2, + .. + } if p2.is_empty() => { + for s in b2 { + loop_body.push(transform_for_foreach(&s)); + } + } _ => {} } - out.push(A::Loop { condition: Box::new(transform_for_foreach(&cond)), body: loop_body, span: Span::unknown() }); + out.push(A::Loop { + condition: Box::new(transform_for_foreach(&cond)), + body: loop_body, + span: Span::unknown(), + }); continue; } } out.push(st); } - A::FunctionCall { name, arguments, .. } if (name == "ny_foreach" || name == "foreach") && arguments.len() == 3 => { + A::FunctionCall { + name, arguments, .. + } if (name == "ny_foreach" || name == "foreach") && arguments.len() == 3 => { let array = arguments[0].clone(); - let param_name = match &arguments[1] { A::Variable { name, .. } => name.clone(), _ => "it".to_string() }; + let param_name = match &arguments[1] { + A::Variable { name, .. } => name.clone(), + _ => "it".to_string(), + }; let body_lam = arguments[2].clone(); if let A::Lambda { params, body, .. } = body_lam { if params.is_empty() { - let iter = A::Variable { name: "__i".to_string(), span: Span::unknown() }; - let zero = A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }; - let one = A::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }; - let init = A::Local { variables: vec!["__i".to_string()], initial_values: vec![Some(Box::new(zero))], span: Span::unknown() }; - let len_call = A::MethodCall { object: Box::new(transform_for_foreach(&array)), method: "len".to_string(), arguments: vec![], span: Span::unknown() }; - let cond = A::BinaryOp { operator: BinaryOperator::Less, left: Box::new(iter.clone()), right: Box::new(len_call), span: Span::unknown() }; - let get_call = A::MethodCall { object: Box::new(transform_for_foreach(&array)), method: "get".to_string(), arguments: vec![iter.clone()], span: Span::unknown() }; - let body_stmts: Vec = body.into_iter().map(|s| subst_var(&s, ¶m_name, &get_call)).collect(); - let step = A::Assignment { target: Box::new(iter.clone()), value: Box::new(A::BinaryOp { operator: BinaryOperator::Add, left: Box::new(iter), right: Box::new(one), span: Span::unknown() }), span: Span::unknown() }; + let iter = A::Variable { + name: "__i".to_string(), + span: Span::unknown(), + }; + let zero = A::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }; + let one = A::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }; + let init = A::Local { + variables: vec!["__i".to_string()], + initial_values: vec![Some(Box::new(zero))], + span: Span::unknown(), + }; + let len_call = A::MethodCall { + object: Box::new(transform_for_foreach(&array)), + method: "len".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + let cond = A::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(iter.clone()), + right: Box::new(len_call), + span: Span::unknown(), + }; + let get_call = A::MethodCall { + object: Box::new(transform_for_foreach(&array)), + method: "get".to_string(), + arguments: vec![iter.clone()], + span: Span::unknown(), + }; + let body_stmts: Vec = body + .into_iter() + .map(|s| subst_var(&s, ¶m_name, &get_call)) + .collect(); + let step = A::Assignment { + target: Box::new(iter.clone()), + value: Box::new(A::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(iter), + right: Box::new(one), + span: Span::unknown(), + }), + span: Span::unknown(), + }; out.push(init); - out.push(A::Loop { condition: Box::new(cond), body: { let mut b = Vec::new(); for s in body_stmts { b.push(transform_for_foreach(&s)); } b.push(step); b }, span: Span::unknown() }); + out.push(A::Loop { + condition: Box::new(cond), + body: { + let mut b = Vec::new(); + for s in body_stmts { + b.push(transform_for_foreach(&s)); + } + b.push(step); + b + }, + span: Span::unknown(), + }); continue; } } @@ -81,18 +251,98 @@ pub(super) fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::AS } // `A` is already imported above match ast.clone() { - A::Program { statements, span } => A::Program { statements: rewrite_stmt_list(statements), span }, - A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(transform_for_foreach(&condition)), then_body: rewrite_stmt_list(then_body), else_body: else_body.map(rewrite_stmt_list), span }, - A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_for_foreach(&condition)), body: rewrite_stmt_list(body), span }, - A::Print { expression, span } => A::Print { expression: Box::new(transform_for_foreach(&expression)), span }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(transform_for_foreach(v))), span }, - A::Assignment { target, value, span } => A::Assignment { target: Box::new(transform_for_foreach(&target)), value: Box::new(transform_for_foreach(&value)), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_for_foreach(&left)), right: Box::new(transform_for_foreach(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_for_foreach(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_for_foreach(&object)), method, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| transform_for_foreach(e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), transform_for_foreach(v))).collect(), span }, + A::Program { statements, span } => A::Program { + statements: rewrite_stmt_list(statements), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { + condition: Box::new(transform_for_foreach(&condition)), + then_body: rewrite_stmt_list(then_body), + else_body: else_body.map(rewrite_stmt_list), + span, + }, + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(transform_for_foreach(&condition)), + body: rewrite_stmt_list(body), + span, + }, + A::Print { expression, span } => A::Print { + expression: Box::new(transform_for_foreach(&expression)), + span, + }, + A::Return { value, span } => A::Return { + value: value.as_ref().map(|v| Box::new(transform_for_foreach(v))), + span, + }, + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(transform_for_foreach(&target)), + value: Box::new(transform_for_foreach(&value)), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_for_foreach(&left)), + right: Box::new(transform_for_foreach(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_for_foreach(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_for_foreach(&object)), + method, + arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements.iter().map(|e| transform_for_foreach(e)).collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .iter() + .map(|(k, v)| (k.clone(), transform_for_foreach(v))) + .collect(), + span, + }, other => other, } } diff --git a/src/runner/modes/macro_child/transforms/if_to_loopform.rs b/src/runner/modes/macro_child/transforms/if_to_loopform.rs index 53eaf134..cb9ae635 100644 --- a/src/runner/modes/macro_child/transforms/if_to_loopform.rs +++ b/src/runner/modes/macro_child/transforms/if_to_loopform.rs @@ -1,24 +1,123 @@ pub(super) fn transform_if_to_loopform(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::{ASTNode as A, Span}; match ast.clone() { - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_if_to_loopform(&n)).collect(), span }, - A::If { condition, then_body, else_body, span } => { + A::Program { statements, span } => A::Program { + statements: statements + .into_iter() + .map(|n| transform_if_to_loopform(&n)) + .collect(), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => { let cond_t = Box::new(transform_if_to_loopform(&condition)); - let then_t = then_body.into_iter().map(|n| transform_if_to_loopform(&n)).collect(); - let else_t = else_body.map(|v| v.into_iter().map(|n| transform_if_to_loopform(&n)).collect()); - let inner_if = A::If { condition: cond_t, then_body: then_t, else_body: else_t, span: Span::unknown() }; - let one = A::Literal { value: nyash_rust::ast::LiteralValue::Integer(1), span: Span::unknown() }; - let loop_body = vec![inner_if, A::Break { span: Span::unknown() }]; - A::Loop { condition: Box::new(one), body: loop_body, span } + let then_t = then_body + .into_iter() + .map(|n| transform_if_to_loopform(&n)) + .collect(); + let else_t = else_body.map(|v| { + v.into_iter() + .map(|n| transform_if_to_loopform(&n)) + .collect() + }); + let inner_if = A::If { + condition: cond_t, + then_body: then_t, + else_body: else_t, + span: Span::unknown(), + }; + let one = A::Literal { + value: nyash_rust::ast::LiteralValue::Integer(1), + span: Span::unknown(), + }; + let loop_body = vec![ + inner_if, + A::Break { + span: Span::unknown(), + }, + ]; + A::Loop { + condition: Box::new(one), + body: loop_body, + span, + } } - A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_if_to_loopform(&condition)), body: body.into_iter().map(|n| transform_if_to_loopform(&n)).collect(), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_if_to_loopform(&left)), right: Box::new(transform_if_to_loopform(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_if_to_loopform(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_if_to_loopform(&object)), method, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_if_to_loopform(&e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_if_to_loopform(&v))).collect(), span }, + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(transform_if_to_loopform(&condition)), + body: body + .into_iter() + .map(|n| transform_if_to_loopform(&n)) + .collect(), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_if_to_loopform(&left)), + right: Box::new(transform_if_to_loopform(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_if_to_loopform(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_if_to_loopform(&object)), + method, + arguments: arguments + .into_iter() + .map(|a| transform_if_to_loopform(&a)) + .collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments + .into_iter() + .map(|a| transform_if_to_loopform(&a)) + .collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| transform_if_to_loopform(&e)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, transform_if_to_loopform(&v))) + .collect(), + span, + }, other => other, } } - diff --git a/src/runner/modes/macro_child/transforms/lift.rs b/src/runner/modes/macro_child/transforms/lift.rs index 774c601d..641ce201 100644 --- a/src/runner/modes/macro_child/transforms/lift.rs +++ b/src/runner/modes/macro_child/transforms/lift.rs @@ -2,86 +2,302 @@ pub(super) fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyas use nyash_rust::ast::ASTNode as A; use std::sync::atomic::{AtomicUsize, Ordering}; static COUNTER: AtomicUsize = AtomicUsize::new(0); - fn gensym(base: &str) -> String { let n = COUNTER.fetch_add(1, Ordering::Relaxed); format!("__ny_lifted_{}_{}", base, n) } + fn gensym(base: &str) -> String { + let n = COUNTER.fetch_add(1, Ordering::Relaxed); + format!("__ny_lifted_{}_{}", base, n) + } fn collect_locals(n: &A, set: &mut std::collections::HashSet) { match n { - A::Local { variables, .. } => { for v in variables { set.insert(v.clone()); } } - A::Program { statements, .. } => for s in statements { collect_locals(s, set); }, - A::FunctionDeclaration { body, .. } => for s in body { collect_locals(s, set); }, - A::If { then_body, else_body, .. } => { for s in then_body { collect_locals(s, set); } if let Some(b) = else_body { for s in b { collect_locals(s, set); } } } + A::Local { variables, .. } => { + for v in variables { + set.insert(v.clone()); + } + } + A::Program { statements, .. } => { + for s in statements { + collect_locals(s, set); + } + } + A::FunctionDeclaration { body, .. } => { + for s in body { + collect_locals(s, set); + } + } + A::If { + then_body, + else_body, + .. + } => { + for s in then_body { + collect_locals(s, set); + } + if let Some(b) = else_body { + for s in b { + collect_locals(s, set); + } + } + } _ => {} } } fn collect_vars(n: &A, set: &mut std::collections::HashSet) { match n { - A::Variable { name, .. } => { set.insert(name.clone()); } - A::Program { statements, .. } => for s in statements { collect_vars(s, set); }, - A::FunctionDeclaration { body, .. } => for s in body { collect_vars(s, set); }, - A::If { condition, then_body, else_body, .. } => { - collect_vars(condition, set); - for s in then_body { collect_vars(s, set); } - if let Some(b) = else_body { for s in b { collect_vars(s, set); } } + A::Variable { name, .. } => { + set.insert(name.clone()); + } + A::Program { statements, .. } => { + for s in statements { + collect_vars(s, set); + } + } + A::FunctionDeclaration { body, .. } => { + for s in body { + collect_vars(s, set); + } + } + A::If { + condition, + then_body, + else_body, + .. + } => { + collect_vars(condition, set); + for s in then_body { + collect_vars(s, set); + } + if let Some(b) = else_body { + for s in b { + collect_vars(s, set); + } + } + } + A::Assignment { target, value, .. } => { + collect_vars(target, set); + collect_vars(value, set); + } + A::Return { value, .. } => { + if let Some(v) = value { + collect_vars(v, set); + } } - A::Assignment { target, value, .. } => { collect_vars(target, set); collect_vars(value, set); } - A::Return { value, .. } => { if let Some(v) = value { collect_vars(v, set); } } A::Print { expression, .. } => collect_vars(expression, set), - A::BinaryOp { left, right, .. } => { collect_vars(left, set); collect_vars(right, set); } + A::BinaryOp { left, right, .. } => { + collect_vars(left, set); + collect_vars(right, set); + } A::UnaryOp { operand, .. } => collect_vars(operand, set), - A::MethodCall { object, arguments, .. } => { collect_vars(object, set); for a in arguments { collect_vars(a, set); } } - A::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, set); } } - A::ArrayLiteral { elements, .. } => { for e in elements { collect_vars(e, set); } } - A::MapLiteral { entries, .. } => { for (_,v) in entries { collect_vars(v, set); } } + A::MethodCall { + object, arguments, .. + } => { + collect_vars(object, set); + for a in arguments { + collect_vars(a, set); + } + } + A::FunctionCall { arguments, .. } => { + for a in arguments { + collect_vars(a, set); + } + } + A::ArrayLiteral { elements, .. } => { + for e in elements { + collect_vars(e, set); + } + } + A::MapLiteral { entries, .. } => { + for (_, v) in entries { + collect_vars(v, set); + } + } _ => {} } } fn rename_calls(n: &A, mapping: &std::collections::HashMap) -> A { use nyash_rust::ast::ASTNode as A; match n.clone() { - A::FunctionCall { name, arguments, span } => { + A::FunctionCall { + name, + arguments, + span, + } => { let new_name = mapping.get(&name).cloned().unwrap_or(name); - A::FunctionCall { name: new_name, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span } + A::FunctionCall { + name: new_name, + arguments: arguments + .into_iter() + .map(|a| rename_calls(&a, mapping)) + .collect(), + span, + } } - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|s| rename_calls(&s, mapping)).collect(), span }, - A::FunctionDeclaration { name, params, body, is_static, is_override, span } => { - A::FunctionDeclaration { name, params, body: body.into_iter().map(|s| rename_calls(&s, mapping)).collect(), is_static, is_override, span } - } - A::If { condition, then_body, else_body, span } => A::If { - condition: Box::new(rename_calls(&condition, mapping)), - then_body: then_body.into_iter().map(|s| rename_calls(&s, mapping)).collect(), - else_body: else_body.map(|v| v.into_iter().map(|s| rename_calls(&s, mapping)).collect()), + A::Program { statements, span } => A::Program { + statements: statements + .into_iter() + .map(|s| rename_calls(&s, mapping)) + .collect(), + span, + }, + A::FunctionDeclaration { + name, + params, + body, + is_static, + is_override, + span, + } => A::FunctionDeclaration { + name, + params, + body: body + .into_iter() + .map(|s| rename_calls(&s, mapping)) + .collect(), + is_static, + is_override, + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { + condition: Box::new(rename_calls(&condition, mapping)), + then_body: then_body + .into_iter() + .map(|s| rename_calls(&s, mapping)) + .collect(), + else_body: else_body + .map(|v| v.into_iter().map(|s| rename_calls(&s, mapping)).collect()), + span, + }, + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(rename_calls(&target, mapping)), + value: Box::new(rename_calls(&value, mapping)), + span, + }, + A::Return { value, span } => A::Return { + value: value.as_ref().map(|v| Box::new(rename_calls(v, mapping))), + span, + }, + A::Print { expression, span } => A::Print { + expression: Box::new(rename_calls(&expression, mapping)), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(rename_calls(&left, mapping)), + right: Box::new(rename_calls(&right, mapping)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(rename_calls(&operand, mapping)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(rename_calls(&object, mapping)), + method, + arguments: arguments + .into_iter() + .map(|a| rename_calls(&a, mapping)) + .collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| rename_calls(&e, mapping)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, rename_calls(&v, mapping))) + .collect(), span, }, - A::Assignment { target, value, span } => A::Assignment { target: Box::new(rename_calls(&target, mapping)), value: Box::new(rename_calls(&value, mapping)), span }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(rename_calls(v, mapping))), span }, - A::Print { expression, span } => A::Print { expression: Box::new(rename_calls(&expression, mapping)), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(rename_calls(&left, mapping)), right: Box::new(rename_calls(&right, mapping)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(rename_calls(&operand, mapping)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(rename_calls(&object, mapping)), method, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| rename_calls(&e, mapping)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, rename_calls(&v, mapping))).collect(), span }, other => other, } } - fn lift_in_body(body: Vec, hoisted: &mut Vec, mapping: &mut std::collections::HashMap) -> Vec { + fn lift_in_body( + body: Vec, + hoisted: &mut Vec, + mapping: &mut std::collections::HashMap, + ) -> Vec { use std::collections::HashSet; let mut out: Vec = Vec::new(); for st in body.into_iter() { match st.clone() { - A::FunctionDeclaration { name, params, body, is_static, is_override, span } => { + A::FunctionDeclaration { + name, + params, + body, + is_static, + is_override, + span, + } => { let mut locals: HashSet = HashSet::new(); - collect_locals(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut locals); + collect_locals( + &A::FunctionDeclaration { + name: name.clone(), + params: params.clone(), + body: body.clone(), + is_static, + is_override, + span, + }, + &mut locals, + ); let mut used: HashSet = HashSet::new(); - collect_vars(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut used); + collect_vars( + &A::FunctionDeclaration { + name: name.clone(), + params: params.clone(), + body: body.clone(), + is_static, + is_override, + span, + }, + &mut used, + ); let params_set: HashSet = params.iter().cloned().collect(); let mut extra: HashSet = used.drain().collect(); extra.retain(|v| !params_set.contains(v) && !locals.contains(v)); if extra.is_empty() { let new_name = gensym(&name); - let lifted = A::FunctionDeclaration { name: new_name.clone(), params, body, is_static: true, is_override, span }; + let lifted = A::FunctionDeclaration { + name: new_name.clone(), + params, + body, + is_static: true, + is_override, + span, + }; hoisted.push(lifted); mapping.insert(name, new_name); continue; - } else { out.push(st); } + } else { + out.push(st); + } } other => out.push(other), } @@ -93,30 +309,115 @@ pub(super) fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyas match n.clone() { A::Program { statements, span } => { let mut mapping = std::collections::HashMap::new(); - let stmts2 = lift_in_body(statements.into_iter().map(|s| walk(&s, hoisted)).collect(), hoisted, &mut mapping); - A::Program { statements: stmts2, span } + let stmts2 = lift_in_body( + statements.into_iter().map(|s| walk(&s, hoisted)).collect(), + hoisted, + &mut mapping, + ); + A::Program { + statements: stmts2, + span, + } } - A::FunctionDeclaration { name, params, body, is_static, is_override, span } => { + A::FunctionDeclaration { + name, + params, + body, + is_static, + is_override, + span, + } => { let mut mapping = std::collections::HashMap::new(); let body2: Vec = body.into_iter().map(|s| walk(&s, hoisted)).collect(); let body3 = lift_in_body(body2, hoisted, &mut mapping); - A::FunctionDeclaration { name, params, body: body3, is_static, is_override, span } + A::FunctionDeclaration { + name, + params, + body: body3, + is_static, + is_override, + span, + } } - A::If { condition, then_body, else_body, span } => A::If { + A::If { + condition, + then_body, + else_body, + span, + } => A::If { condition: Box::new(walk(&condition, hoisted)), then_body: then_body.into_iter().map(|s| walk(&s, hoisted)).collect(), else_body: else_body.map(|v| v.into_iter().map(|s| walk(&s, hoisted)).collect()), span, }, - A::Assignment { target, value, span } => A::Assignment { target: Box::new(walk(&target, hoisted)), value: Box::new(walk(&value, hoisted)), span }, - A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(walk(v, hoisted))), span }, - A::Print { expression, span } => A::Print { expression: Box::new(walk(&expression, hoisted)), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(walk(&left, hoisted)), right: Box::new(walk(&right, hoisted)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(walk(&operand, hoisted)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(walk(&object, hoisted)), method, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| walk(&e, hoisted)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, walk(&v, hoisted))).collect(), span }, + A::Assignment { + target, + value, + span, + } => A::Assignment { + target: Box::new(walk(&target, hoisted)), + value: Box::new(walk(&value, hoisted)), + span, + }, + A::Return { value, span } => A::Return { + value: value.as_ref().map(|v| Box::new(walk(v, hoisted))), + span, + }, + A::Print { expression, span } => A::Print { + expression: Box::new(walk(&expression, hoisted)), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(walk(&left, hoisted)), + right: Box::new(walk(&right, hoisted)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(walk(&operand, hoisted)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(walk(&object, hoisted)), + method, + arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements.into_iter().map(|e| walk(&e, hoisted)).collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, walk(&v, hoisted))) + .collect(), + span, + }, other => other, } } @@ -125,8 +426,10 @@ pub(super) fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyas if let A::Program { statements, span } = out.clone() { let mut ss = statements; ss.extend(hoisted.into_iter()); - out = A::Program { statements: ss, span }; + out = A::Program { + statements: ss, + span, + }; } out } - diff --git a/src/runner/modes/macro_child/transforms/loops.rs b/src/runner/modes/macro_child/transforms/loops.rs index 93ed73a0..7870586f 100644 --- a/src/runner/modes/macro_child/transforms/loops.rs +++ b/src/runner/modes/macro_child/transforms/loops.rs @@ -1,25 +1,103 @@ pub(super) fn transform_loop_normalize(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::ASTNode as A; match ast.clone() { - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_loop_normalize(&n)).collect(), span }, - A::If { condition, then_body, else_body, span } => A::If { - condition: Box::new(transform_loop_normalize(&condition)), - then_body: then_body.into_iter().map(|n| transform_loop_normalize(&n)).collect(), - else_body: else_body.map(|v| v.into_iter().map(|n| transform_loop_normalize(&n)).collect()), + A::Program { statements, span } => A::Program { + statements: statements + .into_iter() + .map(|n| transform_loop_normalize(&n)) + .collect(), span, }, - A::Loop { condition, body, span } => A::Loop { + A::If { + condition, + then_body, + else_body, + span, + } => A::If { condition: Box::new(transform_loop_normalize(&condition)), - body: body.into_iter().map(|n| transform_loop_normalize(&n)).collect(), + then_body: then_body + .into_iter() + .map(|n| transform_loop_normalize(&n)) + .collect(), + else_body: else_body.map(|v| { + v.into_iter() + .map(|n| transform_loop_normalize(&n)) + .collect() + }), + span, + }, + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(transform_loop_normalize(&condition)), + body: body + .into_iter() + .map(|n| transform_loop_normalize(&n)) + .collect(), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_loop_normalize(&left)), + right: Box::new(transform_loop_normalize(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_loop_normalize(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_loop_normalize(&object)), + method, + arguments: arguments + .into_iter() + .map(|a| transform_loop_normalize(&a)) + .collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments + .into_iter() + .map(|a| transform_loop_normalize(&a)) + .collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| transform_loop_normalize(&e)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, transform_loop_normalize(&v))) + .collect(), span, }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_loop_normalize(&left)), right: Box::new(transform_loop_normalize(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_loop_normalize(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_loop_normalize(&object)), method, arguments: arguments.into_iter().map(|a| transform_loop_normalize(&a)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_loop_normalize(&a)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_loop_normalize(&e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, transform_loop_normalize(&v))).collect(), span }, other => other, } } - diff --git a/src/runner/modes/macro_child/transforms/map.rs b/src/runner/modes/macro_child/transforms/map.rs index bc7740b3..4b70bf40 100644 --- a/src/runner/modes/macro_child/transforms/map.rs +++ b/src/runner/modes/macro_child/transforms/map.rs @@ -5,31 +5,94 @@ pub(super) fn transform_map_insert_tag(ast: &nyash_rust::ASTNode) -> nyash_rust: let mut new_entries: Vec<(String, A)> = Vec::with_capacity(entries.len() + 1); let already_tagged = entries.get(0).map(|(k, _)| k == "__macro").unwrap_or(false); if already_tagged { - for (k, v) in entries { new_entries.push((k.clone(), transform_map_insert_tag(v))); } + for (k, v) in entries { + new_entries.push((k.clone(), transform_map_insert_tag(v))); + } } else { new_entries.push(( "__macro".to_string(), - A::Literal { value: LiteralValue::String("on".to_string()), span: Span::unknown() }, + A::Literal { + value: LiteralValue::String("on".to_string()), + span: Span::unknown(), + }, )); - for (k, v) in entries { new_entries.push((k.clone(), transform_map_insert_tag(v))); } + for (k, v) in entries { + new_entries.push((k.clone(), transform_map_insert_tag(v))); + } + } + A::MapLiteral { + entries: new_entries, + span: Span::unknown(), } - A::MapLiteral { entries: new_entries, span: Span::unknown() } } - A::Program { statements, .. } => A::Program { statements: statements.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() }, - A::Print { expression, .. } => A::Print { expression: Box::new(transform_map_insert_tag(expression)), span: Span::unknown() }, - A::Return { value, .. } => A::Return { value: value.as_ref().map(|v| Box::new(transform_map_insert_tag(v))), span: Span::unknown() }, - A::Assignment { target, value, .. } => A::Assignment { target: Box::new(transform_map_insert_tag(target)), value: Box::new(transform_map_insert_tag(value)), span: Span::unknown() }, - A::If { condition, then_body, else_body, .. } => A::If { - condition: Box::new(transform_map_insert_tag(condition)), - then_body: then_body.iter().map(transform_map_insert_tag).collect(), - else_body: else_body.as_ref().map(|v| v.iter().map(transform_map_insert_tag).collect()), + A::Program { statements, .. } => A::Program { + statements: statements.iter().map(transform_map_insert_tag).collect(), + span: Span::unknown(), + }, + A::Print { expression, .. } => A::Print { + expression: Box::new(transform_map_insert_tag(expression)), + span: Span::unknown(), + }, + A::Return { value, .. } => A::Return { + value: value + .as_ref() + .map(|v| Box::new(transform_map_insert_tag(v))), + span: Span::unknown(), + }, + A::Assignment { target, value, .. } => A::Assignment { + target: Box::new(transform_map_insert_tag(target)), + value: Box::new(transform_map_insert_tag(value)), + span: Span::unknown(), + }, + A::If { + condition, + then_body, + else_body, + .. + } => A::If { + condition: Box::new(transform_map_insert_tag(condition)), + then_body: then_body.iter().map(transform_map_insert_tag).collect(), + else_body: else_body + .as_ref() + .map(|v| v.iter().map(transform_map_insert_tag).collect()), + span: Span::unknown(), + }, + A::BinaryOp { + operator, + left, + right, + .. + } => A::BinaryOp { + operator: operator.clone(), + left: Box::new(transform_map_insert_tag(left)), + right: Box::new(transform_map_insert_tag(right)), + span: Span::unknown(), + }, + A::UnaryOp { + operator, operand, .. + } => A::UnaryOp { + operator: operator.clone(), + operand: Box::new(transform_map_insert_tag(operand)), + span: Span::unknown(), + }, + A::MethodCall { + object, + method, + arguments, + .. + } => A::MethodCall { + object: Box::new(transform_map_insert_tag(object)), + method: method.clone(), + arguments: arguments.iter().map(transform_map_insert_tag).collect(), + span: Span::unknown(), + }, + A::FunctionCall { + name, arguments, .. + } => A::FunctionCall { + name: name.clone(), + arguments: arguments.iter().map(transform_map_insert_tag).collect(), span: Span::unknown(), }, - A::BinaryOp { operator, left, right, .. } => A::BinaryOp { operator: operator.clone(), left: Box::new(transform_map_insert_tag(left)), right: Box::new(transform_map_insert_tag(right)), span: Span::unknown() }, - A::UnaryOp { operator, operand, .. } => A::UnaryOp { operator: operator.clone(), operand: Box::new(transform_map_insert_tag(operand)), span: Span::unknown() }, - A::MethodCall { object, method, arguments, .. } => A::MethodCall { object: Box::new(transform_map_insert_tag(object)), method: method.clone(), arguments: arguments.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() }, - A::FunctionCall { name, arguments, .. } => A::FunctionCall { name: name.clone(), arguments: arguments.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() }, other => other.clone(), } } - diff --git a/src/runner/modes/macro_child/transforms/mod.rs b/src/runner/modes/macro_child/transforms/mod.rs index dc86ddbb..d449b798 100644 --- a/src/runner/modes/macro_child/transforms/mod.rs +++ b/src/runner/modes/macro_child/transforms/mod.rs @@ -2,15 +2,15 @@ * Macro child transforms — split modules */ -mod peek; mod array; -mod map; -mod loops; mod foreach; -mod scopebox; -mod lift; mod if_to_loopform; +mod lift; +mod loops; +mod map; +mod peek; mod postfix; +mod scopebox; // Re-exported via thin wrappers to keep names stable pub(super) fn transform_peek_match_literal(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { @@ -47,15 +47,33 @@ pub fn normalize_core_pass(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { let a1 = transform_for_foreach(ast); let a2 = transform_peek_match_literal(&a1); let a3 = transform_loop_normalize(&a2); - let a4 = if std::env::var("NYASH_SCOPEBOX_ENABLE").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) { + let a4 = if std::env::var("NYASH_SCOPEBOX_ENABLE") + .ok() + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false) + { transform_scopebox_inject(&a3) - } else { a3 }; + } else { + a3 + }; let a4b = transform_lift_nested_functions(&a4); - let a5 = if std::env::var("NYASH_IF_AS_LOOPFORM").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) { + let a5 = if std::env::var("NYASH_IF_AS_LOOPFORM") + .ok() + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false) + { transform_if_to_loopform(&a4b) - } else { a4b }; - let a6 = if std::env::var("NYASH_CATCH_NEW").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) { + } else { + a4b + }; + let a6 = if std::env::var("NYASH_CATCH_NEW") + .ok() + .map(|v| v == "1" || v == "true" || v == "on") + .unwrap_or(false) + { transform_postfix_handlers(&a5) - } else { a5 }; + } else { + a5 + }; a6 } diff --git a/src/runner/modes/macro_child/transforms/peek.rs b/src/runner/modes/macro_child/transforms/peek.rs index 72f352d4..ff1ba9de 100644 --- a/src/runner/modes/macro_child/transforms/peek.rs +++ b/src/runner/modes/macro_child/transforms/peek.rs @@ -1,113 +1,329 @@ -fn map_expr_to_stmt(e: nyash_rust::ASTNode) -> nyash_rust::ASTNode { e } +fn map_expr_to_stmt(e: nyash_rust::ASTNode) -> nyash_rust::ASTNode { + e +} fn transform_peek_to_if_expr(peek: &nyash_rust::ASTNode) -> Option { use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span}; - if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek { + if let A::MatchExpr { + scrutinee, + arms, + else_expr, + .. + } = peek + { let mut conds_bodies: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new(); - for (lit, body) in arms { conds_bodies.push((lit.clone(), (*body).clone())); } + for (lit, body) in arms { + conds_bodies.push((lit.clone(), (*body).clone())); + } let mut current: A = *(*else_expr).clone(); for (lit, body) in conds_bodies.into_iter().rev() { - let rhs = A::Literal { value: lit, span: Span::unknown() }; - let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() }; + let rhs = A::Literal { + value: lit, + span: Span::unknown(), + }; + let cond = A::BinaryOp { + operator: BinaryOperator::Equal, + left: scrutinee.clone(), + right: Box::new(rhs), + span: Span::unknown(), + }; let then_body = vec![map_expr_to_stmt(body)]; let else_body = Some(vec![map_expr_to_stmt(current)]); - current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() }; + current = A::If { + condition: Box::new(cond), + then_body, + else_body, + span: Span::unknown(), + }; } Some(current) - } else { None } + } else { + None + } } -fn transform_peek_to_if_stmt_assign(peek: &nyash_rust::ASTNode, target: &nyash_rust::ASTNode) -> Option { +fn transform_peek_to_if_stmt_assign( + peek: &nyash_rust::ASTNode, + target: &nyash_rust::ASTNode, +) -> Option { use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span}; - if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek { + if let A::MatchExpr { + scrutinee, + arms, + else_expr, + .. + } = peek + { let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new(); - for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); } + for (lit, body) in arms { + pairs.push((lit.clone(), (*body).clone())); + } let mut current: A = *(*else_expr).clone(); for (lit, body) in pairs.into_iter().rev() { - let rhs = A::Literal { value: lit, span: Span::unknown() }; - let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() }; - let then_body = vec![A::Assignment { target: Box::new(target.clone()), value: Box::new(body), span: Span::unknown() }]; + let rhs = A::Literal { + value: lit, + span: Span::unknown(), + }; + let cond = A::BinaryOp { + operator: BinaryOperator::Equal, + left: scrutinee.clone(), + right: Box::new(rhs), + span: Span::unknown(), + }; + let then_body = vec![A::Assignment { + target: Box::new(target.clone()), + value: Box::new(body), + span: Span::unknown(), + }]; let else_body = Some(vec![map_expr_to_stmt(current)]); - current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() }; + current = A::If { + condition: Box::new(cond), + then_body, + else_body, + span: Span::unknown(), + }; } Some(current) - } else { None } + } else { + None + } } fn transform_peek_to_if_stmt_return(peek: &nyash_rust::ASTNode) -> Option { use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span}; - if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek { + if let A::MatchExpr { + scrutinee, + arms, + else_expr, + .. + } = peek + { let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new(); - for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); } + for (lit, body) in arms { + pairs.push((lit.clone(), (*body).clone())); + } let mut current: A = *(*else_expr).clone(); for (lit, body) in pairs.into_iter().rev() { - let rhs = A::Literal { value: lit, span: Span::unknown() }; - let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() }; - let then_body = vec![A::Return { value: Some(Box::new(body)), span: Span::unknown() }]; + let rhs = A::Literal { + value: lit, + span: Span::unknown(), + }; + let cond = A::BinaryOp { + operator: BinaryOperator::Equal, + left: scrutinee.clone(), + right: Box::new(rhs), + span: Span::unknown(), + }; + let then_body = vec![A::Return { + value: Some(Box::new(body)), + span: Span::unknown(), + }]; let else_body = Some(vec![map_expr_to_stmt(current)]); - current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() }; + current = A::If { + condition: Box::new(cond), + then_body, + else_body, + span: Span::unknown(), + }; } Some(current) - } else { None } + } else { + None + } } fn transform_peek_to_if_stmt_print(peek: &nyash_rust::ASTNode) -> Option { use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span}; - if let A::MatchExpr { scrutinee, arms, else_expr, .. } = peek { + if let A::MatchExpr { + scrutinee, + arms, + else_expr, + .. + } = peek + { let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new(); - for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); } + for (lit, body) in arms { + pairs.push((lit.clone(), (*body).clone())); + } let mut current: A = *(*else_expr).clone(); for (lit, body) in pairs.into_iter().rev() { - let rhs = A::Literal { value: lit, span: Span::unknown() }; - let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() }; - let then_body = vec![A::Print { expression: Box::new(body), span: Span::unknown() }]; + let rhs = A::Literal { + value: lit, + span: Span::unknown(), + }; + let cond = A::BinaryOp { + operator: BinaryOperator::Equal, + left: scrutinee.clone(), + right: Box::new(rhs), + span: Span::unknown(), + }; + let then_body = vec![A::Print { + expression: Box::new(body), + span: Span::unknown(), + }]; let else_body = Some(vec![map_expr_to_stmt(current)]); - current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() }; + current = A::If { + condition: Box::new(cond), + then_body, + else_body, + span: Span::unknown(), + }; } Some(current) - } else { None } + } else { + None + } } pub(super) fn transform_peek_match_literal(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::ASTNode as A; match ast.clone() { - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_peek_match_literal(&n)).collect(), span }, - A::If { condition, then_body, else_body, span } => A::If { - condition: Box::new(transform_peek_match_literal(&condition)), - then_body: then_body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(), - else_body: else_body.map(|v| v.into_iter().map(|n| transform_peek_match_literal(&n)).collect()), + A::Program { statements, span } => A::Program { + statements: statements + .into_iter() + .map(|n| transform_peek_match_literal(&n)) + .collect(), span, }, - A::Loop { condition, body, span } => A::Loop { + A::If { + condition, + then_body, + else_body, + span, + } => A::If { condition: Box::new(transform_peek_match_literal(&condition)), - body: body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(), + then_body: then_body + .into_iter() + .map(|n| transform_peek_match_literal(&n)) + .collect(), + else_body: else_body.map(|v| { + v.into_iter() + .map(|n| transform_peek_match_literal(&n)) + .collect() + }), span, }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_peek_match_literal(&left)), right: Box::new(transform_peek_match_literal(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_peek_match_literal(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_peek_match_literal(&object)), method, arguments: arguments.into_iter().map(|a| transform_peek_match_literal(&a)).collect(), span }, - A::FunctionCall { name, arguments, span } => { - if let Some(if_expr) = transform_peek_to_if_expr(&A::FunctionCall { name: name.clone(), arguments: arguments.clone(), span }) { + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(transform_peek_match_literal(&condition)), + body: body + .into_iter() + .map(|n| transform_peek_match_literal(&n)) + .collect(), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_peek_match_literal(&left)), + right: Box::new(transform_peek_match_literal(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_peek_match_literal(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_peek_match_literal(&object)), + method, + arguments: arguments + .into_iter() + .map(|a| transform_peek_match_literal(&a)) + .collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => { + if let Some(if_expr) = transform_peek_to_if_expr(&A::FunctionCall { + name: name.clone(), + arguments: arguments.clone(), + span, + }) { if_expr - } else { A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_peek_match_literal(&a)).collect(), span } } + } else { + A::FunctionCall { + name, + arguments: arguments + .into_iter() + .map(|a| transform_peek_match_literal(&a)) + .collect(), + span, + } + } } - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_peek_match_literal(&e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_peek_match_literal(&v))).collect(), span }, - A::Assignment { target, value, span } => { - if let Some(ifstmt) = transform_peek_to_if_stmt_assign(&value, &target) { ifstmt } - else { A::Assignment { target, value: Box::new(transform_peek_match_literal(&value)), span } } + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| transform_peek_match_literal(&e)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, transform_peek_match_literal(&v))) + .collect(), + span, + }, + A::Assignment { + target, + value, + span, + } => { + if let Some(ifstmt) = transform_peek_to_if_stmt_assign(&value, &target) { + ifstmt + } else { + A::Assignment { + target, + value: Box::new(transform_peek_match_literal(&value)), + span, + } + } } A::Return { value, span } => { if let Some(v) = &value { - if let Some(ifstmt) = transform_peek_to_if_stmt_return(v) { ifstmt } - else { A::Return { value: Some(Box::new(transform_peek_match_literal(v))), span } } - } else { A::Return { value: None, span } } + if let Some(ifstmt) = transform_peek_to_if_stmt_return(v) { + ifstmt + } else { + A::Return { + value: Some(Box::new(transform_peek_match_literal(v))), + span, + } + } + } else { + A::Return { value: None, span } + } } A::Print { expression, span } => { - if let Some(ifstmt) = transform_peek_to_if_stmt_print(&expression) { ifstmt } - else { A::Print { expression: Box::new(transform_peek_match_literal(&expression)), span } } + if let Some(ifstmt) = transform_peek_to_if_stmt_print(&expression) { + ifstmt + } else { + A::Print { + expression: Box::new(transform_peek_match_literal(&expression)), + span, + } + } } other => other, } } - diff --git a/src/runner/modes/macro_child/transforms/postfix.rs b/src/runner/modes/macro_child/transforms/postfix.rs index 8dfbe0fa..c8477c68 100644 --- a/src/runner/modes/macro_child/transforms/postfix.rs +++ b/src/runner/modes/macro_child/transforms/postfix.rs @@ -1,14 +1,74 @@ pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::{ASTNode as A, CatchClause, Span}; - fn map_vec(v: Vec) -> Vec { v.into_iter().map(|n| transform_postfix_handlers(&n)).collect() } + fn map_vec(v: Vec) -> Vec { + v.into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect() + } match ast.clone() { - A::Program { statements, span } => A::Program { statements: map_vec(statements), span }, - A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(transform_postfix_handlers(&condition)), then_body: map_vec(then_body), else_body: else_body.map(map_vec), span }, - A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_postfix_handlers(&condition)), body: map_vec(body), span }, - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_postfix_handlers(&left)), right: Box::new(transform_postfix_handlers(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_postfix_handlers(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_postfix_handlers(&object)), method, arguments: arguments.into_iter().map(|a| transform_postfix_handlers(&a)).collect(), span }, - A::FunctionCall { name, arguments, span } => { + A::Program { statements, span } => A::Program { + statements: map_vec(statements), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => A::If { + condition: Box::new(transform_postfix_handlers(&condition)), + then_body: map_vec(then_body), + else_body: else_body.map(map_vec), + span, + }, + A::Loop { + condition, + body, + span, + } => A::Loop { + condition: Box::new(transform_postfix_handlers(&condition)), + body: map_vec(body), + span, + }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_postfix_handlers(&left)), + right: Box::new(transform_postfix_handlers(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_postfix_handlers(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_postfix_handlers(&object)), + method, + arguments: arguments + .into_iter() + .map(|a| transform_postfix_handlers(&a)) + .collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => { let name_l = name.to_ascii_lowercase(); if name_l == "postfix_catch" { let mut args = arguments; @@ -17,17 +77,32 @@ pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rus let (type_opt, handler) = if args.len() == 1 { (None, args.remove(0)) } else if args.len() >= 2 { - let ty = match args.remove(0) { A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => Some(s), _ => None }; + let ty = match args.remove(0) { + A::Literal { + value: nyash_rust::ast::LiteralValue::String(s), + .. + } => Some(s), + _ => None, + }; (ty, args.remove(0)) } else { - (None, A::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: Span::unknown() }) + ( + None, + A::Literal { + value: nyash_rust::ast::LiteralValue::Integer(0), + span: Span::unknown(), + }, + ) }; if let A::Lambda { params, body, .. } = handler { if params.len() == 1 { let cc = CatchClause { exception_type: type_opt, variable_name: Some(params[0].clone()), - body: body.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), + body: body + .into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect(), span: Span::unknown(), }; return A::TryCatch { @@ -39,7 +114,14 @@ pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rus } } } - A::FunctionCall { name, arguments: args.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span } + A::FunctionCall { + name, + arguments: args + .into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect(), + span, + } } else if name_l == "with_cleanup" { let mut args = arguments; if args.len() >= 2 { @@ -50,20 +132,49 @@ pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rus return A::TryCatch { try_body: vec![expr], catch_clauses: vec![], - finally_body: Some(body.into_iter().map(|n| transform_postfix_handlers(&n)).collect()), + finally_body: Some( + body.into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect(), + ), span: Span::unknown(), }; } } } - A::FunctionCall { name, arguments: args.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span } + A::FunctionCall { + name, + arguments: args + .into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect(), + span, + } } else { - A::FunctionCall { name, arguments: arguments.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span } + A::FunctionCall { + name, + arguments: arguments + .into_iter() + .map(|n| transform_postfix_handlers(&n)) + .collect(), + span, + } } } - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_postfix_handlers(&e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, transform_postfix_handlers(&v))).collect(), span }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| transform_postfix_handlers(&e)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, transform_postfix_handlers(&v))) + .collect(), + span, + }, other => other, } } - diff --git a/src/runner/modes/macro_child/transforms/scopebox.rs b/src/runner/modes/macro_child/transforms/scopebox.rs index 3c59a74b..b16aaaeb 100644 --- a/src/runner/modes/macro_child/transforms/scopebox.rs +++ b/src/runner/modes/macro_child/transforms/scopebox.rs @@ -1,25 +1,122 @@ pub(super) fn transform_scopebox_inject(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode { use nyash_rust::ast::ASTNode as A; match ast.clone() { - A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span }, - A::If { condition, then_body, else_body, span } => { + A::Program { statements, span } => A::Program { + statements: statements + .into_iter() + .map(|n| transform_scopebox_inject(&n)) + .collect(), + span, + }, + A::If { + condition, + then_body, + else_body, + span, + } => { let cond = Box::new(transform_scopebox_inject(&condition)); - let then_wrapped = vec![A::ScopeBox { body: then_body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }]; - let else_wrapped = else_body.map(|v| vec![A::ScopeBox { body: v.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }]); - A::If { condition: cond, then_body: then_wrapped, else_body: else_wrapped, span } + let then_wrapped = vec![A::ScopeBox { + body: then_body + .into_iter() + .map(|n| transform_scopebox_inject(&n)) + .collect(), + span: nyash_rust::ast::Span::unknown(), + }]; + let else_wrapped = else_body.map(|v| { + vec![A::ScopeBox { + body: v + .into_iter() + .map(|n| transform_scopebox_inject(&n)) + .collect(), + span: nyash_rust::ast::Span::unknown(), + }] + }); + A::If { + condition: cond, + then_body: then_wrapped, + else_body: else_wrapped, + span, + } } - A::Loop { condition, body, span } => { + A::Loop { + condition, + body, + span, + } => { let cond = Box::new(transform_scopebox_inject(&condition)); - let body_wrapped = vec![A::ScopeBox { body: body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }]; - A::Loop { condition: cond, body: body_wrapped, span } + let body_wrapped = vec![A::ScopeBox { + body: body + .into_iter() + .map(|n| transform_scopebox_inject(&n)) + .collect(), + span: nyash_rust::ast::Span::unknown(), + }]; + A::Loop { + condition: cond, + body: body_wrapped, + span, + } } - A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_scopebox_inject(&left)), right: Box::new(transform_scopebox_inject(&right)), span }, - A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_scopebox_inject(&operand)), span }, - A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_scopebox_inject(&object)), method, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span }, - A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span }, - A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_scopebox_inject(&e)).collect(), span }, - A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_scopebox_inject(&v))).collect(), span }, + A::BinaryOp { + operator, + left, + right, + span, + } => A::BinaryOp { + operator, + left: Box::new(transform_scopebox_inject(&left)), + right: Box::new(transform_scopebox_inject(&right)), + span, + }, + A::UnaryOp { + operator, + operand, + span, + } => A::UnaryOp { + operator, + operand: Box::new(transform_scopebox_inject(&operand)), + span, + }, + A::MethodCall { + object, + method, + arguments, + span, + } => A::MethodCall { + object: Box::new(transform_scopebox_inject(&object)), + method, + arguments: arguments + .into_iter() + .map(|a| transform_scopebox_inject(&a)) + .collect(), + span, + }, + A::FunctionCall { + name, + arguments, + span, + } => A::FunctionCall { + name, + arguments: arguments + .into_iter() + .map(|a| transform_scopebox_inject(&a)) + .collect(), + span, + }, + A::ArrayLiteral { elements, span } => A::ArrayLiteral { + elements: elements + .into_iter() + .map(|e| transform_scopebox_inject(&e)) + .collect(), + span, + }, + A::MapLiteral { entries, span } => A::MapLiteral { + entries: entries + .into_iter() + .map(|(k, v)| (k, transform_scopebox_inject(&v))) + .collect(), + span, + }, other => other, } } - diff --git a/src/runner/modes/mir.rs b/src/runner/modes/mir.rs index c89c1bf7..8c48677c 100644 --- a/src/runner/modes/mir.rs +++ b/src/runner/modes/mir.rs @@ -21,7 +21,11 @@ impl NyashRunner { let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); process::exit(1); } }; @@ -71,7 +75,9 @@ impl NyashRunner { // Emit MIR JSON if requested and exit if let Some(path) = groups.emit.emit_mir_json.as_ref() { let p = std::path::Path::new(path); - if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness(&compile_result.module, p) { + if let Err(e) = + crate::runner::mir_json_emit::emit_mir_json_for_harness(&compile_result.module, p) + { eprintln!("❌ MIR JSON emit error: {}", e); std::process::exit(1); } diff --git a/src/runner/modes/mir_interpreter.rs b/src/runner/modes/mir_interpreter.rs index 9be8b297..694242a7 100644 --- a/src/runner/modes/mir_interpreter.rs +++ b/src/runner/modes/mir_interpreter.rs @@ -15,7 +15,14 @@ impl NyashRunner { // Parse to AST let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { eprintln!("❌ Parse error in {}: {}", filename, e); process::exit(1); } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); + process::exit(1); + } }; let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); diff --git a/src/runner/modes/mod.rs b/src/runner/modes/mod.rs index 782554c7..c977b891 100644 --- a/src/runner/modes/mod.rs +++ b/src/runner/modes/mod.rs @@ -1,10 +1,10 @@ // bench module removed with vm-legacy pub mod llvm; +pub mod macro_child; pub mod mir; +pub mod pyvm; pub mod vm; pub mod vm_fallback; -pub mod pyvm; -pub mod macro_child; // Shared helpers extracted from common.rs (in progress) pub mod common_util; diff --git a/src/runner/modes/pyvm.rs b/src/runner/modes/pyvm.rs index 6b86e794..465854ff 100644 --- a/src/runner/modes/pyvm.rs +++ b/src/runner/modes/pyvm.rs @@ -4,7 +4,9 @@ use std::{fs, process}; /// Execute using PyVM only (no Rust VM runtime). Emits MIR(JSON) and invokes tools/pyvm_runner.py. pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { - if crate::config::env::env_bool("NYASH_PYVM_TRACE") { eprintln!("[pyvm] entry"); } + if crate::config::env::env_bool("NYASH_PYVM_TRACE") { + eprintln!("[pyvm] entry"); + } // Read the file let code = match fs::read_to_string(filename) { Ok(content) => content, @@ -16,7 +18,9 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { // Using handling: AST-prelude collection (legacy inlining removed) let mut code = if crate::config::env::enable_using() { - match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(runner, &code, filename) { + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled( + runner, &code, filename, + ) { Ok((clean, paths)) => { if !paths.is_empty() && !crate::config::env::using_ast_enabled() { eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); @@ -25,9 +29,14 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { // PyVM pipeline currently does not merge prelude ASTs here; rely on main/common path for that. clean } - Err(e) => { eprintln!("❌ {}", e); process::exit(1); } + Err(e) => { + eprintln!("❌ {}", e); + process::exit(1); + } } - } else { code }; + } else { + code + }; // Dev sugar pre-expand: line-head @name[:T] = expr → local name[:T] = expr code = crate::runner::modes::common_util::resolve::preexpand_at_local(&code); @@ -42,31 +51,76 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { while let Some(c) = it.next() { if in_line { out.push(c); - if c == '\n' { in_line = false; } + if c == '\n' { + in_line = false; + } continue; } if in_block { out.push(c); - if c == '*' && matches!(it.peek(), Some('/')) { out.push('/'); it.next(); in_block = false; } + if c == '*' && matches!(it.peek(), Some('/')) { + out.push('/'); + it.next(); + in_block = false; + } continue; } if in_str { out.push(c); - if c == '\\' { if let Some(nc) = it.next() { out.push(nc); } continue; } - if c == '"' { in_str = false; } + if c == '\\' { + if let Some(nc) = it.next() { + out.push(nc); + } + continue; + } + if c == '"' { + in_str = false; + } continue; } match c { - '"' => { in_str = true; out.push(c); } - '/' => { - match it.peek() { Some('/') => { out.push('/'); out.push('/'); it.next(); in_line = true; }, Some('*') => { out.push('/'); out.push('*'); it.next(); in_block = true; }, _ => out.push('/') } + '"' => { + in_str = true; + out.push(c); + } + '/' => match it.peek() { + Some('/') => { + out.push('/'); + out.push('/'); + it.next(); + in_line = true; + } + Some('*') => { + out.push('/'); + out.push('*'); + it.next(); + in_block = true; + } + _ => out.push('/'), + }, + '#' => { + in_line = true; + out.push('#'); } - '#' => { in_line = true; out.push('#'); } '|' => { - if matches!(it.peek(), Some('|')) { out.push_str(" or "); it.next(); } else if matches!(it.peek(), Some('>')) { out.push('|'); out.push('>'); it.next(); } else { out.push('|'); } + if matches!(it.peek(), Some('|')) { + out.push_str(" or "); + it.next(); + } else if matches!(it.peek(), Some('>')) { + out.push('|'); + out.push('>'); + it.next(); + } else { + out.push('|'); + } } '&' => { - if matches!(it.peek(), Some('&')) { out.push_str(" and "); it.next(); } else { out.push('&'); } + if matches!(it.peek(), Some('&')) { + out.push_str(" and "); + it.next(); + } else { + out.push('&'); + } } _ => out.push(c), } @@ -79,13 +133,17 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { if crate::config::env::env_bool("NYASH_PYVM_DUMP_CODE") { eprintln!("[pyvm-code]\n{}", code); } - let ast = match NyashParser::parse_from_string(&code) { - Ok(ast) => ast, - Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); - process::exit(1); - } - }; + let ast = match NyashParser::parse_from_string(&code) { + Ok(ast) => ast, + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); + process::exit(1); + } + }; let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast); @@ -101,8 +159,11 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { // Optional: VM-only escape analysis elision pass retained for parity with VM path if crate::config::env::env_bool("NYASH_VM_ESCAPE_ANALYSIS") { - let removed = nyash_rust::mir::passes::escape::escape_elide_barriers_vm(&mut compile_result.module); - if removed > 0 { crate::cli_v!("[PyVM] escape_elide_barriers: removed {} barriers", removed); } + let removed = + nyash_rust::mir::passes::escape::escape_elide_barriers_vm(&mut compile_result.module); + if removed > 0 { + crate::cli_v!("[PyVM] escape_elide_barriers: removed {} barriers", removed); + } } // Optional: delegate to Ny selfhost executor (Stage 0 scaffold: no-op) @@ -111,12 +172,16 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_selfhost_mir.json"); - if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&compile_result.module, &mir_json_path) { + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin( + &compile_result.module, + &mir_json_path, + ) { eprintln!("❌ Selfhost MIR JSON emit error: {}", e); process::exit(1); } // Resolve nyash executable and runner path - let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash")); + let exe = std::env::current_exe() + .unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash")); let runner = std::path::Path::new("apps/selfhost-runtime/runner.hako"); if !runner.exists() { eprintln!("❌ Selfhost runner missing: {}", runner.display()); @@ -124,7 +189,8 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { } let mut cmd = std::process::Command::new(&exe); crate::runner::child_env::apply_core_wrapper_env(&mut cmd); - cmd.arg("--backend").arg("vm") + cmd.arg("--backend") + .arg("vm") .arg(runner) .arg("--") .arg(mir_json_path.display().to_string()); @@ -139,14 +205,25 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { // Avoid recursive selfhost delegation inside the child. .env_remove("NYASH_SELFHOST_EXEC") .status() - .unwrap_or_else(|e| { eprintln!("❌ spawn selfhost runner failed: {}", e); std::process::exit(1); }); + .unwrap_or_else(|e| { + eprintln!("❌ spawn selfhost runner failed: {}", e); + std::process::exit(1); + }); let code = status.code().unwrap_or(1); process::exit(code); } // Delegate to common PyVM harness - match crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib(&compile_result.module, "pyvm") { - Ok(code) => { process::exit(code); } - Err(e) => { eprintln!("❌ PyVM error: {}", e); process::exit(1); } + match crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib( + &compile_result.module, + "pyvm", + ) { + Ok(code) => { + process::exit(code); + } + Err(e) => { + eprintln!("❌ PyVM error: {}", e); + process::exit(1); + } } } diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 88866fb0..ec4dc51e 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -1,9 +1,5 @@ use super::super::NyashRunner; -use nyash_rust::{ - ast::ASTNode, - parser::NyashParser, - mir::MirCompiler, -}; +use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser}; use std::{fs, process}; impl NyashRunner { @@ -71,9 +67,7 @@ impl NyashRunner { // Centralized plugin guard let strict = crate::config::env::env_bool("NYASH_VM_PLUGIN_STRICT"); crate::runner::modes::common_util::plugin_guard::check_and_report( - strict, - quiet_pipe, - "vm", + strict, quiet_pipe, "vm", ); } @@ -97,17 +91,13 @@ impl NyashRunner { // When using is enabled, resolve preludes/profile; otherwise, keep original code. let mut code_final = if crate::config::env::enable_using() { match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled( - self, - &code, - filename, + self, &code, filename, ) { Ok((_, prelude_paths)) => { if !prelude_paths.is_empty() { // SSOT: always text-merge for VM (includes .hako-safe handling inside) match crate::runner::modes::common_util::resolve::merge_prelude_text( - self, - &code, - filename, + self, &code, filename, ) { Ok(merged) => { if trace { @@ -144,24 +134,18 @@ impl NyashRunner { }; // Dev sugar pre-expand: @name = expr → local name = expr - code_final = - crate::runner::modes::common_util::resolve::preexpand_at_local(&code_final); + code_final = crate::runner::modes::common_util::resolve::preexpand_at_local(&code_final); // Hako-friendly normalize: strip leading `local ` at line head for Nyash parser compatibility. if crate::runner::modes::common_util::hako::looks_like_hako_code(&code_final) || filename.ends_with(".hako") { - code_final = - crate::runner::modes::common_util::hako::strip_local_decl(&code_final); + code_final = crate::runner::modes::common_util::hako::strip_local_decl(&code_final); } // Optional: dump merged Hako source after using/prelude merge and Hako normalization. // Guarded by env; defaultはOFF(Phase 25.1a selfhost builder デバッグ用)。 - if std::env::var("NYASH_VM_DUMP_MERGED_HAKO") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_VM_DUMP_MERGED_HAKO").ok().as_deref() == Some("1") { let default_path = { let mut tmp = std::env::temp_dir(); tmp.push("nyash_merged_vm.hako"); @@ -176,15 +160,14 @@ impl NyashRunner { if trace { eprintln!("[vm/merged-hako] failed to write {}: {}", path, e); } - } else if trace - || crate::config::env::env_bool("NYASH_VM_DUMP_MERGED_HAKO_LOG") - { + } else if trace || crate::config::env::env_bool("NYASH_VM_DUMP_MERGED_HAKO_LOG") { eprintln!("[vm/merged-hako] dumped merged code to {}", path); } } - if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into()) - || std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into())) + if trace + && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into()) + || std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into())) { eprintln!("[vm] Stage-3: enabled (env) for {}", filename); } @@ -210,9 +193,12 @@ impl NyashRunner { let ast_combined = match NyashParser::parse_from_string(&code_final) { Ok(ast) => ast, Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, &code_final, &e, + ); // Enhanced context: list merged prelude files if any - let preludes = crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); + let preludes = + crate::runner::modes::common_util::resolve::clone_last_merged_preludes(); if !preludes.is_empty() { eprintln!("[parse/context] merged prelude files ({}):", preludes.len()); let show = std::cmp::min(16, preludes.len()); @@ -297,7 +283,8 @@ impl NyashRunner { type_parameters, is_static, .. - } = st { + } = st + { if *is_static { static_names.push(name.clone()); // Store static box declaration for VM singleton persistence @@ -372,7 +359,8 @@ impl NyashRunner { &self, name: &str, args: &[Box], - ) -> Result, RuntimeError> { + ) -> Result, RuntimeError> + { let opt = { self.decls.read().unwrap().get(name).cloned() }; let decl = match opt { Some(d) => d, @@ -408,7 +396,9 @@ impl NyashRunner { let factory = InlineUserBoxFactory { decls: Arc::new(RwLock::new(decls)), }; - crate::runtime::unified_registry::register_user_defined_factory(std::sync::Arc::new(factory)); + crate::runtime::unified_registry::register_user_defined_factory( + std::sync::Arc::new(factory), + ); } // Return static_box_decls for VM registration @@ -430,10 +420,7 @@ impl NyashRunner { if crate::config::env::env_bool("NYASH_VM_ESCAPE_ANALYSIS") { let removed = crate::mir::passes::escape::escape_elide_barriers_vm(&mut module_vm); if removed > 0 { - crate::cli_v!( - "[VM] escape_elide_barriers: removed {} barriers", - removed - ); + crate::cli_v!("[VM] escape_elide_barriers: removed {} barriers", removed); } } @@ -476,13 +463,17 @@ impl NyashRunner { match vm.execute_module(&module_vm) { Ok(ret) => { - use crate::box_trait::{IntegerBox, BoolBox}; + use crate::box_trait::{BoolBox, IntegerBox}; // Extract exit code from return value let exit_code = if let Some(ib) = ret.as_any().downcast_ref::() { ib.value as i32 } else if let Some(bb) = ret.as_any().downcast_ref::() { - if bb.value { 1 } else { 0 } + if bb.value { + 1 + } else { + 0 + } } else { // For non-integer/bool returns, default to 0 (success) 0 diff --git a/src/runner/modes/vm_fallback.rs b/src/runner/modes/vm_fallback.rs index 65f90553..70ddf7ff 100644 --- a/src/runner/modes/vm_fallback.rs +++ b/src/runner/modes/vm_fallback.rs @@ -35,16 +35,12 @@ impl NyashRunner { // - merge_prelude_text で text-merge(.hako は AST parse しない) let mut code2 = if crate::config::env::enable_using() { match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled( - self, - &code, - filename, + self, &code, filename, ) { Ok((_, prelude_paths)) => { if !prelude_paths.is_empty() { match crate::runner::modes::common_util::resolve::merge_prelude_text( - self, - &code, - filename, + self, &code, filename, ) { Ok(merged) => { if trace { @@ -90,8 +86,9 @@ impl NyashRunner { code2 = crate::runner::modes::common_util::hako::strip_local_decl(&code2); } - if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into()) - || std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into())) + if trace + && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into()) + || std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into())) { eprintln!("[vm-fallback] Stage-3: enabled (env) for {}", filename); } @@ -102,7 +99,9 @@ impl NyashRunner { let on = crate::runner::modes::common_util::hako::fail_fast_on_hako(); if on { let s = code2.as_str(); - let hako_like = s.contains("static box ") || s.contains("using selfhost.") || s.contains("using hakorune."); + let hako_like = s.contains("static box ") + || s.contains("using selfhost.") + || s.contains("using hakorune."); if hako_like { eprintln!( "❌ Hako-like source detected in Nyash VM path. Use Hakorune VM (v1 dispatcher) or Core/LLVM for MIR.\n hint: set HAKO_VERIFY_PRIMARY=hakovm in verify path" @@ -116,7 +115,11 @@ impl NyashRunner { let main_ast = match NyashParser::parse_from_string(&code2) { Ok(ast) => ast, Err(e) => { - eprintln!("❌ Parse error in {}: {}", filename, e); + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code2, + &e, + ); process::exit(1); } }; @@ -186,7 +189,8 @@ impl NyashRunner { type_parameters, is_static, .. - } = st { + } = st + { if *is_static { static_names.push(name.clone()); continue; // modules/static boxes are not user-instantiable directly @@ -308,7 +312,9 @@ impl NyashRunner { if let Err(errors) = verifier.verify_function(func) { if !errors.is_empty() { eprintln!("[vm-verify] function: {}", name); - for er in errors { eprintln!(" • {}", er); } + for er in errors { + eprintln!(" • {}", er); + } } } } @@ -321,13 +327,17 @@ impl NyashRunner { } match vm.execute_module(&module_vm) { Ok(ret) => { - use crate::box_trait::{IntegerBox, BoolBox}; + use crate::box_trait::{BoolBox, IntegerBox}; // Extract exit code from return value let exit_code = if let Some(ib) = ret.as_any().downcast_ref::() { ib.value as i32 } else if let Some(bb) = ret.as_any().downcast_ref::() { - if bb.value { 1 } else { 0 } + if bb.value { + 1 + } else { + 0 + } } else { // For non-integer/bool returns, default to 0 (success) 0 @@ -355,21 +365,54 @@ impl NyashRunner { instance_v2::InstanceBox, mir::MirCompiler, }; - use std::sync::{Arc, RwLock}; use std::process; + use std::sync::{Arc, RwLock}; // Macro expand (if enabled) let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); // Minimal user-defined Box support (inline factory) { use nyash_rust::ast::ASTNode; - let mut nonstatic_decls: std::collections::HashMap = std::collections::HashMap::new(); + let mut nonstatic_decls: std::collections::HashMap = + std::collections::HashMap::new(); let mut static_names: Vec = Vec::new(); if let ASTNode::Program { statements, .. } = &ast { for st in statements { - if let ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, .. } = st { - if *is_static { static_names.push(name.clone()); continue; } - let decl = CoreBoxDecl { name: name.clone(), fields: fields.clone(), public_fields: public_fields.clone(), private_fields: private_fields.clone(), methods: methods.clone(), constructors: constructors.clone(), init_fields: init_fields.clone(), weak_fields: weak_fields.clone(), is_interface: *is_interface, extends: extends.clone(), implements: implements.clone(), type_parameters: type_parameters.clone() }; + if let ASTNode::BoxDeclaration { + name, + fields, + public_fields, + private_fields, + methods, + constructors, + init_fields, + weak_fields, + is_interface, + extends, + implements, + type_parameters, + is_static, + .. + } = st + { + if *is_static { + static_names.push(name.clone()); + continue; + } + let decl = CoreBoxDecl { + name: name.clone(), + fields: fields.clone(), + public_fields: public_fields.clone(), + private_fields: private_fields.clone(), + methods: methods.clone(), + constructors: constructors.clone(), + init_fields: init_fields.clone(), + weak_fields: weak_fields.clone(), + is_interface: *is_interface, + extends: extends.clone(), + implements: implements.clone(), + type_parameters: type_parameters.clone(), + }; nonstatic_decls.insert(name.clone(), decl); } } @@ -390,7 +433,8 @@ impl NyashRunner { &self, name: &str, args: &[Box], - ) -> Result, RuntimeError> { + ) -> Result, RuntimeError> + { let opt = { self.decls.read().unwrap().get(name).cloned() }; let decl = match opt { Some(d) => d, @@ -409,8 +453,12 @@ impl NyashRunner { Ok(Box::new(inst)) } - fn box_types(&self) -> Vec<&str> { vec![] } - fn is_available(&self) -> bool { true } + fn box_types(&self) -> Vec<&str> { + vec![] + } + fn is_available(&self) -> bool { + true + } fn factory_type(&self) -> crate::box_factory::FactoryType { crate::box_factory::FactoryType::User } @@ -425,7 +473,10 @@ impl NyashRunner { let mut compiler = MirCompiler::with_options(!self.config.no_optimize); let module = match compiler.compile(ast) { Ok(r) => r.module, - Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); } + Err(e) => { + eprintln!("❌ MIR compilation error: {}", e); + process::exit(1); + } }; let mut interp = MirInterpreter::new(); match interp.execute_module(&module) { @@ -434,7 +485,11 @@ impl NyashRunner { let rc = if let Some(ib) = result.as_any().downcast_ref::() { ib.value as i32 } else if let Some(bb) = result.as_any().downcast_ref::() { - if bb.value { 1 } else { 0 } + if bb.value { + 1 + } else { + 0 + } } else { 0 }; diff --git a/src/runner/modes/wasm.rs b/src/runner/modes/wasm.rs index 648cc781..100b063a 100644 --- a/src/runner/modes/wasm.rs +++ b/src/runner/modes/wasm.rs @@ -17,7 +17,14 @@ impl NyashRunner { // Parse to AST let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { eprintln!("❌ Parse error in {}: {}", filename, e); process::exit(1); } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, + &code, + &e, + ); + process::exit(1); + } }; let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); diff --git a/src/runner/pipe_io.rs b/src/runner/pipe_io.rs index 57ca5a63..95168715 100644 --- a/src/runner/pipe_io.rs +++ b/src/runner/pipe_io.rs @@ -41,7 +41,8 @@ impl NyashRunner { match super::json_v0_bridge::parse_json_v0_to_module(&json) { Ok(module) => { let p = std::path::Path::new(out); - if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, p) { + if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, p) + { eprintln!("❌ Program→MIR emit error: {}", e); std::process::exit(1); } @@ -61,16 +62,21 @@ impl NyashRunner { let runner = std::path::Path::new("tools/pyvm_runner.py"); if runner.exists() { // We need a MIR module for PyVM: try v1 bridge first, then v0 parse - if let Ok(Some(module)) = crate::runner::json_v1_bridge::try_parse_v1_to_module(&json) { + if let Ok(Some(module)) = + crate::runner::json_v1_bridge::try_parse_v1_to_module(&json) + { super::json_v0_bridge::maybe_dump_mir(&module); let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); - if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin( + &module, + &mir_json_path, + ) { eprintln!("❌ PyVM MIR JSON emit error: {}", e); std::process::exit(1); } - crate::cli_v!("[Bridge] using PyVM (pipe) → {}", mir_json_path.display()); + crate::cli_v!("[Bridge] using PyVM (pipe) → {}", mir_json_path.display()); // Determine entry function let allow_top = crate::config::env::entry_allow_toplevel_main(); let entry = if module.functions.contains_key("Main.main") { @@ -80,7 +86,9 @@ impl NyashRunner { } else if module.functions.contains_key("main") { eprintln!("[entry] Warning: using top-level 'main' without explicit allow; set NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 to silence."); "main" - } else { "Main.main" }; + } else { + "Main.main" + }; let mut cmd = std::process::Command::new(py3); crate::runner::child_env::apply_core_wrapper_env(&mut cmd); let status = cmd @@ -95,7 +103,9 @@ impl NyashRunner { .map_err(|e| format!("spawn pyvm: {}", e)) .unwrap(); let code = status.code().unwrap_or(1); - if !status.success() { crate::cli_v!("❌ PyVM (pipe) failed (status={})", code); } + if !status.success() { + crate::cli_v!("❌ PyVM (pipe) failed (status={})", code); + } std::process::exit(code); } // v0 fallback for PyVM @@ -104,11 +114,17 @@ impl NyashRunner { let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); - if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin( + &module, + &mir_json_path, + ) { eprintln!("❌ PyVM MIR JSON emit error: {}", e); std::process::exit(1); } - crate::cli_v!("[Bridge] using PyVM (pipe, v0) → {}", mir_json_path.display()); + crate::cli_v!( + "[Bridge] using PyVM (pipe, v0) → {}", + mir_json_path.display() + ); let mut cmd = std::process::Command::new(py3); crate::runner::child_env::apply_core_wrapper_env(&mut cmd); let status = cmd @@ -121,7 +137,9 @@ impl NyashRunner { .map_err(|e| format!("spawn pyvm: {}", e)) .unwrap(); let code = status.code().unwrap_or(1); - if !status.success() { crate::cli_v!("❌ PyVM (pipe,v0) failed (status={})", code); } + if !status.success() { + crate::cli_v!("❌ PyVM (pipe,v0) failed (status={})", code); + } std::process::exit(code); } Err(e) => { diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs index abba1630..ca50c8dc 100644 --- a/src/runner/pipeline.rs +++ b/src/runner/pipeline.rs @@ -8,9 +8,9 @@ */ use super::*; -use std::collections::HashMap; -use crate::using::spec::{UsingPackage, PackageKind}; +use crate::using::spec::{PackageKind, UsingPackage}; use crate::using::ssot_bridge::{call_using_resolve_ssot, SsotCtx}; +use std::collections::HashMap; /// Using/module resolution context accumulated from config/env/nyash.toml pub(super) struct UsingContext { @@ -149,13 +149,18 @@ pub(super) fn resolve_using_target( } // Phase 22.1: Thin SSOT hook (future wiring). No behavior change for now. if std::env::var("HAKO_USING_SSOT").ok().as_deref() == Some("1") - && std::env::var("HAKO_USING_SSOT_INVOKING") - .ok() - .as_deref() - != Some("1") + && std::env::var("HAKO_USING_SSOT_INVOKING").ok().as_deref() != Some("1") { if let Some(ssot_res) = try_resolve_using_target_ssot( - tgt, is_path, modules, using_paths, aliases, packages, context_dir, strict, verbose, + tgt, + is_path, + modules, + using_paths, + aliases, + packages, + context_dir, + strict, + verbose, ) { return Ok(ssot_res); } @@ -220,7 +225,9 @@ pub(super) fn resolve_using_target( let mut cur = tgt.to_string(); let mut depth = 0usize; while let Some(next) = aliases.get(&cur).cloned() { - if trace { crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", cur, next)); } + if trace { + crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", cur, next)); + } if !seen.insert(cur.clone()) { return Err(format!("alias cycle detected at '{}'", cur)); } @@ -230,10 +237,22 @@ pub(super) fn resolve_using_target( return Err(format!("alias resolution too deep starting at '{}'", tgt)); } // Continue while next is also an alias; break when concrete - if !aliases.contains_key(&cur) { break; } + if !aliases.contains_key(&cur) { + break; + } } // Recurse once into final target to materialize path/token - let rec = resolve_using_target(&cur, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?; + let rec = resolve_using_target( + &cur, + false, + modules, + using_paths, + aliases, + packages, + context_dir, + strict, + verbose, + )?; crate::runner::box_index::cache_put(&key, rec.clone()); return Ok(rec); } @@ -244,7 +263,10 @@ pub(super) fn resolve_using_target( // Return a marker token to avoid inlining attempts; loader will consume later stages let out = format!("dylib:{}", pkg.path); if trace { - crate::runner::trace::log(format!("[using/resolve] dylib '{}' -> '{}'", tgt, out)); + crate::runner::trace::log(format!( + "[using/resolve] dylib '{}' -> '{}'", + tgt, out + )); } crate::runner::box_index::cache_put(&key, out.clone()); return Ok(out); @@ -253,25 +275,39 @@ pub(super) fn resolve_using_target( // Compute entry: main or .hako let base = std::path::Path::new(&pkg.path); let out = if let Some(m) = &pkg.main { - if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { // path is a file; ignore main and use as-is pkg.path.clone() } else { base.join(m).to_string_lossy().to_string() } } else { - if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { pkg.path.clone() } else { let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(tgt); // prefer .hako when package path points to a directory without explicit main let hako = base.join(format!("{}.hako", leaf)); - if hako.exists() { hako.to_string_lossy().to_string() } - else { base.join(format!("{}.hako", leaf)).to_string_lossy().to_string() } + if hako.exists() { + hako.to_string_lossy().to_string() + } else { + base.join(format!("{}.hako", leaf)) + .to_string_lossy() + .to_string() + } } }; if trace { - crate::runner::trace::log(format!("[using/resolve] package '{}' -> '{}'", tgt, out)); + crate::runner::trace::log(format!( + "[using/resolve] package '{}' -> '{}'", + tgt, out + )); } crate::runner::box_index::cache_put(&key, out.clone()); return Ok(out); @@ -321,8 +357,11 @@ pub(super) fn resolve_using_target( } Err(e) => { // Maintain previous behavior: return original name and log when unresolved - if trace { crate::runner::trace::log(format!("[using] unresolved '{}' ({})", tgt, e)); } - else { eprintln!("[using] not found: '{}'", tgt); } + if trace { + crate::runner::trace::log(format!("[using] unresolved '{}' ({})", tgt, e)); + } else { + eprintln!("[using] not found: '{}'", tgt); + } Ok(tgt.to_string()) } } @@ -349,7 +388,11 @@ fn try_resolve_using_target_ssot( map.insert(k.clone(), v.clone()); } let cwd_str = context_dir.and_then(|p| p.to_str()).map(|s| s.to_string()); - let ctx = SsotCtx { modules: map, using_paths: using_paths.to_vec(), cwd: cwd_str }; + let ctx = SsotCtx { + modules: map, + using_paths: using_paths.to_vec(), + cwd: cwd_str, + }; if let Some(hit) = call_using_resolve_ssot(tgt, &ctx) { if trace { crate::runner::trace::log(format!("[using/ssot] '{}' -> '{}'", tgt, hit)); @@ -412,9 +455,21 @@ fn try_resolve_using_target_ssot( let prev = std::env::var("HAKO_USING_SSOT_INVOKING").ok(); std::env::set_var("HAKO_USING_SSOT_INVOKING", "1"); let res = resolve_using_target( - tgt, is_path, modules, using_paths, aliases, packages, context_dir, strict, verbose, + tgt, + is_path, + modules, + using_paths, + aliases, + packages, + context_dir, + strict, + verbose, ); - if let Some(val) = prev { std::env::set_var("HAKO_USING_SSOT_INVOKING", val); } else { let _ = std::env::remove_var("HAKO_USING_SSOT_INVOKING"); } + if let Some(val) = prev { + std::env::set_var("HAKO_USING_SSOT_INVOKING", val); + } else { + let _ = std::env::remove_var("HAKO_USING_SSOT_INVOKING"); + } res.ok() } diff --git a/src/runner/plugins.rs b/src/runner/plugins.rs index c71c47b3..68665a6e 100644 --- a/src/runner/plugins.rs +++ b/src/runner/plugins.rs @@ -45,22 +45,49 @@ impl NyashRunner { } // Optional Ny script plugins loader (best-effort) - if groups.load_ny_plugins || std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1") { + if groups.load_ny_plugins + || std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1") + { if let Ok(text) = std::fs::read_to_string("nyash.toml") { if let Ok(doc) = toml::from_str::(&text) { if let Some(np) = doc.get("ny_plugins") { let mut list: Vec = Vec::new(); - if let Some(arr) = np.as_array() { for v in arr { if let Some(s) = v.as_str() { list.push(s.to_string()); } } } - else if let Some(tbl) = np.as_table() { for (_k, v) in tbl { if let Some(s) = v.as_str() { list.push(s.to_string()); } else if let Some(arr) = v.as_array() { for e in arr { if let Some(s) = e.as_str() { list.push(s.to_string()); } } } } } + if let Some(arr) = np.as_array() { + for v in arr { + if let Some(s) = v.as_str() { + list.push(s.to_string()); + } + } + } else if let Some(tbl) = np.as_table() { + for (_k, v) in tbl { + if let Some(s) = v.as_str() { + list.push(s.to_string()); + } else if let Some(arr) = v.as_array() { + for e in arr { + if let Some(s) = e.as_str() { + list.push(s.to_string()); + } + } + } + } + } if !list.is_empty() { - let list_only = std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref() == Some("1"); + let list_only = + std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref() + == Some("1"); println!("🧩 Ny script plugins ({}):", list.len()); for p in list { - if list_only { println!(" • {}", p); continue; } + if list_only { + println!(" • {}", p); + continue; + } match std::fs::read_to_string(&p) { Ok(_code) => { // Legacy interpreter removed - ny_plugins execution disabled - println!("[ny_plugins] {}: SKIP (legacy interpreter removed)", p); + println!( + "[ny_plugins] {}: SKIP (legacy interpreter removed)", + p + ); } Err(e) => println!("[ny_plugins] {}: FAIL (read: {})", p, e), } @@ -74,7 +101,10 @@ impl NyashRunner { // Provider verify (受け口): env で warn/strict のみ動作(未設定時は無処理) match crate::runtime::provider_verify::verify_from_env() { Ok(()) => {} - Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); } + Err(e) => { + eprintln!("❌ {}", e); + std::process::exit(1); + } } // Provider Lock — lock after registry and plugins are initialized (受け口) diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs index 93d8fcdf..89ad7726 100644 --- a/src/runner/selfhost.rs +++ b/src/runner/selfhost.rs @@ -54,7 +54,9 @@ impl NyashRunner { let using_ast = crate::config::env::using_ast_enabled(); if using_ast { // Text-based merge: faster for inline/selfhost execution - match crate::runner::modes::common_util::resolve::merge_prelude_text(self, &code, filename) { + match crate::runner::modes::common_util::resolve::merge_prelude_text( + self, &code, filename, + ) { Ok(merged) => { code_ref = std::borrow::Cow::Owned(merged); } @@ -65,7 +67,9 @@ impl NyashRunner { } } else { // Legacy: strip only (no prelude merge) - match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) { + match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled( + self, &code, filename, + ) { Ok((clean, paths)) => { if !paths.is_empty() { eprintln!("[ny-compiler] using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."); @@ -73,14 +77,18 @@ impl NyashRunner { } code_ref = std::borrow::Cow::Owned(clean); } - Err(e) => { eprintln!("[ny-compiler] {}", e); return false; } + Err(e) => { + eprintln!("[ny-compiler] {}", e); + return false; + } } } } // Promote dev sugar to standard: pre-expand line-head '@name[:T] = expr' to 'local name[:T] = expr' { - let expanded = crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref.as_ref()); + let expanded = + crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref.as_ref()); code_ref = std::borrow::Cow::Owned(expanded); } @@ -98,47 +106,56 @@ impl NyashRunner { { let preenv = std::env::var("NYASH_MACRO_SELFHOST_PRE_EXPAND") .ok() - .or_else(|| if crate::r#macro::enabled() { Some("auto".to_string()) } else { None }); + .or_else(|| { + if crate::r#macro::enabled() { + Some("auto".to_string()) + } else { + None + } + }); let do_pre = match preenv.as_deref() { Some("1") => true, Some("auto") => crate::r#macro::enabled() && crate::config::env::vm_use_py(), _ => false, }; if do_pre && crate::r#macro::enabled() { - crate::cli_v!("[ny-compiler] selfhost macro pre-expand: engaging (mode={:?})", preenv); - match NyashParser::parse_from_string(code_ref.as_ref()) { - Ok(ast0) => { - let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false); - // Compile to MIR and execute (respect VM/PyVM policy similar to vm mode) - let mut mir_compiler = MirCompiler::with_options(true); - match mir_compiler.compile(ast) { - Ok(result) => { - let prefer_pyvm = crate::config::env::vm_use_py(); - if prefer_pyvm { - if let Ok(code) = crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib(&result.module, "selfhost-preexpand") { + crate::cli_v!( + "[ny-compiler] selfhost macro pre-expand: engaging (mode={:?})", + preenv + ); + match NyashParser::parse_from_string(code_ref.as_ref()) { + Ok(ast0) => { + let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false); + // Compile to MIR and execute (respect VM/PyVM policy similar to vm mode) + let mut mir_compiler = MirCompiler::with_options(true); + match mir_compiler.compile(ast) { + Ok(result) => { + let prefer_pyvm = crate::config::env::vm_use_py(); + if prefer_pyvm { + if let Ok(code) = crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib(&result.module, "selfhost-preexpand") { println!("Result: {}", code); std::process::exit(code); } else { eprintln!("❌ PyVM error (selfhost-preexpand)"); std::process::exit(1); } - } else { - // For now, only PyVM path is supported in pre-expand mode; fall back otherwise. - crate::cli_v!("[ny-compiler] pre-expand path requires NYASH_VM_USE_PY=1; falling back to default selfhost"); + } else { + // For now, only PyVM path is supported in pre-expand mode; fall back otherwise. + crate::cli_v!("[ny-compiler] pre-expand path requires NYASH_VM_USE_PY=1; falling back to default selfhost"); + return false; + } + } + Err(e) => { + eprintln!("[ny-compiler] pre-expand compile error: {}", e); return false; } } - Err(e) => { - eprintln!("[ny-compiler] pre-expand compile error: {}", e); - return false; - } + } + Err(e) => { + eprintln!("[ny-compiler] pre-expand parse error: {}", e); + return false; } } - Err(e) => { - eprintln!("[ny-compiler] pre-expand parse error: {}", e); - return false; - } - } } } let tmp_path = tmp_dir.join("ny_parser_input.ny"); @@ -194,7 +211,9 @@ impl NyashRunner { .collect(); if !items.is_empty() { extra_owned.push("--".to_string()); - for it in items { extra_owned.push(it); } + for it in items { + extra_owned.push(it); + } } } let extra: Vec<&str> = extra_owned.iter().map(|s| s.as_str()).collect(); @@ -204,10 +223,7 @@ impl NyashRunner { parser_prog, timeout_ms, &extra, - &[ - "NYASH_USE_NY_COMPILER", - "NYASH_CLI_VERBOSE", - ], + &["NYASH_USE_NY_COMPILER", "NYASH_CLI_VERBOSE"], &[ ("NYASH_JSON_ONLY", "1"), ("NYASH_DISABLE_PLUGINS", "1"), @@ -220,9 +236,9 @@ impl NyashRunner { match json::parse_json_v0_line(&line) { Ok(module) => { if crate::config::env::cli_verbose() { - if crate::config::env::cli_verbose() { - super::json_v0_bridge::maybe_dump_mir(&module); - } + if crate::config::env::cli_verbose() { + super::json_v0_bridge::maybe_dump_mir(&module); + } } let emit_only = crate::config::env::ny_compiler_emit_only(); if emit_only { @@ -230,14 +246,14 @@ impl NyashRunner { } // Prefer PyVM path when requested if crate::config::env::vm_use_py() { - if let Some(code) = crate::runner::modes::common_util::selfhost::json::run_pyvm_module(&module, "selfhost") { + if let Some(code) = crate::runner::modes::common_util::selfhost::json::run_pyvm_module(&module, "selfhost") { println!("Result: {}", code); std::process::exit(code); } - } - self.execute_mir_module(&module); - return true; } + self.execute_mir_module(&module); + return true; + } Err(e) => { eprintln!("[ny-compiler] json parse error (child): {}", e); } @@ -260,10 +276,14 @@ impl NyashRunner { .ok() .and_then(|s| s.parse().ok()) .unwrap_or(60000); // Phase 25.1b: Increased to 60000ms (60s) for consistency - let out = match super::modes::common_util::io::spawn_with_timeout(cmd, timeout_ms) { - Ok(o) => o, - Err(e) => { eprintln!("[ny-compiler] python harness failed: {}", e); return false; } - }; + let out = + match super::modes::common_util::io::spawn_with_timeout(cmd, timeout_ms) { + Ok(o) => o, + Err(e) => { + eprintln!("[ny-compiler] python harness failed: {}", e); + return false; + } + }; if !out.timed_out { if let Ok(s) = String::from_utf8(out.stdout) { if let Some(line) = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&s) { @@ -340,14 +360,18 @@ impl NyashRunner { .ok() .and_then(|s| s.parse().ok()) .unwrap_or(2000); - if let Some(module) = super::modes::common_util::selfhost_exe::exe_try_parse_json_v0(filename, timeout_ms) { + if let Some(module) = super::modes::common_util::selfhost_exe::exe_try_parse_json_v0( + filename, timeout_ms, + ) { if crate::config::env::cli_verbose() { super::json_v0_bridge::maybe_dump_mir(&module); } let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY") .unwrap_or_else(|_| "1".to_string()) == "1"; - if emit_only { return false; } + if emit_only { + return false; + } // Prefer PyVM when requested (reference semantics) if std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") { if let Ok(py3) = which::which("python3") { @@ -356,20 +380,40 @@ impl NyashRunner { let tmp_dir = std::path::Path::new("tmp"); let _ = std::fs::create_dir_all(tmp_dir); let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); - if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + if let Err(e) = + crate::runner::mir_json_emit::emit_mir_json_for_harness_bin( + &module, + &mir_json_path, + ) + { eprintln!("❌ PyVM MIR JSON emit error: {}", e); process::exit(1); } - crate::cli_v!("[Bridge] using PyVM (selfhost) → {}", mir_json_path.display()); + crate::cli_v!( + "[Bridge] using PyVM (selfhost) → {}", + mir_json_path.display() + ); let allow_top = crate::config::env::entry_allow_toplevel_main(); - let entry = if module.functions.contains_key("Main.main") { "Main.main" } - else if allow_top && module.functions.contains_key("main") { "main" } - else if module.functions.contains_key("main") { eprintln!("[entry] Warning: using top-level 'main' without explicit allow; set NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 to silence."); "main" } - else { "Main.main" }; + let entry = if module.functions.contains_key("Main.main") { + "Main.main" + } else if allow_top && module.functions.contains_key("main") { + "main" + } else if module.functions.contains_key("main") { + eprintln!("[entry] Warning: using top-level 'main' without explicit allow; set NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 to silence."); + "main" + } else { + "Main.main" + }; let mut cmd = std::process::Command::new(py3); crate::runner::child_env::apply_core_wrapper_env(&mut cmd); let status = cmd - .args(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry]) + .args([ + "tools/pyvm_runner.py", + "--in", + &mir_json_path.display().to_string(), + "--entry", + entry, + ]) .status() .map_err(|e| format!("spawn pyvm: {}", e)) .unwrap(); @@ -382,11 +426,13 @@ impl NyashRunner { crate::runner::child_env::pre_run_reset_oob_if_strict(); crate::runner::child_env::pre_run_reset_oob_if_strict(); self.execute_mir_module(&module); - if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { + if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() + { eprintln!("[selfhost][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } - if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { + if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() + { eprintln!("[selfhost][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } @@ -419,28 +465,37 @@ impl NyashRunner { if emit_only { return false; } - // Phase-15 policy: when NYASH_VM_USE_PY=1, prefer PyVM as reference executor - // regardless of BoxCall presence to ensure semantics parity (e.g., PHI merges). - let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1"); - // Backward compatibility: if not preferring PyVM explicitly, still auto-enable when BoxCalls exist. - let needs_pyvm = !prefer_pyvm - && module.functions.values().any(|f| { - f.blocks.values().any(|bb| { - bb.instructions.iter().any(|inst| { - matches!(inst, crate::mir::MirInstruction::BoxCall { .. }) + // Phase-15 policy: when NYASH_VM_USE_PY=1, prefer PyVM as reference executor + // regardless of BoxCall presence to ensure semantics parity (e.g., PHI merges). + let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1"); + // Backward compatibility: if not preferring PyVM explicitly, still auto-enable when BoxCalls exist. + let needs_pyvm = !prefer_pyvm + && module.functions.values().any(|f| { + f.blocks.values().any(|bb| { + bb.instructions.iter().any(|inst| { + matches!(inst, crate::mir::MirInstruction::BoxCall { .. }) + }) }) - }) - }); + }); if prefer_pyvm || needs_pyvm { - let label = if prefer_pyvm { "selfhost" } else { "selfhost-fallback" }; - if let Some(code) = crate::runner::modes::common_util::selfhost::json::run_pyvm_module(&module, label) { + let label = if prefer_pyvm { + "selfhost" + } else { + "selfhost-fallback" + }; + if let Some(code) = + crate::runner::modes::common_util::selfhost::json::run_pyvm_module( + &module, label, + ) + { println!("Result: {}", code); std::process::exit(code); } } crate::runner::child_env::pre_run_reset_oob_if_strict(); self.execute_mir_module(&module); - if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { + if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() + { eprintln!("[selfhost][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } diff --git a/src/runner_plugin_init.rs b/src/runner_plugin_init.rs index 382f6ae9..e1dce5b9 100644 --- a/src/runner_plugin_init.rs +++ b/src/runner_plugin_init.rs @@ -21,9 +21,13 @@ fn resolve_plugin_toml() -> String { } if let Ok(root) = std::env::var("NYASH_ROOT") { let p = std::path::Path::new(&root).join("hakorune.toml"); - if p.exists() { return p.to_string_lossy().to_string(); } + if p.exists() { + return p.to_string_lossy().to_string(); + } let p2 = std::path::Path::new(&root).join("nyash.toml"); - if p2.exists() { return p2.to_string_lossy().to_string(); } + if p2.exists() { + return p2.to_string_lossy().to_string(); + } } "nyash.toml".to_string() } @@ -68,11 +72,15 @@ pub fn init_bid_plugins() { if std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") && std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") { - if plugin_debug || cli_verbose { eprintln!("[using.dylib/autoload] scanning nyash.toml packages …"); } + if plugin_debug || cli_verbose { + eprintln!("[using.dylib/autoload] scanning nyash.toml packages …"); + } let mut using_paths: Vec = Vec::new(); let mut pending_modules: std::vec::Vec<(String, String)> = Vec::new(); - let mut aliases: std::collections::HashMap = std::collections::HashMap::new(); - let mut packages: std::collections::HashMap = std::collections::HashMap::new(); + let mut aliases: std::collections::HashMap = + std::collections::HashMap::new(); + let mut packages: std::collections::HashMap = + std::collections::HashMap::new(); let _ = crate::using::resolver::populate_from_toml( &mut using_paths, &mut pending_modules, @@ -97,7 +105,10 @@ pub fn init_bid_plugins() { eprintln!("[using.dylib/autoload] failed '{}': {}", lib_name, e); } } else if plugin_debug || cli_verbose { - eprintln!("[using.dylib/autoload] loaded '{}' from {}", lib_name, pkg.path); + eprintln!( + "[using.dylib/autoload] loaded '{}' from {}", + lib_name, pkg.path + ); } } } diff --git a/src/runtime/deprecations.rs b/src/runtime/deprecations.rs index 8c77af22..7e8161e7 100644 --- a/src/runtime/deprecations.rs +++ b/src/runtime/deprecations.rs @@ -17,4 +17,3 @@ pub fn warn_nyash_toml_used_once() { "[deprecate] using nyash.toml; please rename to hako.toml", ); } - diff --git a/src/runtime/gc.rs b/src/runtime/gc.rs index b96c7310..75853413 100644 --- a/src/runtime/gc.rs +++ b/src/runtime/gc.rs @@ -35,9 +35,13 @@ pub struct CountingGc { } impl CountingGc { - pub fn new() -> Self { Self::new_with_mode(crate::runtime::gc_mode::GcMode::RcCycle) } + pub fn new() -> Self { + Self::new_with_mode(crate::runtime::gc_mode::GcMode::RcCycle) + } pub fn new_with_mode(mode: crate::runtime::gc_mode::GcMode) -> Self { - Self { inner: crate::runtime::gc_controller::GcController::new(mode) } + Self { + inner: crate::runtime::gc_controller::GcController::new(mode), + } } pub fn snapshot(&self) -> (u64, u64, u64) { self.inner.snapshot() diff --git a/src/runtime/gc_controller.rs b/src/runtime/gc_controller.rs index 772c7eaf..b2b559f9 100644 --- a/src/runtime/gc_controller.rs +++ b/src/runtime/gc_controller.rs @@ -84,8 +84,14 @@ impl GcHooks for GcController { if sp_hit || bytes_hit { // Record reason flags for diagnostics let mut flags: u64 = 0; - if sp_hit { flags |= 1; self.collect_by_sp.fetch_add(1, Ordering::Relaxed); } - if bytes_hit { flags |= 2; self.collect_by_alloc.fetch_add(1, Ordering::Relaxed); } + if sp_hit { + flags |= 1; + self.collect_by_sp.fetch_add(1, Ordering::Relaxed); + } + if bytes_hit { + flags |= 2; + self.collect_by_alloc.fetch_add(1, Ordering::Relaxed); + } self.trial_reason_last.store(flags, Ordering::Relaxed); self.run_trial_collection(); } @@ -114,8 +120,7 @@ impl GcHooks for GcController { } self.alloc_count.fetch_add(1, Ordering::Relaxed); self.alloc_bytes.fetch_add(bytes, Ordering::Relaxed); - self.bytes_since_last - .fetch_add(bytes, Ordering::Relaxed); + self.bytes_since_last.fetch_add(bytes, Ordering::Relaxed); } } @@ -202,5 +207,7 @@ impl GcController { pub fn trial_duration_last_ms(&self) -> u64 { self.trial_duration_last_ms.load(Ordering::Relaxed) } - pub fn trial_reason_last_bits(&self) -> u64 { self.trial_reason_last.load(Ordering::Relaxed) } + pub fn trial_reason_last_bits(&self) -> u64 { + self.trial_reason_last.load(Ordering::Relaxed) + } } diff --git a/src/runtime/gc_mode.rs b/src/runtime/gc_mode.rs index 4d0a714d..6716e652 100644 --- a/src/runtime/gc_mode.rs +++ b/src/runtime/gc_mode.rs @@ -31,4 +31,3 @@ impl GcMode { } } } - diff --git a/src/runtime/gc_trace.rs b/src/runtime/gc_trace.rs index 2dbab311..b9fa801b 100644 --- a/src/runtime/gc_trace.rs +++ b/src/runtime/gc_trace.rs @@ -32,4 +32,3 @@ pub fn trace_children(obj: &dyn NyashBox, visit: &mut dyn FnMut(Arc 0 { st.scope_depth -= 1; } - if st.scope_depth == 0 { do_join = true; } + if st.scope_depth == 0 { + do_join = true; + } // Pop explicit group for this scope popped = st.group_stack.pop(); } diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 49b82cdd..9208d9c8 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -3,6 +3,7 @@ //! プラグインシステムとBox管理の中核 pub mod box_registry; +pub mod deprecations; pub mod gc; pub mod gc_controller; pub mod gc_mode; @@ -10,19 +11,18 @@ pub mod gc_trace; pub mod global_hooks; pub mod leak_tracker; pub mod nyash_runtime; +pub mod observe; // Lightweight observability flags (OOB etc.) pub mod plugin_config; pub mod plugin_ffi_common; pub mod plugin_loader_unified; pub mod plugin_loader_v2; -pub mod scheduler; -pub mod semantics; -pub mod unified_registry; pub mod provider_lock; pub mod provider_verify; -pub mod observe; // Lightweight observability flags (OOB etc.) -pub mod deprecations; // Deprecation warnings with warn-once guards -// pub mod plugin_box; // legacy - 古いPluginBox -// pub mod plugin_loader; // legacy - Host VTable使用 +pub mod scheduler; +pub mod semantics; +pub mod unified_registry; // Deprecation warnings with warn-once guards + // pub mod plugin_box; // legacy - 古いPluginBox + // pub mod plugin_loader; // legacy - Host VTable使用 pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ pub mod host_api; // C ABI: plugins -> host 逆呼び出しAPI(TLSでVMに橋渡し) pub mod host_handles; // C ABI(TLV) 向け HostHandle レジストリ(ユーザー/内蔵Box受け渡し) diff --git a/src/runtime/observe.rs b/src/runtime/observe.rs index 127903a3..9ee2ffe7 100644 --- a/src/runtime/observe.rs +++ b/src/runtime/observe.rs @@ -19,4 +19,3 @@ pub fn mark_oob() { pub fn oob_seen() -> bool { OOB_SEEN.load(Ordering::Relaxed) } - diff --git a/src/runtime/plugin_loader_unified.rs b/src/runtime/plugin_loader_unified.rs index f4cf5691..cbd3dd66 100644 --- a/src/runtime/plugin_loader_unified.rs +++ b/src/runtime/plugin_loader_unified.rs @@ -83,7 +83,12 @@ impl PluginHost { /// Load a single library directly from path for `using kind="dylib"` autoload. /// Boxes list is best-effort (may be empty). When empty, TypeBox FFI is used to resolve metadata. - pub fn load_library_direct(&self, lib_name: &str, path: &str, boxes: &[String]) -> BidResult<()> { + pub fn load_library_direct( + &self, + lib_name: &str, + path: &str, + boxes: &[String], + ) -> BidResult<()> { // If caller didn't provide box names, try to infer from nyash_box.toml let inferred_boxes: Vec = if boxes.is_empty() { let nyb = std::path::Path::new(path) @@ -94,7 +99,11 @@ impl PluginHost { } else { Vec::new() }; - let effective_boxes: Vec = if boxes.is_empty() { inferred_boxes } else { boxes.to_vec() }; + let effective_boxes: Vec = if boxes.is_empty() { + inferred_boxes + } else { + boxes.to_vec() + }; let def = crate::config::nyash_toml_v2::LibraryDefinition { boxes: effective_boxes.clone(), path: path.to_string(), @@ -105,15 +114,29 @@ impl PluginHost { if l.config.is_none() { let mut cfg = NyashConfigV2 { libraries: std::collections::HashMap::new(), - plugin_paths: crate::config::nyash_toml_v2::PluginPaths { search_paths: vec![] }, + plugin_paths: crate::config::nyash_toml_v2::PluginPaths { + search_paths: vec![], + }, plugins: std::collections::HashMap::new(), box_types: std::collections::HashMap::new(), }; - cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() }); + cfg.libraries.insert( + lib_name.to_string(), + crate::config::nyash_toml_v2::LibraryDefinition { + boxes: def.boxes.clone(), + path: def.path.clone(), + }, + ); l.config = Some(cfg); // No dedicated config file; keep config_path None and rely on box_specs fallback } else if let Some(cfg) = l.config.as_mut() { - cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() }); + cfg.libraries.insert( + lib_name.to_string(), + crate::config::nyash_toml_v2::LibraryDefinition { + boxes: def.boxes.clone(), + path: def.path.clone(), + }, + ); } // Load the library now l.load_plugin_direct(lib_name, &def)?; @@ -146,7 +169,8 @@ impl PluginHost { if let Some((lib_name, _lib_def)) = cfg.find_library_for_box(box_type) { if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { // Prefer config mapping; fallback to loader's TypeBox resolve(name) - let (method_id, returns_result) = if let Some(m) = box_conf.methods.get(method_name) { + let (method_id, returns_result) = if let Some(m) = box_conf.methods.get(method_name) + { (m.method_id, m.returns_result) } else { let l = self.loader.read().unwrap(); @@ -177,7 +201,13 @@ impl PluginHost { if let Some(entry) = bm.get(method_name) { // Support both { method_id = N } and bare integer in the future let (method_id, returns_result) = if let Some(mid) = entry.get("method_id") { - (mid.as_integer().unwrap_or(0) as u32, entry.get("returns_result").and_then(|b| b.as_bool()).unwrap_or(false)) + ( + mid.as_integer().unwrap_or(0) as u32, + entry + .get("returns_result") + .and_then(|b| b.as_bool()) + .unwrap_or(false), + ) } else if let Some(mid) = entry.as_integer() { (mid as u32, false) } else { @@ -266,7 +296,9 @@ impl PluginHost { } // Library-backed path if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { + if let Some(box_conf) = + cfg.get_box_config(lib_name, box_type, &toml_value) + { if let Some(m) = box_conf.methods.get(method_name) { return m.returns_result; } @@ -331,28 +363,50 @@ impl PluginHost { /// 2) Top-level tables that look like box sections (have `type_id` or `methods`/`lifecycle`) fn infer_box_names_from_nyash_box(nyb_path: &std::path::Path) -> Vec { let mut out: Vec = Vec::new(); - if !nyb_path.exists() { return out; } - let Ok(text) = std::fs::read_to_string(nyb_path) else { return out; }; - let Ok(doc) = toml::from_str::(&text) else { return out; }; + if !nyb_path.exists() { + return out; + } + let Ok(text) = std::fs::read_to_string(nyb_path) else { + return out; + }; + let Ok(doc) = toml::from_str::(&text) else { + return out; + }; // 1) explicit provides - if let Some(arr) = doc.get("provides").and_then(|v| v.get("boxes")).and_then(|v| v.as_array()) { - for v in arr { if let Some(s) = v.as_str() { out.push(s.to_string()); } } - out.sort(); out.dedup(); - if !out.is_empty() { return out; } + if let Some(arr) = doc + .get("provides") + .and_then(|v| v.get("boxes")) + .and_then(|v| v.as_array()) + { + for v in arr { + if let Some(s) = v.as_str() { + out.push(s.to_string()); + } + } + out.sort(); + out.dedup(); + if !out.is_empty() { + return out; + } } // 2) heuristic: tables with type_id or lifecycle/methods if let Some(tbl) = doc.as_table() { for (k, v) in tbl.iter() { - if k == "box" || k == "implementation" || k == "artifacts" || k == "provides" { continue; } + if k == "box" || k == "implementation" || k == "artifacts" || k == "provides" { + continue; + } if let Some(t) = v.as_table() { let looks_like_box = t.get("type_id").is_some() || t.get("methods").is_some() || t.get("lifecycle").is_some(); - if looks_like_box { out.push(k.clone()); } + if looks_like_box { + out.push(k.clone()); + } } } } - out.sort(); out.dedup(); + out.sort(); + out.dedup(); out } diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs index 2ec95889..a335ebf6 100644 --- a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -245,16 +245,21 @@ pub fn handle_box_introspect( "kind" => { let value = args.get(0).ok_or(BidError::PluginError)?; let info = build_box_info(value.as_ref()); - if std::env::var("NYASH_BOX_INTROSPECT_TRACE") - .ok() - .as_deref() == Some("1") - { + if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref() == Some("1") { eprintln!( "[box_introspect:plugin] kind={} type_name={} is_map={} is_array={}", - info.get(Box::new(StringBox::new("kind"))).to_string_box().value, - info.get(Box::new(StringBox::new("type_name"))).to_string_box().value, - info.get(Box::new(StringBox::new("is_map"))).to_string_box().value, - info.get(Box::new(StringBox::new("is_array"))).to_string_box().value, + info.get(Box::new(StringBox::new("kind"))) + .to_string_box() + .value, + info.get(Box::new(StringBox::new("type_name"))) + .to_string_box() + .value, + info.get(Box::new(StringBox::new("is_map"))) + .to_string_box() + .value, + info.get(Box::new(StringBox::new("is_array"))) + .to_string_box() + .value, ); } Ok(Some(Box::new(info))) diff --git a/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs index d44ab08e..ec49a9c1 100644 --- a/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs +++ b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs @@ -3,8 +3,8 @@ use crate::bid::{BidError, BidResult}; use crate::box_trait::NyashBox; use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2; -use std::sync::Arc; use std::env; +use std::sync::Arc; fn dbg_on() -> bool { std::env::var("PLUGIN_DEBUG").is_ok() @@ -49,25 +49,36 @@ impl PluginLoaderV2 { } // Optional C-core probe (design): emit tag and optionally call into c-core when enabled - if env::var("HAKO_C_CORE_ENABLE").ok().as_deref() == Some("1") && should_route_ccore(box_type, method_name) { + if env::var("HAKO_C_CORE_ENABLE").ok().as_deref() == Some("1") + && should_route_ccore(box_type, method_name) + { eprintln!("[c-core:invoke:{}.{}]", box_type, method_name); #[cfg(feature = "c-core")] { // MapBox.set: call C-core stub (no-op) with available info if box_type == "MapBox" && method_name == "set" { - let key = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default(); - let val = args.get(1).map(|b| b.to_string_box().value).unwrap_or_default(); + let key = args + .get(0) + .map(|b| b.to_string_box().value) + .unwrap_or_default(); + let val = args + .get(1) + .map(|b| b.to_string_box().value) + .unwrap_or_default(); let _ = nyash_c_core::core_map_set(type_id as i32, instance_id, &key, &val); } else if box_type == "ArrayBox" && method_name == "push" { // For design stage, pass 0 (we don't rely on c-core result) let _ = nyash_c_core::core_array_push(type_id as i32, instance_id, 0); } else if box_type == "ArrayBox" && method_name == "get" { let _ = nyash_c_core::core_array_get(type_id as i32, instance_id, 0); - } else if box_type == "ArrayBox" && (method_name == "size" || method_name == "len" || method_name == "length") { + } else if box_type == "ArrayBox" + && (method_name == "size" || method_name == "len" || method_name == "length") + { let _ = nyash_c_core::core_array_len(type_id as i32, instance_id); } else { // Generic probe - let _ = nyash_c_core::core_probe_invoke(box_type, method_name, args.len() as i32); + let _ = + nyash_c_core::core_probe_invoke(box_type, method_name, args.len() as i32); } } } @@ -120,8 +131,12 @@ fn should_trace_tlv_shim(box_type: &str, method: &str) -> bool { let key = format!("{}.{}", box_type, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -135,8 +150,12 @@ fn should_trace_cwrap(box_type: &str, method: &str) -> bool { let key = format!("{}.{}", box_type, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -149,8 +168,12 @@ fn should_trace_call(target: &str, method: &str) -> bool { let key = format!("{}.{}", target, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -162,8 +185,12 @@ fn should_route_ccore(box_type: &str, method: &str) -> bool { let key = format!("{}.{}", box_type, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -203,9 +230,7 @@ fn resolve_type_info(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(Stri /// Decode TLV result into a NyashBox fn decode_tlv_result(box_type: &str, data: &[u8]) -> BidResult>> { - if let Some((tag, _sz, payload)) = - crate::runtime::plugin_ffi_common::decode::tlv_first(data) - { + if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(data) { let bx: Box = match tag { 1 => Box::new(crate::box_trait::BoolBox::new( crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false), diff --git a/src/runtime/plugin_loader_v2/enabled/instance_manager.rs b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs index 26d42fd4..d5edb5ff 100644 --- a/src/runtime/plugin_loader_v2/enabled/instance_manager.rs +++ b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs @@ -2,7 +2,10 @@ use crate::bid::{BidError, BidResult}; use crate::box_trait::NyashBox; -use crate::runtime::plugin_loader_v2::enabled::{PluginLoaderV2, types::{PluginBoxV2, PluginHandleInner}}; +use crate::runtime::plugin_loader_v2::enabled::{ + types::{PluginBoxV2, PluginHandleInner}, + PluginLoaderV2, +}; use std::sync::Arc; fn dbg_on() -> bool { @@ -88,10 +91,7 @@ impl PluginLoaderV2 { } /// Resolve box IDs (type_id, birth_id, fini_id) from configuration or specs -fn resolve_box_ids( - loader: &PluginLoaderV2, - box_type: &str, -) -> BidResult<(u32, u32, Option)> { +fn resolve_box_ids(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(u32, u32, Option)> { let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None); // Try config mapping first (when available) @@ -138,4 +138,4 @@ fn resolve_box_ids( let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?; Ok((type_id, birth_id, fini_id)) -} \ No newline at end of file +} diff --git a/src/runtime/plugin_loader_v2/enabled/loader/singletons.rs b/src/runtime/plugin_loader_v2/enabled/loader/singletons.rs index 47e21414..829ce5f9 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader/singletons.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader/singletons.rs @@ -4,10 +4,13 @@ use crate::runtime::plugin_loader_v2::enabled::{errors, host_bridge, types}; pub(super) fn prebirth_singletons(loader: &PluginLoaderV2) -> BidResult<()> { let config = loader.config.as_ref().ok_or(BidError::PluginError)?; - let cfg_path = loader - .config_path - .as_deref() - .unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" }); + let cfg_path = loader.config_path.as_deref().unwrap_or_else(|| { + if std::path::Path::new("hako.toml").exists() { + "hako.toml" + } else { + "nyash.toml" + } + }); if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() { crate::runtime::deprecations::warn_nyash_toml_used_once(); } @@ -38,10 +41,13 @@ pub(super) fn ensure_singleton_handle( { return Ok(()); } - let cfg_path = loader - .config_path - .as_deref() - .unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" }); + let cfg_path = loader.config_path.as_deref().unwrap_or_else(|| { + if std::path::Path::new("hako.toml").exists() { + "hako.toml" + } else { + "nyash.toml" + } + }); if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() { crate::runtime::deprecations::warn_nyash_toml_used_once(); } diff --git a/src/runtime/plugin_loader_v2/enabled/method_resolver.rs b/src/runtime/plugin_loader_v2/enabled/method_resolver.rs index 19cc06b5..674c2411 100644 --- a/src/runtime/plugin_loader_v2/enabled/method_resolver.rs +++ b/src/runtime/plugin_loader_v2/enabled/method_resolver.rs @@ -11,17 +11,22 @@ impl PluginLoaderV2 { pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult { // First try config mapping if let Some(cfg) = self.config.as_ref() { - let cfg_path = self - .config_path - .as_deref() - .unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" }); + let cfg_path = self.config_path.as_deref().unwrap_or_else(|| { + if std::path::Path::new("hako.toml").exists() { + "hako.toml" + } else { + "nyash.toml" + } + }); if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() { crate::runtime::deprecations::warn_nyash_toml_used_once(); } // Load and parse TOML - let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; - let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; + let toml_content = + std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; + let toml_value: toml::Value = + toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; // Find library for box if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { @@ -75,10 +80,13 @@ impl PluginLoaderV2 { /// Check if a method returns a Result type pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { if let Some(cfg) = self.config.as_ref() { - let cfg_path = self - .config_path - .as_deref() - .unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" }); + let cfg_path = self.config_path.as_deref().unwrap_or_else(|| { + if std::path::Path::new("hako.toml").exists() { + "hako.toml" + } else { + "nyash.toml" + } + }); if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() { crate::runtime::deprecations::warn_nyash_toml_used_once(); } @@ -86,7 +94,8 @@ impl PluginLoaderV2 { if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { if let Ok(toml_value) = toml::from_str::(&toml_content) { if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { + if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) + { if let Some(method_spec) = box_conf.methods.get(method_name) { return method_spec.returns_result; } @@ -107,10 +116,13 @@ impl PluginLoaderV2 { method_name: &str, ) -> BidResult<(u32, u32, bool)> { let cfg = self.config.as_ref().ok_or(BidError::PluginError)?; - let cfg_path = self - .config_path - .as_deref() - .unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" }); + let cfg_path = self.config_path.as_deref().unwrap_or_else(|| { + if std::path::Path::new("hako.toml").exists() { + "hako.toml" + } else { + "nyash.toml" + } + }); if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() { crate::runtime::deprecations::warn_nyash_toml_used_once(); } diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs index 2614c499..da53c428 100644 --- a/src/runtime/plugin_loader_v2/enabled/mod.rs +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -8,13 +8,13 @@ mod loader; mod method_resolver; mod types; +pub use extern_functions::handle_box_introspect; pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2}; pub use loader::PluginLoaderV2; pub use types::{ construct_plugin_box, make_plugin_box_v2, NyashTypeBoxFfi, PluginBoxMetadata, PluginBoxV2, PluginHandleInner, }; -pub use extern_functions::handle_box_introspect; pub fn metadata_for_type_id(type_id: u32) -> Option { let loader = get_global_loader_v2(); diff --git a/src/runtime/provider_lock.rs b/src/runtime/provider_lock.rs index 6ca8c828..76cd4f0e 100644 --- a/src/runtime/provider_lock.rs +++ b/src/runtime/provider_lock.rs @@ -5,29 +5,38 @@ * 既定では挙動を変えず、環境変数により警告/エラー化のみ可能にする。 */ +use crate::boxes::file::provider::{FileCaps, FileIo}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, OnceLock}; -use crate::boxes::file::provider::{FileCaps, FileIo}; static LOCKED: AtomicBool = AtomicBool::new(false); static WARN_ONCE: OnceLock<()> = OnceLock::new(); static FILEBOX_PROVIDER: OnceLock> = OnceLock::new(); /// Return true when providers are locked -pub fn is_locked() -> bool { LOCKED.load(Ordering::Relaxed) } +pub fn is_locked() -> bool { + LOCKED.load(Ordering::Relaxed) +} /// Lock providers (idempotent) -pub fn lock_providers() { LOCKED.store(true, Ordering::Relaxed); } +pub fn lock_providers() { + LOCKED.store(true, Ordering::Relaxed); +} /// Guard called before creating a new box instance. /// Default: no-op. When NYASH_PROVIDER_LOCK_STRICT=1, returns Err if not locked. /// When NYASH_PROVIDER_LOCK_WARN=1, prints a warning once. pub fn guard_before_new_box(box_type: &str) -> Result<(), String> { - if is_locked() { return Ok(()); } + if is_locked() { + return Ok(()); + } let strict = std::env::var("NYASH_PROVIDER_LOCK_STRICT").ok().as_deref() == Some("1"); let warn = std::env::var("NYASH_PROVIDER_LOCK_WARN").ok().as_deref() == Some("1"); if strict { - return Err(format!("E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock", box_type)); + return Err(format!( + "E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock", + box_type + )); } if warn { // Print once per process @@ -40,7 +49,8 @@ pub fn guard_before_new_box(box_type: &str) -> Result<(), String> { /// Set the global FileBox provider (can only be called once) pub fn set_filebox_provider(provider: Arc) -> Result<(), String> { - FILEBOX_PROVIDER.set(provider) + FILEBOX_PROVIDER + .set(provider) .map_err(|_| "FileBox provider already set".to_string()) } diff --git a/src/runtime/provider_verify.rs b/src/runtime/provider_verify.rs index b86aa82b..8aec843a 100644 --- a/src/runtime/provider_verify.rs +++ b/src/runtime/provider_verify.rs @@ -26,7 +26,9 @@ fn parse_required_methods(spec: &str) -> HashMap> { let mut map = HashMap::new(); for part in spec.split(';') { let p = part.trim(); - if p.is_empty() { continue; } + if p.is_empty() { + continue; + } if let Some((ty, rest)) = p.split_once(':') { let methods: Vec = rest .split(',') @@ -44,15 +46,30 @@ fn parse_required_methods(spec: &str) -> HashMap> { fn load_required_methods_from_toml() -> HashMap> { let mut map: HashMap> = HashMap::new(); - let text = match std::fs::read_to_string("nyash.toml") { Ok(s) => s, Err(_) => return map }; - let doc: toml::Value = match toml::from_str(&text) { Ok(v) => v, Err(_) => return map }; + let text = match std::fs::read_to_string("nyash.toml") { + Ok(s) => s, + Err(_) => return map, + }; + let doc: toml::Value = match toml::from_str(&text) { + Ok(v) => v, + Err(_) => return map, + }; // Helper: add entry if array-of-strings let mut add_arr = |ty: &str, arr: &toml::Value| { if let Some(a) = arr.as_array() { let mut v: Vec = Vec::new(); - for e in a { if let Some(s) = e.as_str() { let s = s.trim(); if !s.is_empty() { v.push(s.to_string()); } } } - if !v.is_empty() { map.insert(ty.to_string(), v); } + for e in a { + if let Some(s) = e.as_str() { + let s = s.trim(); + if !s.is_empty() { + v.push(s.to_string()); + } + } + } + if !v.is_empty() { + map.insert(ty.to_string(), v); + } } }; @@ -61,8 +78,15 @@ fn load_required_methods_from_toml() -> HashMap> { if let Some(req) = vrfy.get("required_methods") { if let Some(tbl) = req.as_table() { for (k, v) in tbl.iter() { - if v.is_array() { add_arr(k, v); continue; } - if let Some(t) = v.as_table() { if let Some(m) = t.get("methods") { add_arr(k, m); } } + if v.is_array() { + add_arr(k, v); + continue; + } + if let Some(t) = v.as_table() { + if let Some(m) = t.get("methods") { + add_arr(k, m); + } + } } } } @@ -72,7 +96,11 @@ fn load_required_methods_from_toml() -> HashMap> { if let Some(types) = doc.get("types") { if let Some(tbl) = types.as_table() { for (k, v) in tbl.iter() { - if let Some(t) = v.as_table() { if let Some(m) = t.get("required_methods") { add_arr(k, m); } } + if let Some(t) = v.as_table() { + if let Some(m) = t.get("required_methods") { + add_arr(k, m); + } + } } } } @@ -83,16 +111,22 @@ fn load_required_methods_from_toml() -> HashMap> { pub fn verify_from_env() -> Result<(), String> { let mode = std::env::var("NYASH_PROVIDER_VERIFY").unwrap_or_default(); let mode = mode.to_ascii_lowercase(); - if !(mode == "warn" || mode == "strict") { return Ok(()); } + if !(mode == "warn" || mode == "strict") { + return Ok(()); + } // Merge: nyash.toml + env override let mut req = load_required_methods_from_toml(); let spec = std::env::var("NYASH_VERIFY_REQUIRED_METHODS").unwrap_or_default(); if !spec.trim().is_empty() { let env_map = parse_required_methods(&spec); - for (k, v) in env_map { req.entry(k).or_default().extend(v); } + for (k, v) in env_map { + req.entry(k).or_default().extend(v); + } + } + if req.is_empty() { + return Ok(()); } - if req.is_empty() { return Ok(()); } let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); let host = host.read().unwrap(); @@ -107,11 +141,18 @@ pub fn verify_from_env() -> Result<(), String> { } } - if failures.is_empty() { return Ok(()); } + if failures.is_empty() { + return Ok(()); + } let msg = format!( "Provider verify failed ({}): missing methods: {}", mode, failures.join(", ") ); - if mode == "warn" { eprintln!("[provider-verify][warn] {}", msg); Ok(()) } else { Err(msg) } + if mode == "warn" { + eprintln!("[provider-verify][warn] {}", msg); + Ok(()) + } else { + Err(msg) + } } diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index 682a84df..16b4ce92 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -5,9 +5,9 @@ * nyash.linkなしで動作する基本的な標準関数群 */ +use crate::box_factory::RuntimeError; use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox}; use crate::boxes::ArrayBox; -use crate::box_factory::RuntimeError; use std::collections::HashMap; /// 組み込み標準ライブラリ diff --git a/src/tests/identical_exec_instance.rs b/src/tests/identical_exec_instance.rs index 3d9bc565..29d48d03 100644 --- a/src/tests/identical_exec_instance.rs +++ b/src/tests/identical_exec_instance.rs @@ -4,8 +4,8 @@ mod tests { use std::sync::{Arc, RwLock}; use crate::backend::VM; - use crate::box_trait::NyashBox; use crate::box_factory::RuntimeError; + use crate::box_trait::NyashBox; use crate::mir::{ BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, diff --git a/src/tests/mir_breakfinder_ssa.rs b/src/tests/mir_breakfinder_ssa.rs index bafbf921..eed37615 100644 --- a/src/tests/mir_breakfinder_ssa.rs +++ b/src/tests/mir_breakfinder_ssa.rs @@ -147,10 +147,7 @@ fn mir_compiler_stageb_breakfinder_ssa_debug() { let cr = mc.compile(ast).expect("compile compiler_stageb.hako ok"); // 必要に応じて StageBBodyExtractorBox.build_body_src/2 だけの MIR をダンプする。 - if std::env::var("NYASH_MIR_TEST_DUMP") - .ok() - .as_deref() == Some("1") - { + if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { if let Some(func) = cr .module .functions diff --git a/src/tests/mir_funcscanner_skip_ws.rs b/src/tests/mir_funcscanner_skip_ws.rs index 2a233c6d..078bc653 100644 --- a/src/tests/mir_funcscanner_skip_ws.rs +++ b/src/tests/mir_funcscanner_skip_ws.rs @@ -39,10 +39,17 @@ fn mir_funcscanner_skip_ws_direct_vm() { match mc.compile(ast) { Ok(compiled) => { eprintln!("[test] Compilation successful"); - eprintln!("[test] Module has {} functions", compiled.module.functions.len()); + eprintln!( + "[test] Module has {} functions", + compiled.module.functions.len() + ); // Check if FuncScannerBox.skip_whitespace/2 exists - if let Some(func) = compiled.module.functions.get("FuncScannerBox.skip_whitespace/2") { + if let Some(func) = compiled + .module + .functions + .get("FuncScannerBox.skip_whitespace/2") + { eprintln!("[test] Found FuncScannerBox.skip_whitespace/2"); eprintln!("[test] Function has {} blocks", func.blocks.len()); @@ -50,7 +57,10 @@ fn mir_funcscanner_skip_ws_direct_vm() { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { use crate::mir::MirPrinter; let dump = MirPrinter::new().print_function(func); - eprintln!("----- MIR DUMP: FuncScannerBox.skip_whitespace/2 -----\n{}", dump); + eprintln!( + "----- MIR DUMP: FuncScannerBox.skip_whitespace/2 -----\n{}", + dump + ); } } else { eprintln!("[test] WARNING: FuncScannerBox.skip_whitespace/2 not found in module"); diff --git a/src/tests/mir_funcscanner_ssa.rs b/src/tests/mir_funcscanner_ssa.rs index fbf3c04b..ae57083f 100644 --- a/src/tests/mir_funcscanner_ssa.rs +++ b/src/tests/mir_funcscanner_ssa.rs @@ -37,19 +37,18 @@ fn mir_funcscanner_fib_min_ssa_debug() { // funcscanner_fib_min.hako と同じソースをそのまま使う let src = include_str!("../../lang/src/compiler/tests/funcscanner_fib_min.hako"); - let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse funcscanner_fib_min.hako ok"); + let ast: ASTNode = + NyashParser::parse_from_string(src).expect("parse funcscanner_fib_min.hako ok"); let mut mc = MirCompiler::with_options(false); - let cr = mc.compile(ast).expect("compile funcscanner_fib_min.hako ok"); + let cr = mc + .compile(ast) + .expect("compile funcscanner_fib_min.hako ok"); let mut verifier = MirVerifier::new(); if let Err(errors) = verifier.verify_module(&cr.module) { // デバッグ時に MIR 全体を見たい場合は NYASH_MIR_TEST_DUMP=1 で有効化 - if std::env::var("NYASH_MIR_TEST_DUMP") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); eprintln!("----- MIR DUMP (FuncScanner.fib_min) -----\n{}", dump); } @@ -83,13 +82,12 @@ fn mir_funcscanner_scan_methods_ssa_debug() { let mut verifier = MirVerifier::new(); if let Err(errors) = verifier.verify_module(&cr.module) { - if std::env::var("NYASH_MIR_TEST_DUMP") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (FuncScanner.scan_methods_min) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (FuncScanner.scan_methods_min) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); diff --git a/src/tests/mir_locals_ssa.rs b/src/tests/mir_locals_ssa.rs index aa2f5608..a4c28e0a 100644 --- a/src/tests/mir_locals_ssa.rs +++ b/src/tests/mir_locals_ssa.rs @@ -6,8 +6,8 @@ */ use crate::ast::ASTNode; -use crate::mir::{MirCompiler, MirVerifier}; use crate::mir::printer::MirPrinter; +use crate::mir::{MirCompiler, MirVerifier}; use crate::parser::NyashParser; #[test] @@ -80,13 +80,24 @@ static box TestLocals { } // Assert Copy instructions are present - assert!(has_copy, "MIR should contain Copy instructions for local variable initializations"); + assert!( + has_copy, + "MIR should contain Copy instructions for local variable initializations" + ); // Assert no SSA violations - assert!(violations.is_empty(), "MIR should not have SSA violations: {:?}", violations); + assert!( + violations.is_empty(), + "MIR should not have SSA violations: {:?}", + violations + ); // We expect at least 3 copy instructions (for a, b, c) - assert!(copy_count >= 3, "Expected at least 3 copy instructions, found {}", copy_count); + assert!( + copy_count >= 3, + "Expected at least 3 copy instructions, found {}", + copy_count + ); } #[test] @@ -115,5 +126,8 @@ static box TestUninit { eprintln!("{}", mir_output); // Uninitialized locals should have void constants, not copy - assert!(mir_output.contains("const void"), "Uninitialized locals should have void constants"); + assert!( + mir_output.contains("const void"), + "Uninitialized locals should have void constants" + ); } diff --git a/src/tests/mir_stage1_using_resolver_verify.rs b/src/tests/mir_stage1_using_resolver_verify.rs index 674e8a33..2ac43b28 100644 --- a/src/tests/mir_stage1_using_resolver_verify.rs +++ b/src/tests/mir_stage1_using_resolver_verify.rs @@ -1,6 +1,6 @@ use crate::ast::ASTNode; -use crate::mir::{MirCompiler, MirVerifier}; use crate::mir::printer::MirPrinter; +use crate::mir::{MirCompiler, MirVerifier}; use crate::parser::NyashParser; fn ensure_stage3_env() { @@ -188,6 +188,219 @@ static box Stage1UsingResolverFull { } } +/// Region+next_i パターンのループが SSA 崩れなく MIR 化できることを固定する軽量テスト。 +#[test] +fn mir_stage1_using_resolver_region_loop_verifies() { + ensure_stage3_env(); + let src = r#" +static box Stage1UsingResolverRegionLoop { + process(entries) { + local prefix = "" + local i = 0 + local n = entries.length() + loop(i < n) { + local next_i = i + 1 + local entry = entries.get(i) + local name = "" + entry.get("name") + local ok = 1 + if name == "" { ok = 0 } + if ok == 1 { prefix = prefix + name } + i = next_i + } + return prefix + } + + main() { + local arr = new ArrayBox() + local m1 = new MapBox(); m1.set("name", "A"); arr.push(m1) + local m2 = new MapBox(); m2.set("name", ""); arr.push(m2) + local m3 = new MapBox(); m3.set("name", "B"); arr.push(m3) + return Stage1UsingResolverRegionLoop.process(arr) + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for Stage1UsingResolverRegionLoop"); + } +} + +/// JSON スキャン + early-exit パターンでも PHI/SSA が崩れないことを確認する。 +#[test] +fn mir_stage1_using_resolver_collect_entries_early_exit_verifies() { + ensure_stage3_env(); + let src = r#" +static box Stage1UsingResolverEarlyExit { + // Minimal substring finder (exclusive start/end) for JSON scanning + _find(text, pattern, start_pos) { + local tl = text.length() + local pl = pattern.length() + local i = start_pos + loop(i < tl) { + if i + pl > tl { return -1 } + local j = 0 + local ok = 1 + loop(j < pl) { + if text.substring(i + j, i + j + 1) != pattern.substring(j, j + 1) { + ok = 0 + break + } + j = j + 1 + } + if ok == 1 { return i } + i = i + 1 + } + return -1 + } + + _read_until_quote(text, start_pos) { + local tl = text.length() + local i = start_pos + local out = "" + loop(i < tl) { + local ch = text.substring(i, i + 1) + if ch == "\"" { break } + out = out + ch + i = i + 1 + } + return out + } + + collect_entries(stop_name) { + // Two objects + one early-exit sentinel; loop uses Region+next_pos 形。 + local json = "[{\"name\":\"A\"},{\"name\":\"stop\"},{\"name\":\"C\"}]" + local pos = 0 + local n = json.length() + local count = 0 + loop(pos < n) { + local next_pos = n + local name_idx = me._find(json, "\"name\":\"", pos) + if name_idx < 0 { + next_pos = n + } else { + local name = me._read_until_quote(json, name_idx + 8) + local obj_end = me._find(json, "}", name_idx) + if obj_end < 0 { obj_end = n } + count = count + 1 + if name == stop_name { + next_pos = n + } else { + next_pos = obj_end + 1 + } + } + pos = next_pos + } + return count + } + + main() { + return me.collect_entries("stop") + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for Stage1UsingResolverEarlyExit"); + } +} + +/// modules_list 分割ループを Region+next_start 形で書いた場合でも SSA が崩れないことを確認する。 +#[test] +fn mir_stage1_using_resolver_module_map_regionized_verifies() { + ensure_stage3_env(); + let src = r#" +static box Stage1UsingResolverModuleMap { + _find(text, pat, start_pos) { + local tl = text.length() + local pl = pat.length() + local i = start_pos + loop(i < tl) { + if i + pl > tl { return -1 } + local ok = 1 + local j = 0 + loop(j < pl) { + if text.substring(i + j, i + j + 1) != pat.substring(j, j + 1) { + ok = 0 + break + } + j = j + 1 + } + if ok == 1 { return i } + i = i + 1 + } + return -1 + } + + build_map(raw) { + local map = new MapBox() + if raw == null { return map } + local delim = "|||" + local start = 0 + local cont = 1 + loop(cont == 1) { + local next_start = raw.length() + local next = me._find(raw, delim, start) + local seg = "" + if next >= 0 { + seg = raw.substring(start, next) + next_start = next + delim.length() + } else { + seg = raw.substring(start, raw.length()) + cont = 0 + } + if seg.length() > 0 { + local eq_idx = me._find(seg, "=", 0) + if eq_idx >= 0 { + local key = seg.substring(0, eq_idx) + local val = seg.substring(eq_idx + 1, seg.length()) + if key != "" && val != "" { + map.set(key, val) + } + } + } + start = next_start + } + return map + } + + main() { + // 2 entries + 空 + 終端 + local raw = "A=path_a|||B=path_b|||" + local m = me.build_map(raw) + return m.size() + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for Stage1UsingResolverModuleMap"); + } +} + /// Verify MIR/SSA for ParserBox.parse_program2 in isolation by compiling a small wrapper. #[test] fn mir_parserbox_parse_program2_harness_parses_minimal_source() { diff --git a/src/tests/mir_stageb_like_args_length.rs b/src/tests/mir_stageb_like_args_length.rs index 74280409..8fcadfee 100644 --- a/src/tests/mir_stageb_like_args_length.rs +++ b/src/tests/mir_stageb_like_args_length.rs @@ -1,5 +1,5 @@ -use crate::parser::NyashParser; use crate::mir::MirPrinter; +use crate::parser::NyashParser; fn ensure_stage3_env() { std::env::set_var("NYASH_PARSER_STAGE3", "1"); @@ -98,7 +98,10 @@ static box StageBArgsBox { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (StageBArgsBox.process if+loop) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (StageBArgsBox.process if+loop) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); @@ -154,7 +157,10 @@ static box TestNested { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (TestNested.complex nested if+loop) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (TestNested.complex nested if+loop) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); @@ -196,7 +202,10 @@ static box StageBArgsBox { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (StageBArgsBox.process loop cond uses length) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (StageBArgsBox.process loop cond uses length) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); @@ -238,7 +247,10 @@ static box TestNested2 { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (TestNested2.walk conditional+loop length) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (TestNested2.walk conditional+loop length) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); @@ -286,7 +298,10 @@ static box JsonScanBoxMini { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (JsonScanBoxMini.seek_array_end) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (JsonScanBoxMini.seek_array_end) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); diff --git a/src/tests/mir_stageb_loop_break_continue.rs b/src/tests/mir_stageb_loop_break_continue.rs index e4dd58a8..c7173ec1 100644 --- a/src/tests/mir_stageb_loop_break_continue.rs +++ b/src/tests/mir_stageb_loop_break_continue.rs @@ -59,7 +59,10 @@ static box LoopBreakContinueBox { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (LoopBreakContinueBox.sum_positive_until_null) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (LoopBreakContinueBox.sum_positive_until_null) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); @@ -130,7 +133,10 @@ static box LoopNestedBreakBox { if let Err(errors) = verifier.verify_module(&cr.module) { if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { let dump = MirPrinter::new().print_module(&cr.module); - eprintln!("----- MIR DUMP (LoopNestedBreakBox.nested_walk) -----\n{}", dump); + eprintln!( + "----- MIR DUMP (LoopNestedBreakBox.nested_walk) -----\n{}", + dump + ); } for e in &errors { eprintln!("[rust-mir-verify] {}", e); diff --git a/src/tests/mir_value_kind.rs b/src/tests/mir_value_kind.rs index 9154fbbf..3d6bad7d 100644 --- a/src/tests/mir_value_kind.rs +++ b/src/tests/mir_value_kind.rs @@ -4,7 +4,6 @@ /// - パラメータ型自動登録(Phase 26-A-3) /// - is_parameter型安全判定(Phase 26-A-4) /// - 実際のMIRビルド環境での動作確認 - use crate::mir::{MirBuilder, MirValueKind, ValueId}; /// GUARD checkバグ完全再現防止テスト @@ -94,9 +93,18 @@ fn test_instance_method_parameters() { assert!(!builder.is_value_parameter(ValueId(3)), "not parameter"); // 型情報確認 - assert_eq!(builder.get_value_kind(ValueId(0)), Some(MirValueKind::Parameter(0))); - assert_eq!(builder.get_value_kind(ValueId(1)), Some(MirValueKind::Parameter(1))); - assert_eq!(builder.get_value_kind(ValueId(2)), Some(MirValueKind::Parameter(2))); + assert_eq!( + builder.get_value_kind(ValueId(0)), + Some(MirValueKind::Parameter(0)) + ); + assert_eq!( + builder.get_value_kind(ValueId(1)), + Some(MirValueKind::Parameter(1)) + ); + assert_eq!( + builder.get_value_kind(ValueId(2)), + Some(MirValueKind::Parameter(2)) + ); } /// ループ内でのパラメータ/ローカル変数の区別テスト @@ -108,20 +116,38 @@ fn test_loop_parameter_vs_local_distinction() { // Phase 26-A-2: new_typed_value() で各種ValueId作成 let limit = builder.new_typed_value(MirValueKind::Parameter(0)); // パラメータ - let i = builder.new_typed_value(MirValueKind::Local(0)); // ローカル変数 - let sum = builder.new_typed_value(MirValueKind::Local(1)); // ローカル変数 + let i = builder.new_typed_value(MirValueKind::Local(0)); // ローカル変数 + let sum = builder.new_typed_value(MirValueKind::Local(1)); // ローカル変数 let carrier = builder.new_typed_value(MirValueKind::LoopCarrier); // ループキャリア // パラメータ判定 - assert!(builder.is_value_parameter(limit.value_id()), "limit はパラメータ"); - assert!(!builder.is_value_parameter(i.value_id()), "i はローカル変数"); - assert!(!builder.is_value_parameter(sum.value_id()), "sum はローカル変数"); - assert!(!builder.is_value_parameter(carrier.value_id()), "carrier はループキャリア"); + assert!( + builder.is_value_parameter(limit.value_id()), + "limit はパラメータ" + ); + assert!( + !builder.is_value_parameter(i.value_id()), + "i はローカル変数" + ); + assert!( + !builder.is_value_parameter(sum.value_id()), + "sum はローカル変数" + ); + assert!( + !builder.is_value_parameter(carrier.value_id()), + "carrier はループキャリア" + ); // 型情報確認 - assert_eq!(builder.get_value_kind(limit.value_id()), Some(MirValueKind::Parameter(0))); + assert_eq!( + builder.get_value_kind(limit.value_id()), + Some(MirValueKind::Parameter(0)) + ); assert!(builder.get_value_kind(i.value_id()).unwrap().is_local()); - assert!(builder.get_value_kind(carrier.value_id()).unwrap().is_loop_carrier()); + assert!(builder + .get_value_kind(carrier.value_id()) + .unwrap() + .is_loop_carrier()); } /// パラメータなし関数のテスト diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 2329a0d2..e161d435 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -6,14 +6,16 @@ pub mod identical_exec; pub mod identical_exec_collections; pub mod identical_exec_instance; pub mod identical_exec_string; +pub mod mir_breakfinder_ssa; +pub mod mir_funcscanner_skip_ws; +pub mod mir_funcscanner_ssa; +pub mod mir_locals_ssa; +pub mod mir_loopform_conditional_reassign; +pub mod mir_loopform_exit_phi; +pub mod mir_loopform_complex; +pub mod mir_stage1_using_resolver_verify; pub mod mir_stageb_like_args_length; pub mod mir_stageb_loop_break_continue; -pub mod mir_stage1_using_resolver_verify; -pub mod mir_locals_ssa; -pub mod mir_loopform_exit_phi; -pub mod mir_breakfinder_ssa; -pub mod mir_funcscanner_ssa; -pub mod mir_funcscanner_skip_ws; pub mod mir_value_kind; // Phase 26-A-5: ValueId型安全化統合テスト pub mod nyash_abi_basic; pub mod parser_static_box_members; diff --git a/src/tests/parser_static_box_members.rs b/src/tests/parser_static_box_members.rs index 13a8e1d2..b546b3bc 100644 --- a/src/tests/parser_static_box_members.rs +++ b/src/tests/parser_static_box_members.rs @@ -1,19 +1,27 @@ use crate::parser::NyashParser; -fn parse(src: &str) -> crate::ast::ASTNode { NyashParser::parse_from_string(src).expect("parse ok") } +fn parse(src: &str) -> crate::ast::ASTNode { + NyashParser::parse_from_string(src).expect("parse ok") +} fn no_toplevel_funccall(ast: &crate::ast::ASTNode) -> bool { match ast { - crate::ast::ASTNode::Program { statements, .. } => { - !statements.iter().any(|n| matches!(n, crate::ast::ASTNode::FunctionCall { .. })) - } + crate::ast::ASTNode::Program { statements, .. } => !statements + .iter() + .any(|n| matches!(n, crate::ast::ASTNode::FunctionCall { .. })), _ => true, } } fn box_has_methods(ast: &crate::ast::ASTNode, box_name: &str, methods: &[&str]) -> bool { fn check_box(b: &crate::ast::ASTNode, box_name: &str, methods: &[&str]) -> bool { - if let crate::ast::ASTNode::BoxDeclaration { name, methods: m, is_static, .. } = b { + if let crate::ast::ASTNode::BoxDeclaration { + name, + methods: m, + is_static, + .. + } = b + { if name == box_name && *is_static { return methods.iter().all(|k| { if let Some(node) = m.get(*k) { @@ -25,7 +33,9 @@ fn box_has_methods(ast: &crate::ast::ASTNode, box_name: &str, methods: &[&str]) false } match ast { - crate::ast::ASTNode::Program { statements, .. } => statements.iter().any(|n| check_box(n, box_name, methods)), + crate::ast::ASTNode::Program { statements, .. } => { + statements.iter().any(|n| check_box(n, box_name, methods)) + } _ => false, } } @@ -39,8 +49,14 @@ static box S { } "#; let ast = parse(src); - assert!(no_toplevel_funccall(&ast), "no top-level FunctionCall expected"); - assert!(box_has_methods(&ast, "S", &["f", "g"]), "static box S should have f and g methods"); + assert!( + no_toplevel_funccall(&ast), + "no top-level FunctionCall expected" + ); + assert!( + box_has_methods(&ast, "S", &["f", "g"]), + "static box S should have f and g methods" + ); } #[test] @@ -59,7 +75,13 @@ static box S { } "#; let ast = parse(src); - assert!(no_toplevel_funccall(&ast), "no top-level FunctionCall expected at seams"); - assert!(box_has_methods(&ast, "S", &["parse_float", "is_empty_or_whitespace"])); + assert!( + no_toplevel_funccall(&ast), + "no top-level FunctionCall expected at seams" + ); + assert!(box_has_methods( + &ast, + "S", + &["parse_float", "is_empty_or_whitespace"] + )); } - diff --git a/src/tests/typebox_tlv_diff.rs b/src/tests/typebox_tlv_diff.rs index ae149785..c4e82d90 100644 --- a/src/tests/typebox_tlv_diff.rs +++ b/src/tests/typebox_tlv_diff.rs @@ -67,13 +67,7 @@ mod tests { .unwrap_or_else(|| panic!("{}::{} returned None", box_ty, method)) } - fn inv_void( - h: &PluginHost, - box_ty: &str, - method: &str, - id: u32, - args: &[Box], - ) { + fn inv_void(h: &PluginHost, box_ty: &str, method: &str, id: u32, args: &[Box]) { let _ = h .invoke_instance_method(box_ty, method, id, args) .expect(&format!("invoke {}::{}", box_ty, method)); @@ -104,7 +98,13 @@ mod tests { let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1"); let (bt1, id1, _hold1) = create_plugin_instance("MapBox"); let out_tlv = with_host(|h| { - inv_void(h, &bt1, "set", id1, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]); + inv_void( + h, + &bt1, + "set", + id1, + &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))], + ); let sz = inv_some(h, &bt1, "size", id1, &[]); let gv = inv_some(h, &bt1, "get", id1, &[Box::new(StringBox::new("k"))]); (sz.to_string_box().value, gv.to_string_box().value) @@ -114,7 +114,13 @@ mod tests { let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX"); let (bt2, id2, _hold2) = create_plugin_instance("MapBox"); let out_tb = with_host(|h| { - inv_void(h, &bt2, "set", id2, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]); + inv_void( + h, + &bt2, + "set", + id2, + &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))], + ); let sz = inv_some(h, &bt2, "size", id2, &[]); let gv = inv_some(h, &bt2, "get", id2, &[Box::new(StringBox::new("k"))]); (sz.to_string_box().value, gv.to_string_box().value) @@ -131,7 +137,13 @@ mod tests { let _g = EnvGuard::set("NYASH_DISABLE_TYPEBOX", "1"); let (bt1, id1, _hold1) = create_plugin_instance("ArrayBox"); let out_tlv = with_host(|h| { - inv_void(h, &bt1, "set", id1, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]); + inv_void( + h, + &bt1, + "set", + id1, + &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))], + ); let ln = inv_some(h, &bt1, "len", id1, &[]); let gv = inv_some(h, &bt1, "get", id1, &[Box::new(IntegerBox::new(0))]); (ln.to_string_box().value, gv.to_string_box().value) @@ -140,7 +152,13 @@ mod tests { let _g2 = EnvGuard::remove("NYASH_DISABLE_TYPEBOX"); let (bt2, id2, _hold2) = create_plugin_instance("ArrayBox"); let out_tb = with_host(|h| { - inv_void(h, &bt2, "set", id2, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]); + inv_void( + h, + &bt2, + "set", + id2, + &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))], + ); let ln = inv_some(h, &bt2, "length", id2, &[]); let gv = inv_some(h, &bt2, "get", id2, &[Box::new(IntegerBox::new(0))]); (ln.to_string_box().value, gv.to_string_box().value) @@ -338,7 +356,13 @@ mod tests { let (bt1, id1, _hold1) = create_plugin_instance("RegexBox"); let out_tlv = with_host(|h| { inv_void(h, &bt1, "compile", id1, &[Box::new(StringBox::new("h.+o"))]); - let m = inv_some(h, &bt1, "isMatch", id1, &[Box::new(StringBox::new("hello"))]); + let m = inv_some( + h, + &bt1, + "isMatch", + id1, + &[Box::new(StringBox::new("hello"))], + ); let f = inv_some(h, &bt1, "find", id1, &[Box::new(StringBox::new("hello"))]); (m.to_string_box().value, f.to_string_box().value) }); @@ -348,7 +372,13 @@ mod tests { let (bt2, id2, _hold2) = create_plugin_instance("RegexBox"); let out_tb = with_host(|h| { inv_void(h, &bt2, "compile", id2, &[Box::new(StringBox::new("h.+o"))]); - let m = inv_some(h, &bt2, "isMatch", id2, &[Box::new(StringBox::new("hello"))]); + let m = inv_some( + h, + &bt2, + "isMatch", + id2, + &[Box::new(StringBox::new("hello"))], + ); let f = inv_some(h, &bt2, "find", id2, &[Box::new(StringBox::new("hello"))]); (m.to_string_box().value, f.to_string_box().value) }); @@ -378,8 +408,20 @@ mod tests { Box::new(StringBox::new("c.txt")), ], ); - let d = inv_some(h, &bt1, "dirname", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]); - let b = inv_some(h, &bt1, "basename", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]); + let d = inv_some( + h, + &bt1, + "dirname", + id1, + &[Box::new(StringBox::new("/a/b/c.txt"))], + ); + let b = inv_some( + h, + &bt1, + "basename", + id1, + &[Box::new(StringBox::new("/a/b/c.txt"))], + ); let n = inv_some( h, &bt1, @@ -409,8 +451,20 @@ mod tests { Box::new(StringBox::new("c.txt")), ], ); - let d = inv_some(h, &bt2, "dirname", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]); - let b = inv_some(h, &bt2, "basename", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]); + let d = inv_some( + h, + &bt2, + "dirname", + id2, + &[Box::new(StringBox::new("/a/b/c.txt"))], + ); + let b = inv_some( + h, + &bt2, + "basename", + id2, + &[Box::new(StringBox::new("/a/b/c.txt"))], + ); let n = inv_some( h, &bt2, diff --git a/src/tokenizer/cursor.rs b/src/tokenizer/cursor.rs index c0bc3d56..183d8b32 100644 --- a/src/tokenizer/cursor.rs +++ b/src/tokenizer/cursor.rs @@ -29,4 +29,3 @@ impl NyashTokenizer { self.position >= self.input.len() } } - diff --git a/src/tokenizer/engine.rs b/src/tokenizer/engine.rs index db7fdba1..c9a54981 100644 --- a/src/tokenizer/engine.rs +++ b/src/tokenizer/engine.rs @@ -8,7 +8,9 @@ impl NyashTokenizer { match std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok() { Some(v) => { let lv = v.to_ascii_lowercase(); - if lv == "0" || lv == "false" || lv == "off" { return false; } + if lv == "0" || lv == "false" || lv == "off" { + return false; + } true } None => true, @@ -149,12 +151,20 @@ impl NyashTokenizer { } Some('"') => { let string_value = self.read_string()?; - Ok(Token::new(TokenType::STRING(string_value), start_line, start_column)) + Ok(Token::new( + TokenType::STRING(string_value), + start_line, + start_column, + )) } // Stage‑3: シングルクォート文字列(オプトイン) Some('\'') if crate::config::env::parser_stage3() => { let string_value = self.read_single_quoted_string()?; - Ok(Token::new(TokenType::STRING(string_value), start_line, start_column)) + Ok(Token::new( + TokenType::STRING(string_value), + start_line, + start_column, + )) } Some(c) if c.is_ascii_digit() => { let token_type = self.read_numeric_literal()?; @@ -216,7 +226,11 @@ impl NyashTokenizer { Some('>') if self.peek_char() == Some('=') => { self.advance(); self.advance(); - Ok(Token::new(TokenType::GreaterEquals, start_line, start_column)) + Ok(Token::new( + TokenType::GreaterEquals, + start_line, + start_column, + )) } Some(c) => { if let Some(token) = self.single_char_token(c) { diff --git a/src/tokenizer/kinds.rs b/src/tokenizer/kinds.rs index e1dfbdbe..f7eecc34 100644 --- a/src/tokenizer/kinds.rs +++ b/src/tokenizer/kinds.rs @@ -118,7 +118,11 @@ pub struct Token { impl Token { pub fn new(token_type: TokenType, line: usize, column: usize) -> Self { - Self { token_type, line, column } + Self { + token_type, + line, + column, + } } } @@ -126,7 +130,11 @@ impl Token { #[derive(Error, Debug)] pub enum TokenizeError { #[error("Unexpected character '{char}' at line {line}, column {column}")] - UnexpectedCharacter { char: char, line: usize, column: usize }, + UnexpectedCharacter { + char: char, + line: usize, + column: usize, + }, #[error("Unterminated string literal at line {line}")] UnterminatedString { line: usize }, diff --git a/src/tokenizer/lex_ident.rs b/src/tokenizer/lex_ident.rs index 7ea061f2..5c00c255 100644 --- a/src/tokenizer/lex_ident.rs +++ b/src/tokenizer/lex_ident.rs @@ -82,8 +82,10 @@ impl NyashTokenizer { ); if is_stage3 { if std::env::var("NYASH_TOK_TRACE").ok().as_deref() == Some("1") { - eprintln!("[tok-stage3] Degrading {:?} to IDENTIFIER (NYASH_PARSER_STAGE3={})", - tok, stage3_enabled); + eprintln!( + "[tok-stage3] Degrading {:?} to IDENTIFIER (NYASH_PARSER_STAGE3={})", + tok, stage3_enabled + ); } tok = TokenType::IDENTIFIER(identifier.clone()); } @@ -101,8 +103,10 @@ impl NyashTokenizer { | TokenType::IN ); if is_stage3 { - eprintln!("[tok-stage3] Keeping {:?} as keyword (NYASH_PARSER_STAGE3={})", - tok, stage3_enabled); + eprintln!( + "[tok-stage3] Keeping {:?} as keyword (NYASH_PARSER_STAGE3={})", + tok, stage3_enabled + ); } } } diff --git a/src/tokenizer/lex_number.rs b/src/tokenizer/lex_number.rs index f8b4975b..8370e563 100644 --- a/src/tokenizer/lex_number.rs +++ b/src/tokenizer/lex_number.rs @@ -40,4 +40,3 @@ impl NyashTokenizer { } } } - diff --git a/src/tokenizer/lex_string.rs b/src/tokenizer/lex_string.rs index 5e75881f..9ca20150 100644 --- a/src/tokenizer/lex_string.rs +++ b/src/tokenizer/lex_string.rs @@ -27,14 +27,16 @@ impl NyashTokenizer { Some('\\') => string_value.push('\\'), Some('"') => string_value.push('"'), Some('\'') => string_value.push('\''), // 1-quote: エスケープされたシングルクォート - Some('/') => string_value.push('/'), // \/ を許容 + Some('/') => string_value.push('/'), // \/ を許容 Some('u') => { // Unicode decode (optional; default OFF) if crate::config::env::parser_decode_unicode() { let base = self.position; // index of 'u' - // read 4 hex digits without consuming; then advance position in bulk + // read 4 hex digits without consuming; then advance position in bulk let read_hex4 = |input: &Vec, start: usize| -> Option { - if start + 4 > input.len() { return None; } + if start + 4 > input.len() { + return None; + } let d0 = input.get(start)?.to_digit(16)?; let d1 = input.get(start + 1)?.to_digit(16)?; let d2 = input.get(start + 2)?.to_digit(16)?; @@ -52,7 +54,8 @@ impl NyashTokenizer { && self.input.get(self.position) == Some(&'\\') && self.input.get(self.position + 1) == Some(&'u') { - if let Some(u2) = read_hex4(&self.input, self.position + 2) { + if let Some(u2) = read_hex4(&self.input, self.position + 2) + { if (0xDC00..=0xDFFF).contains(&u2) { let high_ten = (u1 - 0xD800) as u32; let low_ten = (u2 - 0xDC00) as u32; diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index f3387e09..b6c815e2 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -2,13 +2,13 @@ * Nyash Tokenizer — split modules (kinds/cursor/whitespace/lexers/engine) */ -mod kinds; mod cursor; -mod whitespace; -mod lex_string; -mod lex_number; -mod lex_ident; mod engine; +mod kinds; +mod lex_ident; +mod lex_number; +mod lex_string; +mod whitespace; pub use kinds::{Token, TokenType, TokenizeError}; @@ -21,4 +21,3 @@ pub struct NyashTokenizer { } // Public API and core logic are implemented in submodules via impl NyashTokenizer - diff --git a/src/tokenizer/whitespace.rs b/src/tokenizer/whitespace.rs index c62c1a73..ac8e1dfd 100644 --- a/src/tokenizer/whitespace.rs +++ b/src/tokenizer/whitespace.rs @@ -40,4 +40,3 @@ impl NyashTokenizer { } } } - diff --git a/src/using/mod.rs b/src/using/mod.rs index f8da5042..785c54c1 100644 --- a/src/using/mod.rs +++ b/src/using/mod.rs @@ -12,9 +12,9 @@ and plugin metadata fusion (nyash_box.toml / embedded BID).\ */ -pub mod resolver; -pub mod spec; -pub mod policy; pub mod errors; +pub mod policy; +pub mod resolver; pub mod simple_registry; +pub mod spec; pub mod ssot_bridge; diff --git a/src/using/policy.rs b/src/using/policy.rs index b1533338..7d274845 100644 --- a/src/using/policy.rs +++ b/src/using/policy.rs @@ -4,4 +4,3 @@ pub struct UsingPolicy { pub search_paths: Vec, // from [using.paths] } - diff --git a/src/using/resolver.rs b/src/using/resolver.rs index 4decce3f..09d33c00 100644 --- a/src/using/resolver.rs +++ b/src/using/resolver.rs @@ -25,8 +25,8 @@ pub fn populate_from_toml( for name in candidates.iter() { let p = std::path::Path::new(name); if p.exists() { - let txt = std::fs::read_to_string(p) - .map_err(|e| UsingError::ReadToml(e.to_string()))?; + let txt = + std::fs::read_to_string(p).map_err(|e| UsingError::ReadToml(e.to_string()))?; found = Some((txt, p.to_path_buf())); break; } @@ -48,8 +48,8 @@ pub fn populate_from_toml( // 3) Fallback: empty content and path Ok(found.unwrap_or((String::new(), std::path::PathBuf::from("")))) }?; - let doc = toml::from_str::(&text) - .map_err(|e| UsingError::ParseToml(e.to_string()))?; + let doc = + toml::from_str::(&text).map_err(|e| UsingError::ParseToml(e.to_string()))?; let toml_dir = toml_path .parent() .map(|p| p.to_path_buf()) @@ -59,7 +59,11 @@ pub fn populate_from_toml( if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) { fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) { for (k, v) in tbl.iter() { - let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) }; + let name = if prefix.is_empty() { + k.to_string() + } else { + format!("{}.{}", prefix, k) + }; if let Some(s) = v.as_str() { out.push((name, s.to_string())); } else if let Some(t) = v.as_table() { @@ -97,15 +101,35 @@ pub fn populate_from_toml( } // named packages: any subtable not paths/aliases is a package for (k, v) in using_tbl.iter() { - if k == "paths" || k == "aliases" { continue; } + if k == "paths" || k == "aliases" { + continue; + } if let Some(tbl) = v.as_table() { - let kind = tbl.get("kind").and_then(|x| x.as_str()).map(PackageKind::from_str).unwrap_or(PackageKind::Package); + let kind = tbl + .get("kind") + .and_then(|x| x.as_str()) + .map(PackageKind::from_str) + .unwrap_or(PackageKind::Package); // path is required if let Some(path_s) = tbl.get("path").and_then(|x| x.as_str()) { let path = path_s.to_string(); - let main = tbl.get("main").and_then(|x| x.as_str()).map(|s| s.to_string()); - let bid = tbl.get("bid").and_then(|x| x.as_str()).map(|s| s.to_string()); - packages.insert(k.to_string(), UsingPackage { kind, path, main, bid }); + let main = tbl + .get("main") + .and_then(|x| x.as_str()) + .map(|s| s.to_string()); + let bid = tbl + .get("bid") + .and_then(|x| x.as_str()) + .map(|s| s.to_string()); + packages.insert( + k.to_string(), + UsingPackage { + kind, + path, + main, + bid, + }, + ); } } } @@ -139,7 +163,9 @@ pub fn resolve_using_target_common( ) -> Result { // 1) modules mapping if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { - if verbose { eprintln!("[using/resolve] modules '{}' -> '{}'", tgt, p); } + if verbose { + eprintln!("[using/resolve] modules '{}' -> '{}'", tgt, p); + } return Ok(p.clone()); } // 2) named packages @@ -147,28 +173,43 @@ pub fn resolve_using_target_common( match pkg.kind { PackageKind::Dylib => { let out = format!("dylib:{}", pkg.path); - if verbose { eprintln!("[using/resolve] dylib '{}' -> '{}'", tgt, out); } + if verbose { + eprintln!("[using/resolve] dylib '{}' -> '{}'", tgt, out); + } return Ok(out); } PackageKind::Package => { let base = std::path::Path::new(&pkg.path); let out = if let Some(m) = &pkg.main { - if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { pkg.path.clone() } else { base.join(m).to_string_lossy().to_string() } } else { - if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) { + if matches!( + base.extension().and_then(|s| s.to_str()), + Some("nyash") | Some("hako") + ) { pkg.path.clone() } else { let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(tgt); let hako = base.join(format!("{}.hako", leaf)); - if hako.exists() { hako.to_string_lossy().to_string() } - else { base.join(format!("{}.hako", leaf)).to_string_lossy().to_string() } + if hako.exists() { + hako.to_string_lossy().to_string() + } else { + base.join(format!("{}.hako", leaf)) + .to_string_lossy() + .to_string() + } } }; - if verbose { eprintln!("[using/resolve] package '{}' -> '{}'", tgt, out); } + if verbose { + eprintln!("[using/resolve] package '{}' -> '{}'", tgt, out); + } return Ok(out); } } @@ -179,26 +220,41 @@ pub fn resolve_using_target_common( let mut cand: Vec = Vec::new(); if let Some(dir) = context_dir { let c1 = dir.join(&rel_hako); - if c1.exists() { cand.push(c1.to_string_lossy().to_string()); } + if c1.exists() { + cand.push(c1.to_string_lossy().to_string()); + } let c2 = dir.join(&rel_ny); - if c2.exists() { cand.push(c2.to_string_lossy().to_string()); } + if c2.exists() { + cand.push(c2.to_string_lossy().to_string()); + } } for base in using_paths { let p = std::path::Path::new(base); let c1 = p.join(&rel_hako); - if c1.exists() { cand.push(c1.to_string_lossy().to_string()); } + if c1.exists() { + cand.push(c1.to_string_lossy().to_string()); + } let c2 = p.join(&rel_ny); - if c2.exists() { cand.push(c2.to_string_lossy().to_string()); } + if c2.exists() { + cand.push(c2.to_string_lossy().to_string()); + } } if cand.is_empty() { - if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); } - return Err(format!("using: unresolved '{}': searched relative and using.paths", tgt)); + if verbose { + eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); + } + return Err(format!( + "using: unresolved '{}': searched relative and using.paths", + tgt + )); } if cand.len() > 1 && strict { return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", "))); } let out = cand.remove(0); - if verbose { eprintln!("[using/resolve] '{}' -> '{}'", tgt, out); } + if verbose { + eprintln!("[using/resolve] '{}' -> '{}'", tgt, out); + } Ok(out) } @@ -211,12 +267,20 @@ fn load_workspace_modules( let members = workspace_tbl .get("members") .and_then(|v| v.as_array()) - .ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "expected members array".into()))?; + .ok_or_else(|| { + UsingError::ParseWorkspaceModule( + "modules.workspace".into(), + "expected members array".into(), + ) + })?; for entry in members { - let raw_path = entry - .as_str() - .ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "members must be string paths".into()))?; + let raw_path = entry.as_str().ok_or_else(|| { + UsingError::ParseWorkspaceModule( + "modules.workspace".into(), + "members must be string paths".into(), + ) + })?; let module_path = if std::path::Path::new(raw_path).is_absolute() { std::path::PathBuf::from(raw_path) } else { @@ -227,10 +291,16 @@ fn load_workspace_modules( .map(|p| p.to_path_buf()) .unwrap_or_else(|| nyash_dir.to_path_buf()); let module_text = std::fs::read_to_string(&module_path).map_err(|e| { - UsingError::ReadWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string()) + UsingError::ReadWorkspaceModule( + module_path.to_string_lossy().to_string(), + e.to_string(), + ) })?; let module_doc = toml::from_str::(&module_text).map_err(|e| { - UsingError::ParseWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string()) + UsingError::ParseWorkspaceModule( + module_path.to_string_lossy().to_string(), + e.to_string(), + ) })?; let module_name = module_doc .get("module") diff --git a/src/using/simple_registry.rs b/src/using/simple_registry.rs index 73b4319f..bd6c3165 100644 --- a/src/using/simple_registry.rs +++ b/src/using/simple_registry.rs @@ -1,8 +1,8 @@ //! Simple ModuleRegistry for Phase 1 diagnostics //! Collects published symbols (top-level `static box Name`) from using targets. -use std::collections::{HashMap, HashSet}; use once_cell::sync::Lazy; +use std::collections::{HashMap, HashSet}; use std::sync::Mutex; static CACHE: Lazy>>> = @@ -14,7 +14,9 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec { let mut results: Vec = Vec::new(); let snap = crate::runtime::modules_registry::snapshot_names_and_strings(); let wanted = symbol.trim(); - if wanted.is_empty() { return results; } + if wanted.is_empty() { + return results; + } for (name, path_token) in snap { // Skip builtin/dylib marker tokens @@ -31,7 +33,9 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec { if let Some(p) = resolve_path(&path_token) { if let Ok(content) = std::fs::read_to_string(&p) { let syms = scan_static_boxes(&content); - for s in syms { set.insert(s); } + for s in syms { + set.insert(s); + } } } } @@ -47,9 +51,15 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec { fn resolve_path(token: &str) -> Option { let mut p = std::path::PathBuf::from(token); if p.is_relative() { - if let Ok(abs) = std::fs::canonicalize(&p) { p = abs; } + if let Ok(abs) = std::fs::canonicalize(&p) { + p = abs; + } + } + if p.exists() { + Some(p) + } else { + None } - if p.exists() { Some(p) } else { None } } fn scan_static_boxes(content: &str) -> Vec { @@ -58,13 +68,21 @@ fn scan_static_boxes(content: &str) -> Vec { let mut out = Vec::new(); for line in content.lines() { let t = line.trim_start(); - if t.starts_with("//") { continue; } + if t.starts_with("//") { + continue; + } if let Some(rest) = t.strip_prefix("static box ") { let mut name = String::new(); for ch in rest.chars() { - if ch.is_ascii_alphanumeric() || ch == '_' { name.push(ch); } else { break; } + if ch.is_ascii_alphanumeric() || ch == '_' { + name.push(ch); + } else { + break; + } + } + if !name.is_empty() { + out.push(name); } - if !name.is_empty() { out.push(name); } } } out diff --git a/src/using/spec.rs b/src/using/spec.rs index 54b1b6f1..dcfe689c 100644 --- a/src/using/spec.rs +++ b/src/using/spec.rs @@ -39,4 +39,3 @@ pub struct UsingPackage { pub main: Option, pub bid: Option, } - diff --git a/src/using/ssot_bridge.rs b/src/using/ssot_bridge.rs index 24d69775..dab3f3be 100644 --- a/src/using/ssot_bridge.rs +++ b/src/using/ssot_bridge.rs @@ -21,10 +21,14 @@ pub struct SsotCtx { /// - Only consults `modules` map (exact match). /// - Does not access filesystem nor invoke Hako VM. pub fn call_using_resolve_ssot(name: &str, ctx: &SsotCtx) -> Option { - if name.is_empty() { return None; } + if name.is_empty() { + return None; + } // Optional: delegate to Hako resolver when explicitly requested. if std::env::var("HAKO_USING_SSOT_HAKO").ok().as_deref() == Some("1") { - if let Some(hit) = call_hako_box(name, ctx) { return Some(hit); } + if let Some(hit) = call_hako_box(name, ctx) { + return Some(hit); + } } // MVP: modules-only ctx.modules.get(name).cloned() @@ -72,7 +76,8 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option { let mut tf = match tempfile::Builder::new() .prefix("ny_ssot_") .suffix(".hako") - .tempfile() { + .tempfile() + { Ok(f) => f, Err(e) => { if crate::config::env::fail_fast() { @@ -85,8 +90,12 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option { let _ = write!(tf, "{}", code); let path = tf.path().to_path_buf(); // Resolve nyash binary; Fail-Fast aware fallback - let bin = if let Ok(b) = std::env::var("NYASH_BIN") { b } else { - if let Ok(p) = std::env::current_exe() { p.to_string_lossy().to_string() } else { + let bin = if let Ok(b) = std::env::var("NYASH_BIN") { + b + } else { + if let Ok(p) = std::env::current_exe() { + p.to_string_lossy().to_string() + } else { if crate::config::env::fail_fast() { eprintln!("[failfast/ssot/nyash-bin] unable to resolve NYASH_BIN/current_exe"); panic!("Fail-Fast: cannot resolve nyash binary for SSOT child"); @@ -97,7 +106,9 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option { // Stage‑3 + tolerance (matches smokes wrappers) let mut cmd = Command::new(bin); - cmd.arg("--backend").arg("vm").arg(&path) + cmd.arg("--backend") + .arg("vm") + .arg(&path) // Parser/entry tolerances (same as smokes "safe" mode) .env("NYASH_PARSER_STAGE3", "1") .env("HAKO_PARSER_STAGE3", "1") @@ -136,5 +147,7 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option { panic!("Fail-Fast: SSOT child produced empty output"); } None - } else { Some(s) } + } else { + Some(s) + } } diff --git a/tests/mir_instruction_unit.rs b/tests/mir_instruction_unit.rs index 96685566..bd32e618 100644 --- a/tests/mir_instruction_unit.rs +++ b/tests/mir_instruction_unit.rs @@ -37,7 +37,7 @@ fn test_call_instruction() { let inst = MirInstruction::Call { dst: Some(dst), func, - callee: None, // Legacy mode for test + callee: None, // Legacy mode for test args: vec![arg1, arg2], effects: EffectMask::IO, }; diff --git a/tests/mir_static_main_args_loop.rs b/tests/mir_static_main_args_loop.rs index 0c38949e..e69b38a7 100644 --- a/tests/mir_static_main_args_loop.rs +++ b/tests/mir_static_main_args_loop.rs @@ -1,9 +1,8 @@ +use std::fs; /// Bug A investigation: main(args) causes loops not to execute /// This test reproduces the issue where adding a parameter to main() /// causes the loop body to never execute (RC=0 instead of RC=3) - use std::process::Command; -use std::fs; #[test] fn mir_static_main_no_args_loop() { @@ -73,7 +72,10 @@ static box Main { let exit_code = output.status.code().unwrap_or(-1); // This will FAIL due to the bug - loop doesn't execute - assert_eq!(exit_code, 3, "Expected RC=3 for main(args) with loop (BUG: currently returns 0)"); + assert_eq!( + exit_code, 3, + "Expected RC=3 for main(args) with loop (BUG: currently returns 0)" + ); } #[test] diff --git a/tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh index e99ec4b3..d91bf7b7 100644 --- a/tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh @@ -53,6 +53,7 @@ OUT_RAW=$( NYASH_JSON_ONLY=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_STAGEB_FUNC_SCAN=1 \ + HAKO_VM_MAX_STEPS=0 \ HAKO_STAGEB_APPLY_USINGS=0 \ "$NYASH_BIN" --backend vm "$ROOT_DIR/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$TMP_HAKO")" 2>&1 )