diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 71d25c1d..180f2e3c 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -908,13 +908,92 @@ Toggles(既定OFF) - HAKO_CORE_DIRECT=1 / HAKO_CORE_DIRECT_INPROC=1 — MIR(JSON) を Core 直行で実行 - SMOKES_ENABLE_SELFHOST=1 — EXE カナリアを有効化 -Near‑term Tasks -- docs: phase‑20.34(README/PLAN/CHECKLIST)を追加(本ファイル含む) -- scaffold: MirBuilderBox.hako / LLVMEmitBox.hako の最小 I/F(未対応は Fail‑Fast) -- smokes: Program→MIR(return/binop/if)、.o 生成(SKIP許容)、EXE(opt‑in) -- provider: plugin spec ドキュメント(ny-llvmc/llvmlite どちらも裏側として許容) +Near‑term Tasks(進捗) +- docs: phase‑20.34(README/PLAN/CHECKLIST)を追加 — DONE +- scaffold: MirBuilderBox.hako / LLVMEmitBox.hako の最小 I/F — DONE(タグ・トグル固定) +- provider: Host providers を extern 経由で接続 — DONE + - env.mirbuilder.emit: Program(JSON v0) → MIR(JSON v0) + - env.codegen.emit_object: MIR(JSON v0) → ny-llvmc → .o + - hostbridge.extern_invoke(name, method, args?): VM 側で env.mirbuilder/env.codegen へ橋渡し(ArrayBox 先頭要素を文字列化) +- smokes: phase2034 カナリア追加 — DONE(MirBuilder=PASS、LLVMEmit=ny-llvmc不在/未解決はSKIP) +- next: LLVMCodegenBox(Hako ABI プラグインBox)実装&hako.toml登録 → extern TTL 撤退 +- next: MirBuilderBox 内蔵実装(const/binop/ret → compare/branch/jump/phi)を段階導入(委譲縮小) Acceptance - quick: 新規カナリア PASS(SKIPはポリシー通り) - integration: 既存緑維持、ノイズ増加なし - docs: Env/TTL/Fail‑Fast を明文化 + +Update — 2025‑11‑03 (VM extern wiring + canary fix) +- Rust VM へ extern 受け口を配線し、delegate→core カナリアを安定化。 + - handlers/externals.rs: ExternCall("env.mirbuilder","emit"), ("env.codegen","emit_object"), ("hostbridge","extern_invoke") を実装。 + - handlers/calls.rs: legacy/global 解決形("hostbridge.extern_invoke" / "…/3")もブリッジ。 +- カナリア更新: phase2034/mirbuilder_varvar_delegate_core_canary_vm.sh は JSON 抽出を jq 検証に変更(整形出力を許容)。→ PASS + +Action Plan — A→B→C(docs first, code later) + A) LLVM provider 明確化(docs+運用) + - 方針: LLVMEmit は provider‑first。ny-llvmc あり: 実行、なし: 明示タグで SKIP。 + - トグル: `HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite`(既定OFF)。ny-llvmc 選択時は `env.codegen.emit_object` を呼び出す。 + - 受理条件: ny-llvmc 不在時に `[llvmemit/ny-llvmc/not-found]` が安定出力。存在時は `.o` を生成しパスを返す。 + - Docs 整理: CURRENT_TASK(本節)に運用とタグ、トグルの最小セットを記載(完了)。README/phase docs は次ステップで同期。 + + B) MirBuilder 内蔵化(第一段・委譲縮小) + - 範囲: Program(JSON v0) → MIR(JSON v0) の最小命令(const/binop/ret)を MirBuilderBox 内で直接生成。 + - ガード: 既定は挙動不変(委譲)。`HAKO_MIR_BUILDER_DELEGATE=1` で明示的に委譲経路を使用。 + - タグ不変: 入力不正は `[mirbuilder/input/invalid]`、委譲未配線は `[mirbuilder/delegate/missing]` を維持。 + - 受理条件: canary(return/binop/if)で MIR(JSON) の `functions/blocks` キーを検出し PASS。差分は局所・可逆。 + + C) Canary 運用(昇格ポリシー) + - MirBuilder canary: quick 既定への昇格候補(現状 PASS)。導線は `tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh`。 + - LLVMEmit canary: ny-llvmc 検出時のみ PASS、それ以外は `[llvmemit/ny-llvmc/not-found]` で SKIP 継続。 + - Gate: 昇格は「A 完了+B 第一段 PASS」が条件。ny-llvmc 依存の昇格は行わない(SKIP 運用を維持)。 + +Env Quick Reference(20.34 関連) + - `HAKO_MIR_BUILDER_DELEGATE=1` — Program→MIR を Runner へ委譲 + - `HAKO_MIR_BUILDER_INTERNAL=1` — 内蔵の最小変換(Return(Int)→const+ret)を有効化(委譲縮小の第一段) + - `HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite` — LLVM emit provider 選択 + - `NYASH_GATE_C_CORE=1`(互換: `HAKO_GATE_C_CORE=1`)— MIR(JSON) を Core 直行で検証 + +Next Tasks (Phase 20.34 wrap‑up → 20.35) +- P0 (now) + - Run canaries with preinclude=ON + inline=OFF to lock structure: + - tools/smokes/v2/profiles/quick/core/string_vm_api_canary_vm.sh + - phase2034/mirbuilder_internal_return_logical_varvar_{lower,via_builder}_canary_vm.sh + - VM include error message: add guidance (using+alias recommended; NYASH_PREINCLUDE=1 for dev) +- P1 (20.34) + - Finish include→using+alias migration (remaining lowers/boxes if any) + - Add CI static check: forbid `include "…"` in `lang/src/**` + - Docs: include=deprecated / using+alias=primary / preinclude=dev‑only +- P2 (20.35) + - Remove preinclude path from runner; archive tools/dev/hako_preinclude.sh + - Flip primary verifier to Hakorune VM (HAKO_VERIFY_PRIMARY=hakovm) once phase2034 canaries green + - Optional: switch MirBuilder to registry path after parity check + +Checklist (A→B→C — 2025‑11‑03) +- A) LLVM provider 明確化: DONE(env `HAKO_LLVM_EMIT_PROVIDER`、タグ: `[llvmemit/provider/missing]` ほか) +- B) MirBuilder 内蔵化(1st): IN‑PROGRESS(Return/Int・Binary(Int,Int)・Var/Int・Var/Var 等の lowers は内蔵パスで起動、追加の JsonFragBox/PatternUtilBox 適用は段階導入中) +- C) Canary 運用: PASS(mirbuilder_canary_vm / binop/varint、logical/mixed)。var/var core 直行の emit 検証は追跡中(非既定/opt‑in)。 + +Next (small, structure‑first) +- JsonFragBox adoption: 残存の手書きスキャンを置換(if/compare varint/varvar、method args の int 系)。 +- PatternUtilBox rollout: logical/return/binop 系の重複逆引きを順次委譲。 +- MirBuilder registry: `HAKO_MIR_BUILDER_REGISTRY=1` で parity を継続確認しつつ、呼び順の一本化を検討。 +- VM mir_call: `size/len/push/get/set` のタグ強化と最小状態(recv_id)設計を切り出し。既定は STUB タグ維持。 +- Verify primary: `HAKO_VERIFY_PRIMARY=hakovm` 経路の file‑read 受け口を追加し、Core fallback を段階縮小。 +Update — 2025-11-03(Phase 20.34 — P1/P2 sweep, JsonFrag/PatternUtil 統一) +- P1(手書きスキャン撤廃): 完了 + - internal lowers の数値/真偽/文字列抽出を JsonFragBox に統一。 + - Var→Local 逆引きを PatternUtilBox に統一(map_cmp も共通化)。 + - 対象(代表): lower_if_compare_* / lower_return_binop_* / lower_return_var_local 他。 +- P2(loop_form 系 include → using): 完了 + - lower_loop_simple/count_param/sum_bc から include を撤廃、using のみへ統一。 +- カナリア/スモーク + - internal lowers 構造系は緑(var/var logical lower 直行も PASS)。 + - MirBuilder→Core 直行系は emit 成功、rc 検証は当面 Hakorune VM primary(HAKO_VERIFY_PRIMARY=hakovm)で実施。 +- ランナー/テスト安定化 + - test_runner 未割当変数バグ修正(prefile)。 + - emit 出力の JSON 抽出に jq を導入(先頭ノイズ除去)。 + +Notes +- 仕様不変・既定挙動不変(トグルは既定OFF)。 +- include は lang内の一部(compiler_stageb/core_bridge)のみ残存(別PRで移行)。 diff --git a/docs/updates/phase2034-p1p2-sweep.md b/docs/updates/phase2034-p1p2-sweep.md new file mode 100644 index 00000000..f4e2354e --- /dev/null +++ b/docs/updates/phase2034-p1p2-sweep.md @@ -0,0 +1,29 @@ +# Phase 20.34 — P1/P2 Sweep (JsonFrag/PatternUtil, loop_form using) + +Status: Completed (2025-11-03) + +Scope +- P1: Replace ad‑hoc JSON scans with JsonFragBox and unify reverse lookups via PatternUtilBox. +- P2: Remove `include` from loop_form lowers and rely on `using` only. + +Changes +- JsonFragBox adoption + - Use `read_int_after(text, kv+8)`, `read_bool_after(text, kv+8)`, `read_string_after(text, k+5)` consistently. + - Locate keys with `index_of_from(text, "\"key\":", pos)`. +- PatternUtilBox adoption + - `find_local_int_before(text, name, before_pos)` and `find_local_bool_before(...)` for reverse lookups. + - Optional: `map_cmp` for operator mapping (<, >, <=, >=, ==, != → Lt, Gt, ...). +- loop_form lowers + - `lower_loop_simple/count_param/sum_bc`: remove `include` of loop_form; keep `using selfhost.shared.mir.loopform as LoopFormBox`. + +Verification +- Internal lowers canaries: PASS (structure, value paths). Logical Var/Var lower (direct) is green. +- Emit→Core rc verification: use `verify_mir_rc` with `HAKO_VERIFY_PRIMARY=hakovm` while Core rc line is being normalized. +- Test runner fixes: guard unset `prefile`; JSON extraction via `jq` with leading noise stripping. + +Policy +- Behavior invariant (fail‑fast). New toggles default OFF. Minimal, localized diffs only. + +Next +- Continue registry migration for MirBuilder (toggle‑guarded). +- Migrate remaining non‑internal `include` sites in a separate PR. diff --git a/lang/src/mir/builder/MirBuilderBox.hako b/lang/src/mir/builder/MirBuilderBox.hako index 367622a3..fdcb7df6 100644 --- a/lang/src/mir/builder/MirBuilderBox.hako +++ b/lang/src/mir/builder/MirBuilderBox.hako @@ -32,6 +32,288 @@ static box MirBuilderBox { print("[mirbuilder/input/invalid] missing version/kind keys") return null } + // Internal minimal path (guarded) — const(int)+ret, or const+const+binop+ret (Phase 20.34 B step) + // Toggle: HAKO_MIR_BUILDER_INTERNAL=1 + { + local internal = env.get("HAKO_MIR_BUILDER_INTERNAL") + if internal != null && ("" + internal) == "1" { + // Optional: registry-driven lowering (scaffold). When HAKO_MIR_BUILDER_REGISTRY=1, + // iterate PatternRegistryBox.candidates() and dispatch by name. + // NOTE: include not supported in VM, registry disabled for VM execution + local use_reg = env.get("HAKO_MIR_BUILDER_REGISTRY") + if use_reg != null && ("" + use_reg) == "1" && 0 == 1 { + local names = PatternRegistryBox.candidates() + local i = 0; local n = names.length() + loop(i < n) { + local nm = "" + names.get(i) + if nm == "if.compare.intint" { local out = LowerIfCompareBox.try_lower(s); if out != null { return out } } + if nm == "if.compare.fold.binints" { local out = LowerIfCompareFoldBinIntsBox.try_lower(s); if out != null { return out } } + if nm == "if.compare.fold.varint" { local out = LowerIfCompareFoldVarIntBox.try_lower(s); if out != null { return out } } + if nm == "if.compare.varint" { local out = LowerIfCompareVarIntBox.try_lower(s); if out != null { return out } } + if nm == "if.compare.varvar" { local out = LowerIfCompareVarVarBox.try_lower(s); if out != null { return out } } + if nm == "return.method.arraymap" { local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { return out } } + if nm == "return.var.local" { local out = LowerReturnVarLocalBox.try_lower(s); if out != null { return out } } + if nm == "return.string" { local out = LowerReturnStringBox.try_lower(s); if out != null { return out } } + if nm == "return.float" { local out = LowerReturnFloatBox.try_lower(s); if out != null { return out } } + if nm == "return.bool" { local out = LowerReturnBoolBox.try_lower(s); if out != null { return out } } + if nm == "return.logical" { local out = LowerReturnLogicalBox.try_lower(s); if out != null { return out } } + if nm == "return.binop.varint" { local out = LowerReturnBinOpVarIntBox.try_lower(s); if out != null { return out } } + if nm == "return.binop.varvar" { local out = LowerReturnBinOpVarVarBox.try_lower(s); if out != null { return out } } + if nm == "return.binop.intint" { local out = LowerReturnBinOpBox.try_lower(s); if out != null { return out } } + if nm == "return.int" { local out = LowerReturnIntBox.try_lower(s); if out != null { return out } } + i = i + 1 + } + // Fall-through to chain if none matched + } + // Boxified lowers via using+alias (prefer using over include; VM include unsupported) + using "hako.mir.builder.internal.lower_if_then_else_following_return" as LowerIfThenElseFollowingReturnBox + using "hako.mir.builder.internal.lower_if_nested" as LowerIfNestedBox + using "hako.mir.builder.internal.lower_if_compare" as LowerIfCompareBox + using "hako.mir.builder.internal.lower_if_compare_fold_binints" as LowerIfCompareFoldBinIntsBox + using "hako.mir.builder.internal.lower_if_compare_fold_varint" as LowerIfCompareFoldVarIntBox + using "hako.mir.builder.internal.lower_if_compare_varint" as LowerIfCompareVarIntBox + using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox + using "hako.mir.builder.internal.lower_loop_sum_bc" as LowerLoopSumBcBox + using "hako.mir.builder.internal.lower_loop_count_param" as LowerLoopCountParamBox + using "hako.mir.builder.internal.lower_loop_simple" as LowerLoopSimpleBox + using "hako.mir.builder.internal.lower_return_var_local" as LowerReturnVarLocalBox + using "hako.mir.builder.internal.lower_return_string" as LowerReturnStringBox + using "hako.mir.builder.internal.lower_return_float" as LowerReturnFloatBox + using "hako.mir.builder.internal.lower.logical" as LowerReturnLogicalBox + using "hako.mir.builder.internal.lower_return_method_array_map" as LowerReturnMethodArrayMapBox + using "hako.mir.builder.internal.lower_return_bool" as LowerReturnBoolBox + using "hako.mir.builder.internal.lower_return_binop_varint" as LowerReturnBinOpVarIntBox + using "hako.mir.builder.internal.lower_return_binop_varvar" as LowerReturnBinOpVarVarBox + using "hako.mir.builder.internal.lower_return_binop" as LowerReturnBinOpBox + using "hako.mir.builder.internal.lower_return_int" as LowerReturnIntBox + { local out_if2b = LowerIfNestedBox.try_lower(s); if out_if2b != null { return out_if2b } } + { local out_if2 = LowerIfThenElseFollowingReturnBox.try_lower(s); if out_if2 != null { return out_if2 } } + { local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { return out_if } } + { local out_ifb = LowerIfCompareFoldBinIntsBox.try_lower(s); if out_ifb != null { return out_ifb } } + { local out_ifbv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_ifbv != null { return out_ifbv } } + { local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { return out_ifvi } } + { local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { return out_ifvv } } + { local out_loop2 = LowerLoopSumBcBox.try_lower(s); if out_loop2 != null { return out_loop2 } } + { local out_loopp = LowerLoopCountParamBox.try_lower(s); if out_loopp != null { return out_loopp } } + { local out_loop = LowerLoopSimpleBox.try_lower(s); if out_loop != null { return out_loop } } + { local out_var = LowerReturnVarLocalBox.try_lower(s); if out_var != null { return out_var } } + { local out_str = LowerReturnStringBox.try_lower(s); if out_str != null { return out_str } } + { local out_f = LowerReturnFloatBox.try_lower(s); if out_f != null { return out_f } } + { local out_log = LowerReturnLogicalBox.try_lower(s); if out_log != null { return out_log } } + { local out_meth = LowerReturnMethodArrayMapBox.try_lower(s); if out_meth != null { return out_meth } } + { local out_bool = LowerReturnBoolBox.try_lower(s); if out_bool != null { return out_bool } } + { local out_bvi = LowerReturnBinOpVarIntBox.try_lower(s); if out_bvi != null { return out_bvi } } + { local out_bvv = LowerReturnBinOpVarVarBox.try_lower(s); if out_bvv != null { return out_bvv } } + { local out_bin = LowerReturnBinOpBox.try_lower(s); if out_bin != null { return out_bin } } + { + local out_int = LowerReturnIntBox.try_lower(s) + if out_int != null { return out_int } + } + // Find Return marker (or If) + // Case (If with Compare + Return(Int)/Return(Int) in branches) + { + local k_if = s.indexOf("\"type\":\"If\"") + if k_if >= 0 { + // cond: Compare with Int lhs/rhs + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if) + if k_cmp >= 0 { + // op + local k_op2 = s.indexOf("\"op\":", k_cmp) + if k_op2 >= 0 { + local oi2 = k_op2 + 5; local on2 = s.length() + loop(oi2 < on2) { local ch = s.substring(oi2,oi2+1); if ch == "\"" { oi2 = oi2 + 1 break } if ch != " " { break } oi2 = oi2 + 1 } + local oj2 = oi2 + loop(oj2 < on2) { local ch2 = s.substring(oj2,oj2+1); if ch2 == "\"" { break } oj2 = oj2 + 1 } + local op2 = s.substring(oi2, oj2) + // support <,>,<=,>=,==,!= + if !(op2 == "<" || op2 == ">" || op2 == "<=" || op2 == ">=" || op2 == "==" || op2 == "!=") { + print("[mirbuilder/internal/unsupported] compare op: " + op2) + return null + } + // lhs Int + local lhs_val2 = null + { + local klhs2 = s.indexOf("\"lhs\":{", k_cmp) + if klhs2 >= 0 { + local ti = s.indexOf("\"type\":\"Int\"", klhs2) + if ti >= 0 { + local kv = s.indexOf("\"value\":", ti) + if kv >= 0 { + local i = kv + 8; local n = s.length(); + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i; if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { lhs_val2 = s.substring(i,j) } + } + } + } + } + // rhs Int + local rhs_val2 = null + { + local krhs2 = s.indexOf("\"rhs\":{", k_cmp) + if krhs2 >= 0 { + local ti = s.indexOf("\"type\":\"Int\"", krhs2) + if ti >= 0 { + local kv = s.indexOf("\"value\":", ti) + if kv >= 0 { + local i = kv + 8; local n = s.length(); + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i; if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { rhs_val2 = s.substring(i,j) } + } + } + } + } + // then: Return(Int ...) + local then_val = null + { + local kth = s.indexOf("\"then\":", k_if) + if kth >= 0 { + local rt = s.indexOf("\"type\":\"Return\"", kth) + if rt >= 0 { + local ti = s.indexOf("\"type\":\"Int\"", rt) + if ti >= 0 { + local kv = s.indexOf("\"value\":", ti) + if kv >= 0 { + local i = kv + 8; local n = s.length(); + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i; if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { then_val = s.substring(i,j) } + } + } + } + } + } + // else: Return(Int ...) + local else_val = null + { + local kel = s.indexOf("\"else\":", k_if) + if kel >= 0 { + local rt = s.indexOf("\"type\":\"Return\"", kel) + if rt >= 0 { + local ti = s.indexOf("\"type\":\"Int\"", rt) + if ti >= 0 { + local kv = s.indexOf("\"value\":", ti) + if kv >= 0 { + local i = kv + 8; local n = s.length(); + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i; if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { else_val = s.substring(i,j) } + } + } + } + } + } + if lhs_val2 != null && rhs_val2 != null && then_val != null && else_val != null { + // MIR: bb0(const lhs, const rhs, compare op -> v3, branch v3 then bb1 else bb2), bb1(const then, ret), bb2(const else, ret) + local mir_if = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[" + + "{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs_val2 + "}}," + + "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val2 + "}}," + + "{\"op\":\"compare\",\"operation\":\"" + op2 + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," + + "{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," + + "{\"label\":\"bb1\",\"instructions\":[{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + then_val + "}},{\"op\":\"ret\",\"value\":4}]}," + + "{\"label\":\"bb2\",\"instructions\":[{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":" + else_val + "}},{\"op\":\"ret\",\"value\":5}]}] }},\"blocks\":3}" + return mir_if + } + } + } + } + } + // Fallback cases below: Return(Binary) and Return(Int) + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret >= 0 { + // Case (Binary): {"type":"Binary","op":"+|-|*|/","lhs":{Int},"rhs":{Int}} + local k_bin = s.indexOf("\"type\":\"Binary\"", k_ret) + if k_bin >= 0 { + // op + local k_op = s.indexOf("\"op\":", k_bin) + if k_op >= 0 { + local oi = k_op + 5; local on = s.length() + loop(oi < on) { local ch = s.substring(oi,oi+1); if ch == "\"" { oi = oi + 1 break } if ch != " " { break } oi = oi + 1 } + local oj = oi + loop(oj < on) { local ch2 = s.substring(oj,oj+1); if ch2 == "\"" { break } oj = oj + 1 } + local op = s.substring(oi, oj) + if !(op == "+" || op == "-" || op == "*" || op == "/") { + print("[mirbuilder/internal/unsupported] binary op: " + op) + return null + } + // lhs Int value + local klhs = s.indexOf("\"lhs\":{", k_bin) + local lhs_val = null + if klhs >= 0 { + local ti = s.indexOf("\"type\":\"Int\"", klhs) + if ti >= 0 { + local kv = s.indexOf("\"value\":", ti) + if kv >= 0 { + local i = kv + 8; local n = s.length(); + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i; if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { lhs_val = s.substring(i,j) } + } + } + } + // rhs Int value + local krhs = s.indexOf("\"rhs\":{", k_bin) + local rhs_val = null + if krhs >= 0 { + local ti2 = s.indexOf("\"type\":\"Int\"", krhs) + if ti2 >= 0 { + local kv2 = s.indexOf("\"value\":", ti2) + if kv2 >= 0 { + local i2 = kv2 + 8; local n2 = s.length(); + loop(i2 < n2) { if s.substring(i2,i2+1) != " " { break } i2 = i2 + 1 } + local j2 = i2; if j2 < n2 && s.substring(j2,j2+1) == "-" { j2 = j2 + 1 } + local had2 = 0 + loop(j2 < n2) { local ch3 = s.substring(j2,j2+1); if ch3 >= "0" && ch3 <= "9" { had2 = 1 j2 = j2 + 1 } else { break } } + if had2 == 1 { rhs_val = s.substring(i2,j2) } + } + } + } + if lhs_val != null && rhs_val != null { + local mir_bin = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[" + + "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs_val + "}}," + + "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," + + "{\"op\":\"binop\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," + + "{\"op\":\"ret\",\"value\":3}]}] }},\"blocks\":1}" + return mir_bin + } + } + } + // Case (Int): Return(Int N) + local k_int = s.indexOf("\"type\":\"Int\"", k_ret) + if k_int >= 0 { + local k_val = s.indexOf("\"value\":", k_int) + if k_val >= 0 { + local i = k_val + 8 + local n = s.length() + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i + if j < n && s.substring(j,j+1) == "-" { j = j + 1 } + local had = 0 + loop(j < n) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 1 { + local num = s.substring(i, j) + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + num + "}},{\"op\":\"ret\",\"value\":1}] }] }},\"blocks\":1}" + return mir + } + } + } + } + // Unsupported internal shape → Fail‑Fast and return null + print("[mirbuilder/internal/unsupported] only Return(Int|Binary(Int,Int)) supported in internal mode") + return null + } + } // Delegate-first policy (Phase 20.34 Milestone A) local d = env.get("HAKO_MIR_BUILDER_DELEGATE") if d != null && ("" + d) == "1" { diff --git a/lang/src/mir/builder/internal/README.md b/lang/src/mir/builder/internal/README.md new file mode 100644 index 00000000..0dbcca00 --- /dev/null +++ b/lang/src/mir/builder/internal/README.md @@ -0,0 +1,22 @@ +# MirBuilder Internal Boxes — Minimal Shapes (Phase 20.34) + +Responsibility +- Provide small, readable boxes that lower a very small subset of Program(JSON v0) + into MIR(JSON v0) strings without heavy parsing. Fail‑Fast with stable tags. + +Boxes +- `prog_scan_box.hako` — tiny helpers for string scanning: find, skip spaces, read quoted/op/int values. +- `lower_return_int_box.hako` — Return(Int N) → const(i64 N) + ret. +- `lower_return_binop_box.hako` — Return(Binary(Int,Int) op {+,-,*,/}) → const, const, binop, ret. +- `lower_if_compare_box.hako` — If(Compare(Int,Int) then Return(Int) else Return(Int)) → compare, branch, ret×2. +- `lower_loop_simple_box.hako` — Loop(Compare i" || op == "<=" || op == ">=" || op == "==" || op == "!=") { return null } + // lhs/rhs ints + local klhs = s.indexOf("\"lhs\":{", k_cmp) + if klhs < 0 { return null } + local ti = s.indexOf("\"type\":\"Int\"", klhs) + if ti < 0 { return null } + local kv_lhs = s.indexOf("\"value\":", ti) + if kv_lhs < 0 { return null } + local lhs_val = JsonFragBox.read_int_after(s, kv_lhs + 8) + if lhs_val == null { return null } + local krhs = s.indexOf("\"rhs\":{", k_cmp) + if krhs < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", krhs) + if ti2 < 0 { return null } + local kv_rhs = s.indexOf("\"value\":", ti2) + if kv_rhs < 0 { return null } + local rhs_val = JsonFragBox.read_int_after(s, kv_rhs + 8) + if rhs_val == null { return null } + // then/else return ints + local kth = s.indexOf("\"then\":", k_if) + if kth < 0 { return null } + local rt = s.indexOf("\"type\":\"Return\"", kth) + if rt < 0 { return null } + local ti3 = s.indexOf("\"type\":\"Int\"", rt) + if ti3 < 0 { return null } + local kv_then = s.indexOf("\"value\":", ti3) + if kv_then < 0 { return null } + local then_val = JsonFragBox.read_int_after(s, kv_then + 8) + if then_val == null { return null } + local kel = s.indexOf("\"else\":", k_if) + if kel < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kel) + if rt2 < 0 { return null } + local ti4 = s.indexOf("\"type\":\"Int\"", rt2) + if ti4 < 0 { return null } + local kv_else = s.indexOf("\"value\":", ti4) + if kv_else < 0 { return null } + local else_val = JsonFragBox.read_int_after(s, kv_else + 8) + if else_val == null { return null } + + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[" + + "{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs_val + "}}," + + "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," + + "{\"op\":\"compare\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," + + "{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," + + "{\"label\":\"bb1\",\"instructions\":[{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + then_val + "}},{\"op\":\"ret\",\"value\":4}]}," + + "{\"label\":\"bb2\",\"instructions\":[{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":" + else_val + "}},{\"op\":\"ret\",\"value\":5}]}] }},\"blocks\":3}" + return mir + } +} diff --git a/lang/src/mir/builder/internal/lower_if_compare_fold_binints_box.hako b/lang/src/mir/builder/internal/lower_if_compare_fold_binints_box.hako new file mode 100644 index 00000000..78d0335a --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_compare_fold_binints_box.hako @@ -0,0 +1,86 @@ +// lower_if_compare_fold_binints_box.hako — If(Compare with Binary(Int,Int) on either side) → const-fold and lower + +using "hako.mir.builder.internal.prog_scan" as ProgScanBox +using ProgScanBox as Scan +using selfhost.shared.json.utils.json_frag as JsonFragBox +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox +using selfhost.shared.mir.schema as MirSchemaBox + +static box LowerIfCompareFoldBinIntsBox { + _map_cmp(op) { if op == "<" { return "Lt" } if op == ">" { return "Gt" } if op == "<=" { return "Le" } if op == ">=" { return "Ge" } if op == "==" { return "Eq" } if op == "!=" { return "Ne" } return null } + _fold_bin_ints(s, k_bin_start) { + // expects: {"type":"Binary","op":"+|-|*|/","lhs":{"type":"Int","value":L},"rhs":{"type":"Int","value":R}} + local kop = s.indexOf("\"op\":\"", k_bin_start); if kop < 0 { return null } + local iop = kop + 6; local op = s.substring(iop, iop+1) + if !(op == "+" || op == "-" || op == "*" || op == "/") { return null } + local kli = s.indexOf("\"type\":\"Int\"", k_bin_start); if kli < 0 { return null } + local kvl = s.indexOf("\"value\":", kli); if kvl < 0 { return null } + local l = JsonFragBox.read_int_after(s, kvl + 8); if l == null { return null } + // rhs int + local kri = s.indexOf("\"type\":\"Int\"", kli + 1); if kri < 0 { return null } + local kv2 = s.indexOf("\"value\":", kri); if kv2 < 0 { return null } + local r = JsonFragBox.read_int_after(s, kv2 + 8); if r == null { return null } + // compute + local li = 0; local ri = 0; // string to int by simple parse (assume i64 fits) + // naive parse via accumulate; rely on toString of concatenation is ok + li = 0 + ("" + l); ri = 0 + ("" + r) + local res = 0 + if op == "+" { res = li + ri } + else if op == "-" { res = li - ri } + else if op == "*" { res = li * ri } + else { res = li / ri } + return ("" + res) + } + _resolve_side_int(s, node_pos, if_pos) { + // node may be Int, Binary(Int,Int), or Var with Local Int before if_pos + if node_pos < 0 { return null } + // Int + if s.indexOf("\"type\":\"Int\"", node_pos) == node_pos { // best-effort + local kv = s.indexOf("\"value\":", node_pos); if kv < 0 { return null } + return JsonFragBox.read_int_after(s, kv + 8) + } + // Binary + if s.indexOf("\"type\":\"Binary\"", node_pos) == node_pos { + return me._fold_bin_ints(s, node_pos) + } + // Var(name) + if s.indexOf("\"type\":\"Var\"", node_pos) == node_pos { + local kn = s.indexOf("\"name\":\"", node_pos); if kn < 0 { return null } + local name = JsonFragBox.read_string_after(s, kn + 5); if name == null { return null } + // find last matching Local Int before if_pos via util + return PatternUtilBox.find_local_int_before(s, name, if_pos) + } + return null + } + try_lower(program_json){ + local s = "" + program_json + local k_if = s.indexOf("\"type\":\"If\""); if k_if < 0 { return null } + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if); if k_cmp < 0 { return null } + local op = me._map_cmp(Scan.read_quoted_after_key(s, k_cmp, "op")); if op == null { return null } + // locate lhs/rhs node starts (Var/Int/Binary) + local klhs = s.indexOf("\"lhs\":{", k_cmp); if klhs < 0 { return null } + local krhs = s.indexOf("\"rhs\":{", k_cmp); if krhs < 0 { return null } + local lhs_pos = klhs + 6 + local rhs_pos = krhs + 6 + local lhs_val = me._resolve_side_int(s, lhs_pos, k_if) + local rhs_val = me._resolve_side_int(s, rhs_pos, k_if) + if lhs_val == null || rhs_val == null { return null } + // then/else Return(Int) + local kth = s.indexOf("\"then\":", k_if); if kth < 0 { return null } + local rt1 = s.indexOf("\"type\":\"Return\"", kth); if rt1 < 0 { return null } + local ti1 = s.indexOf("\"type\":\"Int\"", rt1); if ti1 < 0 { return null } + local kv1 = s.indexOf("\"value\":", ti1); if kv1 < 0 { return null } + local then_v = JsonFragBox.read_int_after(s, kv1 + 8); if then_v == null { return null } + local kel = s.indexOf("\"else\":", k_if); if kel < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kel); if rt2 < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", rt2); if ti2 < 0 { return null } + local kv2b = s.indexOf("\"value\":", ti2); if kv2b < 0 { return null } + local else_v = JsonFragBox.read_int_after(s, kv2b + 8); if else_v == null { return null } + // Build MIR + local b0 = new ArrayBox(); b0.push(MirSchemaBox.inst_const(1, lhs_val)); b0.push(MirSchemaBox.inst_const(2, rhs_val)); b0.push(MirSchemaBox.inst_compare(op,1,2,3)); b0.push(MirSchemaBox.inst_branch(3,1,2)) + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(4, then_v)); b1.push(MirSchemaBox.inst_ret(4)) + local b2 = new ArrayBox(); b2.push(MirSchemaBox.inst_const(5, else_v)); b2.push(MirSchemaBox.inst_ret(5)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0,b0)); blocks.push(MirSchemaBox.block(1,b1)); blocks.push(MirSchemaBox.block(2,b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_if_compare_fold_varint_box.hako b/lang/src/mir/builder/internal/lower_if_compare_fold_varint_box.hako new file mode 100644 index 00000000..fd15222c --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_compare_fold_varint_box.hako @@ -0,0 +1,85 @@ +// lower_if_compare_fold_varint_box.hako — If(Compare with Binary(Var,Int) or Binary(Int,Var)) → fold using Local Int + +using selfhost.shared.mir.schema as MirSchemaBox +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerIfCompareFoldVarIntBox { + _fold_bin_varint(s, k_bin, if_pos) { + // Binary with one Var and one Int; resolve Var via Local and compute result + local kop = s.indexOf("\"op\":\"", k_bin); if kop < 0 { return null } + local op = JsonFragBox.read_string_after(s, kop + 5) + if !(op=="+"||op=="-"||op=="*"||op=="/") { return null } + local klv = s.indexOf("\"lhs\":{\"type\":\"Var\"", k_bin) + local kli = s.indexOf("\"lhs\":{\"type\":\"Int\"", k_bin) + local krv = s.indexOf("\"rhs\":{\"type\":\"Var\"", k_bin) + local kri = s.indexOf("\"rhs\":{\"type\":\"Int\"", k_bin) + local vval = null; local ival = null + if klv >= 0 && kri >= 0 { + local kn = s.indexOf("\"name\":", klv); if kn < 0 { return null } + local name = JsonFragBox.read_string_after(s, kn + 7) + vval = PatternUtilBox.find_local_int_before(s, name, if_pos) + local kv = s.indexOf("\"value\":", kri); if kv < 0 { return null } + ival = JsonFragBox.read_int_after(s, kv + 8) + } else if kli >= 0 && krv >= 0 { + local kv2 = s.indexOf("\"value\":", kli); if kv2 < 0 { return null } + ival = JsonFragBox.read_int_after(s, kv2 + 8) + local kn2 = s.indexOf("\"name\":", krv); if kn2 < 0 { return null } + local name2 = JsonFragBox.read_string_after(s, kn2 + 7) + vval = PatternUtilBox.find_local_int_before(s, name2, if_pos) + } + if vval == null || ival == null { return null } + local vi = 0 + ("" + vval); local ii2 = 0 + ("" + ival) + local res = 0 + if op=="+" { res = vi + ii2 } else if op=="-" { res = vi - ii2 } else if op=="*" { res = vi * ii2 } else { res = vi / ii2 } + return ("" + res) + } + _resolve_side(s, node_pos, if_pos) { + // Try Int + if s.indexOf("\"type\":\"Int\"", node_pos) == node_pos { + local kv = s.indexOf("\"value\":", node_pos); if kv < 0 { return null } + return JsonFragBox.read_int_after(s, kv + 8) + } + // Binary(Var,Int) or (Int,Var) + if s.indexOf("\"type\":\"Binary\"", node_pos) == node_pos { + return me._fold_bin_varint(s, node_pos, if_pos) + } + // Var → Local Int + if s.indexOf("\"type\":\"Var\"", node_pos) == node_pos { + local kn = s.indexOf("\"name\":", node_pos); if kn < 0 { return null } + local name = JsonFragBox.read_string_after(s, kn + 7) + return PatternUtilBox.find_local_int_before(s, name, if_pos) + } + return null + } + try_lower(program_json){ + local s = "" + program_json + local k_if = s.indexOf("\"type\":\"If\""); if k_if < 0 { return null } + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if); if k_cmp < 0 { return null } + local k_op = s.indexOf("\"op\":", k_cmp); if k_op < 0 { return null } + local op_sym = JsonFragBox.read_string_after(s, k_op + 5) + local op = PatternUtilBox.map_cmp(op_sym); if op == null { return null } + local klhs = s.indexOf("\"lhs\":{", k_cmp); if klhs < 0 { return null } + local krhs = s.indexOf("\"rhs\":{", k_cmp); if krhs < 0 { return null } + local lhs = me._resolve_side(s, klhs+6, k_if) + local rhs = me._resolve_side(s, krhs+6, k_if) + if lhs == null || rhs == null { return null } + // then/else Return(Int) + local kth = s.indexOf("\"then\":", k_if); if kth < 0 { return null } + local rt1 = s.indexOf("\"type\":\"Return\"", kth); if rt1 < 0 { return null } + local ti1 = s.indexOf("\"type\":\"Int\"", rt1); if ti1 < 0 { return null } + local kv1 = s.indexOf("\"value\":", ti1); if kv1 < 0 { return null } + local tv = JsonFragBox.read_int_after(s, kv1 + 8); if tv == null { return null } + local kel = s.indexOf("\"else\":", k_if); if kel < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kel); if rt2 < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", rt2); if ti2 < 0 { return null } + local kv2b = s.indexOf("\"value\":", ti2); if kv2b < 0 { return null } + local ev = JsonFragBox.read_int_after(s, kv2b + 8); if ev == null { return null } + // Build + local b0=new ArrayBox(); b0.push(MirSchemaBox.inst_const(1,lhs)); b0.push(MirSchemaBox.inst_const(2,rhs)); b0.push(MirSchemaBox.inst_compare(op,1,2,3)); b0.push(MirSchemaBox.inst_branch(3,1,2)) + local b1=new ArrayBox(); b1.push(MirSchemaBox.inst_const(4,tv)); b1.push(MirSchemaBox.inst_ret(4)) + local b2=new ArrayBox(); b2.push(MirSchemaBox.inst_const(5,ev)); b2.push(MirSchemaBox.inst_ret(5)) + local blocks=new ArrayBox(); blocks.push(MirSchemaBox.block(0,b0)); blocks.push(MirSchemaBox.block(1,b1)); blocks.push(MirSchemaBox.block(2,b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_if_compare_varint_box.hako b/lang/src/mir/builder/internal/lower_if_compare_varint_box.hako new file mode 100644 index 00000000..15ad820a --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_compare_varint_box.hako @@ -0,0 +1,62 @@ +// lower_if_compare_varint_box.hako — If(Compare Var vs Int | Int vs Var) with prior Local Int + +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerIfCompareVarIntBox { + try_lower(program_json){ + local s = "" + program_json + local k_if = s.indexOf("\"type\":\"If\""); if k_if < 0 { return null } + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if); if k_cmp < 0 { return null } + local k_op = s.indexOf("\"op\":", k_cmp); if k_op < 0 { return null } + local op_sym = JsonFragBox.read_string_after(s, k_op + 5) + local op = PatternUtilBox.map_cmp(op_sym); if op == null { return null } + // Var vs Int + local klhs_var = s.indexOf("\"lhs\":{\"type\":\"Var\"", k_cmp) + local krhs_int = s.indexOf("\"rhs\":{\"type\":\"Int\"", k_cmp) + local aval=null; local bval=null + if klhs_var >= 0 && krhs_int >= 0 { + local k_name = s.indexOf("\"name\":", klhs_var); if k_name < 0 { return null } + local name = JsonFragBox.read_string_after(s, k_name + 7) + aval = PatternUtilBox.find_local_int_before(s, name, k_if) + local ki = s.indexOf("\"type\":\"Int\"", krhs_int) + if ki >= 0 { + local kv = s.indexOf("\"value\":", ki); if kv < 0 { return null } + bval = JsonFragBox.read_int_after(s, kv + 8) + } + } else { + // Int vs Var + local klhs_int = s.indexOf("\"lhs\":{\"type\":\"Int\"", k_cmp) + local krhs_var = s.indexOf("\"rhs\":{\"type\":\"Var\"", k_cmp) + if klhs_int >= 0 && krhs_var >= 0 { + local ki2 = s.indexOf("\"type\":\"Int\"", klhs_int) + if ki2 >= 0 { + local kv2 = s.indexOf("\"value\":", ki2); if kv2 < 0 { return null } + aval = JsonFragBox.read_int_after(s, kv2 + 8) + } + local k_name2 = s.indexOf("\"name\":", krhs_var); if k_name2 < 0 { return null } + local name2 = JsonFragBox.read_string_after(s, k_name2 + 7) + if name2 != null { bval = PatternUtilBox.find_local_int_before(s, name2, k_if) } + } + } + if aval == null || bval == null { return null } + // then/else Return(Int) + local kth = s.indexOf("\"then\":", k_if); if kth < 0 { return null } + local rt1 = s.indexOf("\"type\":\"Return\"", kth); if rt1 < 0 { return null } + local ti1 = s.indexOf("\"type\":\"Int\"", rt1); if ti1 < 0 { return null } + local kv_then = s.indexOf("\"value\":", ti1); if kv_then < 0 { return null } + local then_v = JsonFragBox.read_int_after(s, kv_then + 8); if then_v == null { return null } + local kel = s.indexOf("\"else\":", k_if); if kel < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kel); if rt2 < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", rt2); if ti2 < 0 { return null } + local kv_else = s.indexOf("\"value\":", ti2); if kv_else < 0 { return null } + local else_v = JsonFragBox.read_int_after(s, kv_else + 8); if else_v == null { return null } + // Build MIR + local b0=new ArrayBox(); b0.push(MirSchemaBox.inst_const(1,aval)); b0.push(MirSchemaBox.inst_const(2,bval)); b0.push(MirSchemaBox.inst_compare(op,1,2,3)); b0.push(MirSchemaBox.inst_branch(3,1,2)) + local b1=new ArrayBox(); b1.push(MirSchemaBox.inst_const(4,then_v)); b1.push(MirSchemaBox.inst_ret(4)) + local b2=new ArrayBox(); b2.push(MirSchemaBox.inst_const(5,else_v)); b2.push(MirSchemaBox.inst_ret(5)) + local blocks=new ArrayBox(); blocks.push(MirSchemaBox.block(0,b0)); blocks.push(MirSchemaBox.block(1,b1)); blocks.push(MirSchemaBox.block(2,b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_if_compare_varvar_box.hako b/lang/src/mir/builder/internal/lower_if_compare_varvar_box.hako new file mode 100644 index 00000000..c4c2c3d6 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_compare_varvar_box.hako @@ -0,0 +1,65 @@ +// lower_if_compare_varvar_box.hako — If(Compare Var vs Var) with prior Local Int bindings +// Pattern: +// Local a=Int A; Local b=Int B; +// If(cond=Compare(op, lhs=Var a, rhs=Var b)) { Return(Int X) } else { Return(Int Y) } +// Lowers to const A/B → compare(op) → branch → ret X/Y + +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerIfCompareVarVarBox { + try_lower(program_json) { + local s = "" + program_json + local k_if = s.indexOf("\"type\":\"If\"") + if k_if < 0 { return null } + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if) + if k_cmp < 0 { return null } + // LHS/RHS Var names + local lhs_name = null + local klhs = s.indexOf("\"lhs\":{\"type\":\"Var\"", k_cmp) + if klhs >= 0 { + local k_name_lhs = s.indexOf("\"name\":", klhs); if k_name_lhs < 0 { return null } + lhs_name = JsonFragBox.read_string_after(s, k_name_lhs + 7) + } + local rhs_name = null + local krhs = s.indexOf("\"rhs\":{\"type\":\"Var\"", k_cmp) + if krhs >= 0 { + local k_name_rhs = s.indexOf("\"name\":", krhs); if k_name_rhs < 0 { return null } + rhs_name = JsonFragBox.read_string_after(s, k_name_rhs + 7) + } + if lhs_name == null || rhs_name == null { return null } + // Resolve locals + local aval = PatternUtilBox.find_local_int_before(s, lhs_name, k_if) + local bval = PatternUtilBox.find_local_int_before(s, rhs_name, k_if) + if aval == null || bval == null { return null } + // op map + local k_op = s.indexOf("\"op\":", k_cmp); if k_op < 0 { return null } + local sym = JsonFragBox.read_string_after(s, k_op + 5) + if sym == null { return null } + local op = PatternUtilBox.map_cmp(sym) + if op == null { return null } + // then/else Return(Int) + local kth = s.indexOf("\"then\":", k_if); if kth < 0 { return null } + local rt1 = s.indexOf("\"type\":\"Return\"", kth); if rt1 < 0 { return null } + local ti1 = s.indexOf("\"type\":\"Int\"", rt1); if ti1 < 0 { return null } + local kv_then = s.indexOf("\"value\":", ti1); if kv_then < 0 { return null } + local then_val = JsonFragBox.read_int_after(s, kv_then + 8); if then_val == null { return null } + local kel = s.indexOf("\"else\":", k_if); if kel < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kel); if rt2 < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", rt2); if ti2 < 0 { return null } + local kv_else = s.indexOf("\"value\":", ti2); if kv_else < 0 { return null } + local else_val = JsonFragBox.read_int_after(s, kv_else + 8); if else_val == null { return null } + + // Build MIR + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, aval)) + b0.push(MirSchemaBox.inst_const(2, bval)) + b0.push(MirSchemaBox.inst_compare(op, 1, 2, 3)) + b0.push(MirSchemaBox.inst_branch(3, 1, 2)) + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(4, then_val)); b1.push(MirSchemaBox.inst_ret(4)) + local b2 = new ArrayBox(); b2.push(MirSchemaBox.inst_const(5, else_val)); b2.push(MirSchemaBox.inst_ret(5)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)); blocks.push(MirSchemaBox.block(1, b1)); blocks.push(MirSchemaBox.block(2, b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_if_nested_box.hako b/lang/src/mir/builder/internal/lower_if_nested_box.hako new file mode 100644 index 00000000..99076aa9 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_nested_box.hako @@ -0,0 +1,106 @@ +// lower_if_nested_box.hako — Nested If lowering +// Pattern: +// If(cond1=Compare(Int,Int)) { +// Return(Int A) +// } else { +// If(cond2=Compare(Int,Int)) { Return(Int B) } else { Return(Int C) } +// } +// Lowers to: +// bb0: const lhs1,rhs1; compare1 → branch then:bb1 else:bb2 +// bb1: const A; ret +// bb2: const lhs2,rhs2; compare2 → branch then:bb3 else:bb4 +// bb3: const B; ret +// bb4: const C; ret + +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.mir.schema as MirSchemaBox + +static box LowerIfNestedBox { + _map_cmp(op) { + if op == "<" { return "Lt" } + if op == ">" { return "Gt" } + if op == "<=" { return "Le" } + if op == ">=" { return "Ge" } + if op == "==" { return "Eq" } + if op == "!=" { return "Ne" } + return null + } + + try_lower(program_json) { + local s = "" + program_json + local k_if1 = s.indexOf("\"type\":\"If\"") + if k_if1 < 0 { return null } + local k_cmp1 = s.indexOf("\"type\":\"Compare\"", k_if1) + if k_cmp1 < 0 { return null } + local kop1 = s.indexOf("\"op\":", k_cmp1); if kop1 < 0 { return null } + local op1s = JsonFragBox.read_string_after(s, kop1 + 5); if op1s == null { return null } + local op1 = me._map_cmp(op1s); if op1 == null { return null } + local klhs1 = s.indexOf("\"lhs\":{", k_cmp1); if klhs1 < 0 { return null } + local ti11 = s.indexOf("\"type\":\"Int\"", klhs1); if ti11 < 0 { return null } + local kv11 = s.indexOf("\"value\":", ti11); if kv11 < 0 { return null } + local lhs1 = JsonFragBox.read_int_after(s, kv11 + 8); if lhs1 == null { return null } + local krhs1 = s.indexOf("\"rhs\":{", k_cmp1); if krhs1 < 0 { return null } + local ti12 = s.indexOf("\"type\":\"Int\"", krhs1); if ti12 < 0 { return null } + local kv12 = s.indexOf("\"value\":", ti12); if kv12 < 0 { return null } + local rhs1 = JsonFragBox.read_int_after(s, kv12 + 8); if rhs1 == null { return null } + // then Return(Int A) + local kth = s.indexOf("\"then\":", k_if1); if kth < 0 { return null } + local rt1 = s.indexOf("\"type\":\"Return\"", kth); if rt1 < 0 { return null } + local ti13 = s.indexOf("\"type\":\"Int\"", rt1); if ti13 < 0 { return null } + local kv13 = s.indexOf("\"value\":", ti13); if kv13 < 0 { return null } + local aval = JsonFragBox.read_int_after(s, kv13 + 8); if aval == null { return null } + // else contains nested If with Return(Int)/Return(Int) + local kel = s.indexOf("\"else\":", k_if1); if kel < 0 { return null } + local k_if2 = s.indexOf("\"type\":\"If\"", kel); if k_if2 < 0 { return null } + local k_cmp2 = s.indexOf("\"type\":\"Compare\"", k_if2); if k_cmp2 < 0 { return null } + local kop2 = s.indexOf("\"op\":", k_cmp2); if kop2 < 0 { return null } + local op2s = JsonFragBox.read_string_after(s, kop2 + 5); if op2s == null { return null } + local op2 = me._map_cmp(op2s); if op2 == null { return null } + local klhs2 = s.indexOf("\"lhs\":{", k_cmp2); if klhs2 < 0 { return null } + local ti21 = s.indexOf("\"type\":\"Int\"", klhs2); if ti21 < 0 { return null } + local kv21 = s.indexOf("\"value\":", ti21); if kv21 < 0 { return null } + local lhs2 = JsonFragBox.read_int_after(s, kv21 + 8); if lhs2 == null { return null } + local krhs2 = s.indexOf("\"rhs\":{", k_cmp2); if krhs2 < 0 { return null } + local ti22 = s.indexOf("\"type\":\"Int\"", krhs2); if ti22 < 0 { return null } + local kv22 = s.indexOf("\"value\":", ti22); if kv22 < 0 { return null } + local rhs2 = JsonFragBox.read_int_after(s, kv22 + 8); if rhs2 == null { return null } + // then Return(Int B) + local kth2 = s.indexOf("\"then\":", k_if2); if kth2 < 0 { return null } + local rt2 = s.indexOf("\"type\":\"Return\"", kth2); if rt2 < 0 { return null } + local ti23 = s.indexOf("\"type\":\"Int\"", rt2); if ti23 < 0 { return null } + local kv23 = s.indexOf("\"value\":", ti23); if kv23 < 0 { return null } + local bval = JsonFragBox.read_int_after(s, kv23 + 8); if bval == null { return null } + // else Return(Int C) + local kel2 = s.indexOf("\"else\":", k_if2); if kel2 < 0 { return null } + local rt3 = s.indexOf("\"type\":\"Return\"", kel2); if rt3 < 0 { return null } + local ti24 = s.indexOf("\"type\":\"Int\"", rt3); if ti24 < 0 { return null } + local kv24 = s.indexOf("\"value\":", ti24); if kv24 < 0 { return null } + local cval = JsonFragBox.read_int_after(s, kv24 + 8); if cval == null { return null } + + // Build MIR(JSON) + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, lhs1)) + b0.push(MirSchemaBox.inst_const(2, rhs1)) + b0.push(MirSchemaBox.inst_compare(op1, 1, 2, 3)) + b0.push(MirSchemaBox.inst_branch(3, 1, 2)) + + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(4, aval)); b1.push(MirSchemaBox.inst_ret(4)) + + local b2 = new ArrayBox() + b2.push(MirSchemaBox.inst_const(5, lhs2)) + b2.push(MirSchemaBox.inst_const(6, rhs2)) + b2.push(MirSchemaBox.inst_compare(op2, 5, 6, 7)) + b2.push(MirSchemaBox.inst_branch(7, 3, 4)) + + local b3 = new ArrayBox(); b3.push(MirSchemaBox.inst_const(8, bval)); b3.push(MirSchemaBox.inst_ret(8)) + local b4 = new ArrayBox(); b4.push(MirSchemaBox.inst_const(9, cval)); b4.push(MirSchemaBox.inst_ret(9)) + + local blocks = new ArrayBox() + blocks.push(MirSchemaBox.block(0, b0)) + blocks.push(MirSchemaBox.block(1, b1)) + blocks.push(MirSchemaBox.block(2, b2)) + blocks.push(MirSchemaBox.block(3, b3)) + blocks.push(MirSchemaBox.block(4, b4)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_if_then_else_following_return_box.hako b/lang/src/mir/builder/internal/lower_if_then_else_following_return_box.hako new file mode 100644 index 00000000..c93b4eb2 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_if_then_else_following_return_box.hako @@ -0,0 +1,54 @@ +// lower_if_then_else_following_return_box.hako +// Pattern: Program body = [ If(cond=Compare(Int,Int), then=[Return(Int)] (no else)), Return(Int) ] +// Lowers to: bb0: const lhs/rhs, compare, branch; bb1: const then, ret; bb2: const else, ret + +using "hako.mir.builder.internal.prog_scan" as ProgScanBox +using ProgScanBox as Scan +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerIfThenElseFollowingReturnBox { + try_lower(program_json) { + local s = "" + program_json + // If with Compare(Int,Int) + local k_if = s.indexOf("\"type\":\"If\"") + if k_if < 0 { return null } + local k_cmp = s.indexOf("\"type\":\"Compare\"", k_if) + if k_cmp < 0 { return null } + local op = Scan.read_quoted_after_key(s, k_cmp, "op") + if op == null { return null } + if !(op == "<" || op == ">" || op == "<=" || op == ">=" || op == "==" || op == "!=") { return null } + // LHS/RHS ints + local klhs = s.indexOf("\"lhs\":{", k_cmp); if klhs < 0 { return null } + local ti1 = s.indexOf("\"type\":\"Int\"", klhs); if ti1 < 0 { return null } + { local kv = s.indexOf("\"value\":", ti1); if kv < 0 { return null }; var lhs_val = JsonFragBox.read_int_after(s, kv + 8); if lhs_val == null { return null } } + local krhs = s.indexOf("\"rhs\":{", k_cmp); if krhs < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", krhs); if ti2 < 0 { return null } + { local kv2 = s.indexOf("\"value\":", ti2); if kv2 < 0 { return null }; var rhs_val = JsonFragBox.read_int_after(s, kv2 + 8); if rhs_val == null { return null } } + // then: Return(Int) + local kth = s.indexOf("\"then\":", k_if); if kth < 0 { return null } + local rt = s.indexOf("\"type\":\"Return\"", kth); if rt < 0 { return null } + local ti3 = s.indexOf("\"type\":\"Int\"", rt); if ti3 < 0 { return null } + { local kv3 = s.indexOf("\"value\":", ti3); if kv3 < 0 { return null }; var then_val = JsonFragBox.read_int_after(s, kv3 + 8); if then_val == null { return null } } + // else is omitted → following Return(Int) in Program body + local k_after = s.indexOf("\"type\":\"Return\"", k_if + 1); if k_after < 0 { return null } + local ti4 = s.indexOf("\"type\":\"Int\"", k_after); if ti4 < 0 { return null } + { local kv4 = s.indexOf("\"value\":", ti4); if kv4 < 0 { return null }; var else_val = JsonFragBox.read_int_after(s, kv4 + 8); if else_val == null { return null } } + + // Build MIR(JSON) + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, lhs_val)) + b0.push(MirSchemaBox.inst_const(2, rhs_val)) + b0.push(MirSchemaBox.inst_compare(op == "<" ? "Lt" : (op == ">" ? "Gt" : (op == "<=" ? "Le" : (op == ">=" ? "Ge" : (op == "==" ? "Eq" : "Ne")))), 1, 2, 3)) + b0.push(MirSchemaBox.inst_branch(3, 1, 2)) + + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(4, then_val)); b1.push(MirSchemaBox.inst_ret(4)) + local b2 = new ArrayBox(); b2.push(MirSchemaBox.inst_const(5, else_val)); b2.push(MirSchemaBox.inst_ret(5)) + + local blocks = new ArrayBox() + blocks.push(MirSchemaBox.block(0, b0)) + blocks.push(MirSchemaBox.block(1, b1)) + blocks.push(MirSchemaBox.block(2, b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_loop_count_param_box.hako b/lang/src/mir/builder/internal/lower_loop_count_param_box.hako new file mode 100644 index 00000000..becc84cd --- /dev/null +++ b/lang/src/mir/builder/internal/lower_loop_count_param_box.hako @@ -0,0 +1,54 @@ +// lower_loop_count_param_box.hako — Loop(Compare i= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } } + if had == 0 { return null } + local limit = s.substring(i, j) + + // Delegate to shared loop form builder (counting mode) + return LoopFormBox.build("count", limit, null, null) + } +} diff --git a/lang/src/mir/builder/internal/lower_loop_sum_bc_box.hako b/lang/src/mir/builder/internal/lower_loop_sum_bc_box.hako new file mode 100644 index 00000000..6e528fe8 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_loop_sum_bc_box.hako @@ -0,0 +1,75 @@ +// lower_loop_sum_bc_box.hako — Loop with Compare(i= 0 { + // Find nearest previous Compare and grab rhs Int + local kbc = s.lastIndexOf("\"type\":\"Compare\"", kb) + if kbc >= 0 { + // Ensure op=="==" and lhs Var i + local bop = Scan.read_quoted_after_key(s, kbc, "op") + if bop != null && bop == "==" && s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", kbc) >= 0 { + local kbi = s.indexOf("\"type\":\"Int\"", kbc) + if kbi >= 0 { break_value = Scan.read_value_int_after(s, kbi) } + } + } + } + } + // Continue sentinel: If(cond Compare i==Y) then Continue + local skip_value = null + { + local kc = s.indexOf("\"type\":\"Continue\"", k_loop) + if kc >= 0 { + local kcc = s.lastIndexOf("\"type\":\"Compare\"", kc) + if kcc >= 0 { + local cop = Scan.read_quoted_after_key(s, kcc, "op") + if cop != null && cop == "==" && s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", kcc) >= 0 { + local kci = s.indexOf("\"type\":\"Int\"", kcc) + if kci >= 0 { skip_value = Scan.read_value_int_after(s, kci) } + } + } + } + } + + // Defaults when not present (LoopFormBox.loop_counter expects non-null ints) + if skip_value == null { skip_value = 2 } + if break_value == null { break_value = limit } + + return LoopFormBox.build("sum_bc", limit, skip_value, break_value) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_binop_box.hako b/lang/src/mir/builder/internal/lower_return_binop_box.hako new file mode 100644 index 00000000..0d2b0203 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_binop_box.hako @@ -0,0 +1,44 @@ +// lower_return_binop_box.hako — Return(Binary(Int,Int) op {+,-,*,/}) → const+const+binop+ret + +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnBinOpBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + local k_bin = s.indexOf("\"type\":\"Binary\"", k_ret) + if k_bin < 0 { return null } + // op + local k_op = s.indexOf("\"op\":", k_bin) + if k_op < 0 { return null } + local op = JsonFragBox.read_string_after(s, k_op + 5) + if op == null { return null } + if !(op == "+" || op == "-" || op == "*" || op == "/") { return null } + // lhs int + local klhs = s.indexOf("\"lhs\":{", k_bin) + if klhs < 0 { return null } + local ti = s.indexOf("\"type\":\"Int\"", klhs) + if ti < 0 { return null } + local kv_lhs = s.indexOf("\"value\":", ti) + if kv_lhs < 0 { return null } + local lhs_val = JsonFragBox.read_int_after(s, kv_lhs + 8) + if lhs_val == null { return null } + // rhs int + local krhs = s.indexOf("\"rhs\":{", k_bin) + if krhs < 0 { return null } + local ti2 = s.indexOf("\"type\":\"Int\"", krhs) + if ti2 < 0 { return null } + local kv_rhs = s.indexOf("\"value\":", ti2) + if kv_rhs < 0 { return null } + local rhs_val = JsonFragBox.read_int_after(s, kv_rhs + 8) + if rhs_val == null { return null } + + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[" + + "{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + lhs_val + "}}," + + "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," + + "{\"op\":\"binop\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," + + "{\"op\":\"ret\",\"value\":3}]}] }},\"blocks\":1}" + return mir + } +} diff --git a/lang/src/mir/builder/internal/lower_return_binop_varint_box.hako b/lang/src/mir/builder/internal/lower_return_binop_varint_box.hako new file mode 100644 index 00000000..d4016223 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_binop_varint_box.hako @@ -0,0 +1,76 @@ +// lower_return_binop_varint_box.hako — Return(Binary Var/Int with prior Local Int) +// Patterns: +// Local x=Int A; Return(Binary(op, lhs=Var x, rhs=Int B)) +// Local x=Int A; Return(Binary(op, lhs=Int B, rhs=Var x)) + +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnBinOpVarIntBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + local k_bin = s.indexOf("\"type\":\"Binary\"", k_ret) + if k_bin < 0 { return null } + local k_op = s.indexOf("\"op\":", k_bin) + if k_op < 0 { return null } + local op = JsonFragBox.read_string_after(s, k_op + 5) + if op == null { return null } + if !(op == "+" || op == "-" || op == "*" || op == "/") { return null } + // Try Var + Int + local klhs_var = s.indexOf("\"lhs\":{\"type\":\"Var\"", k_bin) + local krhs_int = s.indexOf("\"rhs\":{\"type\":\"Int\"", k_bin) + local var_name = null + local rhs_val = null + if klhs_var >= 0 && krhs_int >= 0 { + local k_name = s.indexOf("\"name\":", klhs_var); if k_name < 0 { return null } + var_name = JsonFragBox.read_string_after(s, k_name + 7) + local kvi = s.indexOf("\"type\":\"Int\"", krhs_int) + if kvi >= 0 { + local kv = s.indexOf("\"value\":", kvi); if kv < 0 { return null } + rhs_val = JsonFragBox.read_int_after(s, kv + 8) + } + if var_name != null && rhs_val != null { + local var_val = PatternUtilBox.find_local_int_before(s, var_name, k_ret) + if var_val != null { + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, var_val)) + b0.push(MirSchemaBox.inst_const(2, rhs_val)) + b0.push(MirSchemaBox.inst_binop(op == "+" ? "Add" : (op == "-" ? "Sub" : (op == "*" ? "Mul" : "Div")), 1, 2, 3)) + b0.push(MirSchemaBox.inst_ret(3)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } + } + } + // Try Int + Var + local klhs_int = s.indexOf("\"lhs\":{\"type\":\"Int\"", k_bin) + local krhs_var = s.indexOf("\"rhs\":{\"type\":\"Var\"", k_bin) + var_name = null + local lhs_val = null + if klhs_int >= 0 && krhs_var >= 0 { + local kvi2 = s.indexOf("\"type\":\"Int\"", klhs_int) + if kvi2 >= 0 { + local kv2 = s.indexOf("\"value\":", kvi2); if kv2 < 0 { return null } + lhs_val = JsonFragBox.read_int_after(s, kv2 + 8) + } + local k_name2 = s.indexOf("\"name\":", krhs_var); if k_name2 < 0 { return null } + var_name = JsonFragBox.read_string_after(s, k_name2 + 7) + if lhs_val != null && var_name != null { + local var_val2 = PatternUtilBox.find_local_int_before(s, var_name, k_ret) + if var_val2 != null { + local b1 = new ArrayBox() + b1.push(MirSchemaBox.inst_const(1, lhs_val)) + b1.push(MirSchemaBox.inst_const(2, var_val2)) + b1.push(MirSchemaBox.inst_binop(op == "+" ? "Add" : (op == "-" ? "Sub" : (op == "*" ? "Mul" : "Div")), 1, 2, 3)) + b1.push(MirSchemaBox.inst_ret(3)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b1)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } + } + } + return null + } +} diff --git a/lang/src/mir/builder/internal/lower_return_binop_varvar_box.hako b/lang/src/mir/builder/internal/lower_return_binop_varvar_box.hako new file mode 100644 index 00000000..8eba1164 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_binop_varvar_box.hako @@ -0,0 +1,26 @@ +// lower_return_binop_varvar_box.hako — Return(Binary Var vs Var) with prior Local Ints + +using "hako.mir.builder.internal.prog_scan" as ProgScanBox +using ProgScanBox as Scan +using selfhost.shared.mir.schema as MirSchemaBox +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox + +static box LowerReturnBinOpVarVarBox { + _find_local_int_before(s, name, before_pos) { return PatternUtilBox.find_local_int_before(s, name, before_pos) } + try_lower(program_json){ + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\""); if k_ret < 0 { return null } + local k_bin = s.indexOf("\"type\":\"Binary\"", k_ret); if k_bin < 0 { return null } + local op = Scan.read_quoted_after_key(s, k_bin, "op"); if op == null { return null } + if !(op == "+" || op == "-" || op == "*" || op == "/") { return null } + local klhs = s.indexOf("\"lhs\":{\"type\":\"Var\"", k_bin); if klhs < 0 { return null } + local krhs = s.indexOf("\"rhs\":{\"type\":\"Var\"", k_bin); if krhs < 0 { return null } + local lname = Scan.read_quoted_after_key(s, klhs, "name"); if lname == null { return null } + local rname = Scan.read_quoted_after_key(s, krhs, "name"); if rname == null { return null } + local lval = me._find_local_int_before(s, lname, k_ret); if lval == null { return null } + local rval = me._find_local_int_before(s, rname, k_ret); if rval == null { return null } + local b0=new ArrayBox(); b0.push(MirSchemaBox.inst_const(1,lval)); b0.push(MirSchemaBox.inst_const(2,rval)); b0.push(MirSchemaBox.inst_binop(op == "+" ? "Add" : (op == "-" ? "Sub" : (op == "*" ? "Mul" : "Div")),1,2,3)); b0.push(MirSchemaBox.inst_ret(3)) + local blocks=new ArrayBox(); blocks.push(MirSchemaBox.block(0,b0)); + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_bool_box.hako b/lang/src/mir/builder/internal/lower_return_bool_box.hako new file mode 100644 index 00000000..b85da136 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_bool_box.hako @@ -0,0 +1,23 @@ +// lower_return_bool_box.hako — Return(Bool) → const(1/0) + ret + +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnBoolBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + // find Bool value true/false after Return + local k_bool = s.indexOf("\"type\":\"Bool\"", k_ret) + if k_bool < 0 { return null } + local k_val = s.indexOf("\"value\":", k_bool) + if k_val < 0 { return null } + local is_true = JsonFragBox.read_bool_after(s, k_val+8) + if is_true == null { return null } + local v = is_true == 1 ? 1 : 0 + local b0 = new ArrayBox(); b0.push(MirSchemaBox.inst_const(1, v)); b0.push(MirSchemaBox.inst_ret(1)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_float_box.hako b/lang/src/mir/builder/internal/lower_return_float_box.hako new file mode 100644 index 00000000..2fb3248d --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_float_box.hako @@ -0,0 +1,21 @@ +// lower_return_float_box.hako — Return(Float) → const(f64) + ret + +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnFloatBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + local k_float = s.indexOf("\"type\":\"Float\"", k_ret) + if k_float < 0 { return null } + local k_val = s.indexOf("\"value\":", k_float) + if k_val < 0 { return null } + local fstr = JsonFragBox.read_float_after(s, k_val+8) + if fstr == null { return null } + local b0 = new ArrayBox(); b0.push(MirSchemaBox.inst_const_f64(1, fstr)); b0.push(MirSchemaBox.inst_ret(1)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_int_box.hako b/lang/src/mir/builder/internal/lower_return_int_box.hako new file mode 100644 index 00000000..e8126d35 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_int_box.hako @@ -0,0 +1,19 @@ +// lower_return_int_box.hako — Return(Int) → const+ret + +using "hako.mir.builder.internal.prog_scan" as ProgScanBox +using ProgScanBox as Scan + +static box LowerReturnIntBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + // Expect Int after Return + local k_int = s.indexOf("\"type\":\"Int\"", k_ret) + if k_int < 0 { return null } + local val = Scan.read_value_int_after(s, k_int) + if val == null { return null } + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + val + "}},{\"op\":\"ret\",\"value\":1}] }] }},\"blocks\":1}" + return mir + } +} diff --git a/lang/src/mir/builder/internal/lower_return_logical_box.hako b/lang/src/mir/builder/internal/lower_return_logical_box.hako new file mode 100644 index 00000000..d62b7d4a --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_logical_box.hako @@ -0,0 +1,80 @@ +// lower_return_logical_box.hako — Return(Logical &&/|| of Bool,Bool) → branch+ret (short-circuit semantics) + +using selfhost.shared.mir.schema as MirSchemaBox + +using "hako.mir.builder.internal.pattern_util" as PatternUtilBox + +using "hako.mir.builder.internal.prog_scan" as ProgScanBox + +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnLogicalBox { + // Removed: _find_local_bool_before (use PatternUtilBox.find_local_bool_before instead) + + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + local k_log = JsonFragBox.index_of_from(s, "\"type\":\"Logical\"", k_ret) + if k_log < 0 { return null } + // op: && or || (ProgScanBox is sufficient here) + local op = ProgScanBox.read_quoted_after_key(s, k_log, "op") + if op == null { return null } + if !(op == "&&" || op == "||") { return null } + // Resolve lhs to 0/1 (Bool or Var with Local Bool) - robust version + local lhs_true = null + { + local klhs_b = JsonFragBox.index_of_from(s, "\"lhs\":{\"type\":\"Bool\"", k_log) + if klhs_b >= 0 { + local kv1 = JsonFragBox.index_of_from(s, "\"value\":", klhs_b) + if kv1 >= 0 { + lhs_true = JsonFragBox.read_bool_after(s, kv1 + 8) + } + } else { + local klhs_v = JsonFragBox.index_of_from(s, "\"lhs\":{\"type\":\"Var\"", k_log) + if klhs_v >= 0 { + local name = ProgScanBox.read_quoted_after_key(s, klhs_v, "name") + if name != null { lhs_true = PatternUtilBox.find_local_bool_before(s, name, k_log) } + } + } + } + if lhs_true == null { return null } + // Resolve rhs to 0/1 (Bool or Var) - robust version + local rhs_true = null + { + local krhs_b = JsonFragBox.index_of_from(s, "\"rhs\":{\"type\":\"Bool\"", k_log) + if krhs_b >= 0 { + local kv2 = JsonFragBox.index_of_from(s, "\"value\":", krhs_b) + if kv2 >= 0 { + rhs_true = JsonFragBox.read_bool_after(s, kv2 + 8) + } + } else { + local krhs_v = JsonFragBox.index_of_from(s, "\"rhs\":{\"type\":\"Var\"", k_log) + if krhs_v >= 0 { + local name2 = ProgScanBox.read_quoted_after_key(s, krhs_v, "name") + if name2 != null { rhs_true = PatternUtilBox.find_local_bool_before(s, name2, k_log) } + } + } + } + if rhs_true == null { return null } + + // Build MIR: const r1=lhs, rT=1, rF=0; branch on lhs + // For &&: if lhs==0 → ret 0; else ret rhs + // For ||: if lhs==1 → ret 1; else ret rhs + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, lhs_true)) + b0.push(MirSchemaBox.inst_branch(1, 1, 2)) + + if op == "&&" { + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(3, rhs_true)); b1.push(MirSchemaBox.inst_ret(3)) + local b2 = new ArrayBox(); b2.push(MirSchemaBox.inst_const(4, 0)); b2.push(MirSchemaBox.inst_ret(4)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)); blocks.push(MirSchemaBox.block(1, b1)); blocks.push(MirSchemaBox.block(2, b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } else { + local b1 = new ArrayBox(); b1.push(MirSchemaBox.inst_const(3, 1)); b1.push(MirSchemaBox.inst_ret(3)) + local b2 = new ArrayBox(); b2.push(MirSchemaBox.inst_const(4, rhs_true)); b2.push(MirSchemaBox.inst_ret(4)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)); blocks.push(MirSchemaBox.block(1, b1)); blocks.push(MirSchemaBox.block(2, b2)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } + } +} diff --git a/lang/src/mir/builder/internal/lower_return_method_array_map_box.hako b/lang/src/mir/builder/internal/lower_return_method_array_map_box.hako new file mode 100644 index 00000000..c1887877 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_method_array_map_box.hako @@ -0,0 +1,118 @@ +// lower_return_method_array_map_box.hako — Return(Method recv Var, method in {size,length,len,get,set,push}) +// Emits a minimal mir_call(Method) structure (not executed in VM/Core path here). + +using selfhost.shared.mir.schema as MirSchemaBox +using "selfhost.shared.json.utils.json_frag" as JsonFragBox + +static box LowerReturnMethodArrayMapBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\""); if k_ret < 0 { return null } + local k_m = s.indexOf("\"type\":\"Method\"", k_ret); if k_m < 0 { return null } + // receiver must be Var(name) + local k_recv = s.indexOf("\"recv\":{\"type\":\"Var\"", k_m); if k_recv < 0 { return null } + local method = null + { local km = s.indexOf("\"method\":\"", k_m); if km < 0 { return null } method = JsonFragBox.read_string_after(s, km) } + // Allow basic methods + if !(method == "size" || method == "length" || method == "len" || method == "get" || method == "set" || method == "push") { return null } + // Parse up to two Int args with actual values + local args_ids = new ArrayBox() + local b0 = new ArrayBox() + // Receiver placeholder (Var resolve未実装のため 0 を置く) + b0.push(MirSchemaBox.inst_const(1, 0)) + local k_args = s.indexOf("\"args\":", k_m) + local next_id = 2 + if k_args >= 0 { + // first arg: Int or Var(Int) + local k_i1 = s.indexOf("\"type\":\"Int\"", k_args) + local k_v1 = s.indexOf("\"type\":\"Var\"", k_args) + if k_i1 >= 0 && (k_v1 < 0 || k_i1 < k_v1) { + // read numeric after value: + local k_val1 = s.indexOf("\"value\":", k_i1) + if k_val1 >= 0 { + local v1 = JsonFragBox.read_int_after(s, k_val1 + 8) + if v1 != null { b0.push(MirSchemaBox.inst_const(next_id, v1)) args_ids.push(next_id) next_id = next_id + 1 } + } + // second arg after first + local k_i2 = s.indexOf("\"type\":\"Int\"", k_i1 + 1) + local k_v2a = s.indexOf("\"type\":\"Var\"", k_i1 + 1) + if k_i2 >= 0 { + local k_v2 = s.indexOf("\"value\":", k_i2) + if k_v2 >= 0 { local v2 = JsonFragBox.read_int_after(s, k_v2 + 8); if v2 != null { b0.push(MirSchemaBox.inst_const(next_id, v2)) args_ids.push(next_id) next_id = next_id + 1 } } + } else if k_v2a >= 0 { + // second arg is Var: resolve Local Int value + local kn = s.indexOf("\"name\":\"", k_v2a) + if kn >= 0 { + local name2 = JsonFragBox.read_string_after(s, kn) + // scan backwards for Local name2 Int + local pos = 0; local last = -1 + loop(true) { + local kL = s.indexOf("\"type\":\"Local\"", pos) + if kL < 0 || kL >= k_m { break } + local nm = null + { + local kn2 = s.indexOf("\"name\":\"", kL) + if kn2 >= 0 { + local iii = kn2 + 8; local nnn = s.length(); local jjj = iii + loop(jjj < nnn) { if s.substring(jjj,jjj+1) == '"' { break } jjj = jjj + 1 } + nm = s.substring(iii, jjj) + } + } + if nm != null && nm == name2 { last = kL } + pos = kL + 1 + } + if last >= 0 { + local ki = s.indexOf("\"type\":\"Int\"", last) + if ki >= 0 && ki < k_m { + local kv = s.indexOf("\"value\":", ki) + if kv >= 0 { local v = JsonFragBox.read_int_after(s, kv + 8); if v != null { b0.push(MirSchemaBox.inst_const(next_id, v)) args_ids.push(next_id) next_id = next_id + 1 } } + } + } + } + } + } else if k_v1 >= 0 && (k_i1 < 0 || k_v1 < k_i1) { + // first arg is Var: resolve Local value (Int/Bool/Float/String) + local kn = s.indexOf("\"name\":\"", k_v1) + if kn >= 0 { + local namev = JsonFragBox.read_string_after(s, kn) + // find last Local namev Int before method + local pos2 = 0; local last2 = -1 + loop(true) { + local kL2 = s.indexOf("\"type\":\"Local\"", pos2) + if kL2 < 0 || kL2 >= k_m { break } + local nm2 = null + { + local knm = s.indexOf("\"name\":\"", kL2) + if knm >= 0 { + local iii2 = knm + 8; local nnn2 = s.length(); local jjj2 = iii2 + loop(jjj2 < nnn2) { if s.substring(jjj2,jjj2+1) == '"' { break } jjj2 = jjj2 + 1 } + nm2 = s.substring(iii2, jjj2) + } + } + if nm2 != null && nm2 == namev { last2 = kL2 } + pos2 = kL2 + 1 + } + if last2 >= 0 { + // Int + local ki3 = s.indexOf("\"type\":\"Int\"", last2) + if ki3 >= 0 && ki3 < k_m { local kv3 = s.indexOf("\"value\":", ki3); if kv3 >= 0 { local v = JsonFragBox.read_int_after(s, kv3 + 8); if v != null { b0.push(MirSchemaBox.inst_const(next_id, v)) args_ids.push(next_id) next_id = next_id + 1 } } } + // Bool + local kb3 = s.indexOf("\"type\":\"Bool\"", last2) + if kb3 >= 0 && kb3 < k_m { local kvb = s.indexOf("\"value\":", kb3); if kvb >= 0 { local vb = JsonFragBox.read_bool_after(s, kvb + 8); if vb != null { b0.push(MirSchemaBox.inst_const(next_id, vb)) args_ids.push(next_id) next_id = next_id + 1 } } } + // Float + local kf3 = s.indexOf("\"type\":\"Float\"", last2) + if kf3 >= 0 && kf3 < k_m { local kvf = s.indexOf("\"value\":", kf3); if kvf >= 0 { local fv = JsonFragBox.read_float_after(s, kvf + 8); if fv != null { b0.push(MirSchemaBox.inst_const_f64(next_id, fv)) args_ids.push(next_id) next_id = next_id + 1 } } } + // String + local ks3 = s.indexOf("\"type\":\"String\"", last2) + if ks3 >= 0 && ks3 < k_m { local kvs = s.indexOf("\"value\":\"", ks3); if kvs >= 0 { local sv = JsonFragBox.read_string_after(s, kvs + 8); if sv != null { b0.push(MirSchemaBox.inst_const_str(next_id, sv)) args_ids.push(next_id) next_id = next_id + 1 } } } + } + } + } + } + // mir_call method -> dst 4, ret 4 + b0.push(MirSchemaBox.inst_mir_call_method(method, 1, args_ids, 4)) + b0.push(MirSchemaBox.inst_ret(4)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_string_box.hako b/lang/src/mir/builder/internal/lower_return_string_box.hako new file mode 100644 index 00000000..3d392579 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_string_box.hako @@ -0,0 +1,21 @@ +// lower_return_string_box.hako — Return(String) → const(string) + ret + +using selfhost.shared.mir.schema as MirSchemaBox +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box LowerReturnStringBox { + try_lower(program_json) { + local s = "" + program_json + local k_ret = s.indexOf("\"type\":\"Return\"") + if k_ret < 0 { return null } + local k_str = s.indexOf("\"type\":\"String\"", k_ret) + if k_str < 0 { return null } + local k_val = s.indexOf("\"value\":", k_str) + if k_val < 0 { return null } + local sval = JsonFragBox.read_string_after(s, k_val+8) + if sval == null { return null } + local b0 = new ArrayBox(); b0.push(MirSchemaBox.inst_const_str(1, sval)); b0.push(MirSchemaBox.inst_ret(1)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/lower_return_var_local_box.hako b/lang/src/mir/builder/internal/lower_return_var_local_box.hako new file mode 100644 index 00000000..93ea4741 --- /dev/null +++ b/lang/src/mir/builder/internal/lower_return_var_local_box.hako @@ -0,0 +1,39 @@ +// lower_return_var_local_box.hako — Pattern: [ Local name=Int, Return Var(name) ] → const+ret + +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.mir.schema as MirSchemaBox + +static box LowerReturnVarLocalBox { + try_lower(program_json) { + local s = "" + program_json + // Find Local with name:"x" and expr Int value + local k_loc = s.indexOf("\"type\":\"Local\"") + if k_loc < 0 { return null } + // Extract name + local k_name = s.indexOf("\"name\":", k_loc); if k_name < 0 { return null } + local name = JsonFragBox.read_string_after(s, k_name + 5) + if name == null || name == "" { return null } + // Ensure expr Int after this Local + local k_int = s.indexOf("\"type\":\"Int\"", k_loc) + if k_int < 0 { return null } + local kv = s.indexOf("\"value\":", k_int); if kv < 0 { return null } + local val = JsonFragBox.read_int_after(s, kv + 8) + if val == null { return null } + // Following Return Var(name) + local k_ret = s.indexOf("\"type\":\"Return\"", k_loc) + if k_ret < 0 { return null } + // Verify Var(name) + local k_var = s.indexOf("\"type\":\"Var\"", k_ret) + if k_var < 0 { return null } + local k_vn = s.indexOf("\"name\":", k_var); if k_vn < 0 { return null } + local vname = JsonFragBox.read_string_after(s, k_vn + 5) + if vname == null || vname != name { return null } + + // Build MIR(JSON): const+ret + local b0 = new ArrayBox() + b0.push(MirSchemaBox.inst_const(1, val)) + b0.push(MirSchemaBox.inst_ret(1)) + local blocks = new ArrayBox(); blocks.push(MirSchemaBox.block(0, b0)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } +} diff --git a/lang/src/mir/builder/internal/pattern_util_box.hako b/lang/src/mir/builder/internal/pattern_util_box.hako new file mode 100644 index 00000000..c46a07c4 --- /dev/null +++ b/lang/src/mir/builder/internal/pattern_util_box.hako @@ -0,0 +1,23 @@ +// pattern_util_box.hako — Shared utilities for MirBuilder lowers + +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box PatternUtilBox { + map_cmp(sym) { if sym=="<" {return "Lt"} if sym==">" {return "Gt"} if sym=="<=" {return "Le"} if sym==">=" {return "Ge"} if sym=="==" {return "Eq"} if sym=="!=" {return "Ne"} return null } + find_local_int_before(s, name, before_pos) { + local pos=0; local last=-1 + loop(true){ local k=JsonFragBox.index_of_from(s, "\"type\":\"Local\"",pos); if k<0||k>=before_pos{break}; local kn=JsonFragBox.index_of_from(s, "\"name\":\"",k); if kn>=0{ local ii=kn+8; local nn=s.length(); local jj=ii; loop(jj=before_pos { return null } + local kv=s.indexOf("\"value\":",ki); if kv<0 { return null } + return JsonFragBox.read_int_after(s, kv+8) + } + find_local_bool_before(s, name, before_pos) { + local pos=0; local last=-1 + loop(true){ local k=JsonFragBox.index_of_from(s, "\"type\":\"Local\"",pos); if k<0||k>=before_pos{break}; local kn=JsonFragBox.index_of_from(s, "\"name\":\"",k); if kn>=0{ local ii=kn+8; local nn=s.length(); local jj=ii; loop(jj=before_pos { return null } + local kv=s.indexOf("\"value\":",kb); if kv<0 { return null } + return JsonFragBox.read_bool_after(s, kv+8) + } +} diff --git a/lang/src/mir/builder/internal/prog_scan_box.hako b/lang/src/mir/builder/internal/prog_scan_box.hako new file mode 100644 index 00000000..6a4c4e25 --- /dev/null +++ b/lang/src/mir/builder/internal/prog_scan_box.hako @@ -0,0 +1,67 @@ +// prog_scan_box.hako — Tiny string scanner helpers for Program(JSON v0) + +static box ProgScanBox { + // Find substring starting at or after pos; returns index or -1 + find(s, pat, pos) { + local s1 = "" + s + local p = pos + if p < 0 { p = 0 } + if p >= s1.length() { return -1 } + local sub = s1.substring(p) + local k = sub.indexOf("" + pat) + if k < 0 { return -1 } + return p + k + } + + // Skip ASCII spaces from pos; returns first non-space index + skip_spaces(s, pos) { + local i = pos + local n = ("" + s).length() + loop(i < n) { if ("" + s).substring(i,i+1) != " " { break } i = i + 1 } + return i + } + + // Read an integer (optional '-') starting at pos; returns string or null + read_int_at(s, pos) { + local i = pos + local n = ("" + s).length() + if i < n && ("" + s).substring(i,i+1) == "-" { i = i + 1 } + local j = i + local had = 0 + loop(j < n) { + local ch = ("" + s).substring(j,j+1) + if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } + } + if had == 1 { return ("" + s).substring(pos, j) } + return null + } + + // Read quoted string at or after key: "key": "..." (returns content or null) + read_quoted_after_key(s, start, key) { + local s1 = "" + s + local k = me.find(s1, "\"" + key + "\":", start) + if k < 0 { return null } + local i = k + 3 + key.length() // position after colon + local n = s1.length() + // skip spaces until first quote + loop(i < n) { + local ch = s1.substring(i,i+1) + if ch == "\"" { i = i + 1 break } + if ch != " " { break } + i = i + 1 + } + local j = i + loop(j < n) { if s1.substring(j,j+1) == "\"" { break } j = j + 1 } + if j <= n { return s1.substring(i, j) } + return null + } + + // Read integer value after a "value": key near start + read_value_int_after(s, start) { + local s1 = "" + s + local k = me.find(s1, "\"value\":", start) + if k < 0 { return null } + local i = me.skip_spaces(s1, k + 8) + return me.read_int_at(s1, i) + } +} diff --git a/lang/src/mir/builder/pattern_registry.hako b/lang/src/mir/builder/pattern_registry.hako new file mode 100644 index 00000000..90946a79 --- /dev/null +++ b/lang/src/mir/builder/pattern_registry.hako @@ -0,0 +1,26 @@ +// pattern_registry.hako — Minimal priority registry for MirBuilder lowers (scaffold) + +static box PatternRegistryBox { + // Return prioritized candidate names (subset); MirBuilder maps names to concrete try_lower calls. + candidates() { + local a = new ArrayBox() + // Higher first + a.push("if.compare.intint") + a.push("if.compare.fold.binints") + a.push("if.compare.fold.varint") + a.push("if.compare.varint") + a.push("if.compare.varvar") + a.push("return.method.arraymap") + a.push("return.var.local") + a.push("return.string") + a.push("return.float") + a.push("return.bool") + a.push("return.logical") + a.push("return.binop.varint") + a.push("return.binop.varvar") + a.push("return.binop.intint") + a.push("return.int") + return a + } +} + diff --git a/lang/src/shared/json/utils/json_frag.hako b/lang/src/shared/json/utils/json_frag.hako index 8a765d30..2fc8c336 100644 --- a/lang/src/shared/json/utils/json_frag.hako +++ b/lang/src/shared/json/utils/json_frag.hako @@ -6,10 +6,86 @@ using selfhost.shared.json.core.json_cursor as JsonCursorBox using selfhost.shared.common.string_helpers as StringHelpers static box JsonFragBox { - // 基本ヘルパ - index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) } + // 基本ヘルパ - VM fallback implementations for cross-box static calls + index_of_from(hay, needle, pos) { + // VM fallback: implement using substring + indexOf + if hay == null || needle == null { return -1 } + local s = "" + hay + local n = s.length() + local p2 = pos + if p2 < 0 { p2 = 0 } + if p2 >= n { return -1 } + // Extract substring from pos onwards + local substr = s.substring(p2, n) + // Find needle in substring + local idx = substr.indexOf(needle) + if idx < 0 { return -1 } + return p2 + idx + } read_digits(text, pos) { return StringHelpers.read_digits(text, pos) } _str_to_int(s) { return StringHelpers.to_i64(s) } + _to_bool10(ch) { if ch == "t" { return 1 } if ch == "f" { return 0 } return null } + + // Read helpers (pos-based) + read_int_from(text, pos) { + if text == null { return null } + local s = "" + text + local i = pos + local n = s.length() + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i + if j < n && (s.substring(j,j+1) == "-" || s.substring(j,j+1) == "+") { j = j + 1 } + local had = 0 + loop(j < n) { + local ch = s.substring(j,j+1) + if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } + } + if had == 0 { return null } + return s.substring(i, j) + } + read_bool_from(text, pos) { + if text == null { return null } + local s = "" + text + local i = pos + local n = s.length() + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + if i < n { return me._to_bool10(s.substring(i,i+1)) } + return null + } + read_string_from(text, pos) { + if text == null { return null } + local s = "" + text + local i = pos + local n = s.length() + // Find opening quote + loop(i < n) { if s.substring(i,i+1) == "\"" { i = i + 1 break } if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i + loop(j < n) { if s.substring(j,j+1) == "\"" { break } j = j + 1 } + if j <= i { return null } + return s.substring(i, j) + } + read_float_from(text, pos) { + if text == null { return null } + local s = "" + text + local i = pos + local n = s.length() + loop(i < n) { if s.substring(i,i+1) != " " { break } i = i + 1 } + local j = i + if j < n && (s.substring(j,j+1) == "+" || s.substring(j,j+1) == "-") { j = j + 1 } + local had = 0 + loop(j < n) { + local ch = s.substring(j,j+1) + if (ch >= "0" && ch <= "9") || ch == "." { had = 1 j = j + 1 } else { break } + } + if had == 0 { return null } + return s.substring(i, j) + } + + // Read helpers (key-based, start at keyPos) + read_int_after(text, key_pos) { return me.read_int_from(text, key_pos) } + read_bool_after(text, key_pos) { return me.read_bool_from(text, key_pos) } + read_string_after(text, key_pos) { return me.read_string_from(text, key_pos) } + read_float_after(text, key_pos) { return me.read_float_from(text, key_pos) } // key に続く数値(最初の一致)を返す。見つからなければ null。 get_int(seg, key) { @@ -22,13 +98,61 @@ static box JsonFragBox { return null } + // Scan for closing quote (VM fallback for scan_string_end) + _scan_string_end(text, quote_pos) { + // quote_pos is the position of opening quote + // Return position of closing quote, or -1 if not found + if text == null { return -1 } + local s = "" + text + local n = s.length() + local i = quote_pos + 1 + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\"" { return i } + if ch == "\\" { + i = i + 1 // Skip escaped character + if i >= n { return -1 } + } + i = i + 1 + } + return -1 + } + + // Seek matching closing bracket (VM fallback for seek_array_end) + _seek_array_end(text, lbracket_pos) { + // lbracket_pos is the position of '[' + // Return position of matching ']', or -1 if not found + if text == null { return -1 } + local s = "" + text + local n = s.length() + local depth = 0 + local i = lbracket_pos + local in_str = 0 + loop(i < n) { + local ch = s.substring(i, i+1) + if in_str == 1 { + if ch == "\"" { in_str = 0 } + if ch == "\\" { i = i + 1 } // Skip escaped char + } else { + if ch == "\"" { in_str = 1 } + if ch == "[" { depth = depth + 1 } + if ch == "]" { + depth = depth - 1 + if depth == 0 { return i } + } + } + i = i + 1 + } + return -1 + } + // key に続く "..." の文字列(最初の一致)を返す。見つからなければ空文字。 get_str(seg, key) { local pat = "\"" + key + "\":\"" local p = me.index_of_from(seg, pat, 0) if p >= 0 { local vstart = p + pat.length() // start of value (right after opening quote) - local vend = JsonCursorBox.scan_string_end(seg, vstart - 1) + local vend = me._scan_string_end(seg, vstart - 1) if vend > vstart { return seg.substring(vstart, vend) } } return "" @@ -62,7 +186,7 @@ static box JsonFragBox { // '[' position local arr_bracket = pk + key.length() - 1 // Use escape-aware scanner to find matching ']' - local endp = JsonCursorBox.seek_array_end(mjson, arr_bracket) + local endp = me._seek_array_end(mjson, arr_bracket) if endp < 0 { return "" } return mjson.substring(arr_bracket + 1, endp) } diff --git a/lang/src/shared/mir/loop_form_box.hako b/lang/src/shared/mir/loop_form_box.hako index a2d5a371..225fd811 100644 --- a/lang/src/shared/mir/loop_form_box.hako +++ b/lang/src/shared/mir/loop_form_box.hako @@ -101,4 +101,126 @@ static box LoopFormBox { return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) } + + // loop_count — Minimal counting loop that returns the final counter value i + // Shape: + // preheader: r1=0 (i0), r2=limit, r3=1 (step), jump header + // header: r10 = PHI(i), r11 = (i < limit), branch then:body else:exit + // body: r12 = i + step, jump latch + // latch: jump header (PHI incoming from body) + // exit: ret r10 + loop_count(limit) { + // Preheader + local pre = new ArrayBox() + pre.push(MirSchemaBox.inst_const(1, 0)) + pre.push(MirSchemaBox.inst_const(2, limit)) + pre.push(MirSchemaBox.inst_const(3, 1)) + pre.push(MirSchemaBox.inst_jump(1)) + + // Header + local header = new ArrayBox() + local inc = new ArrayBox(); inc.push(MirSchemaBox.phi_incoming(0, 1)); inc.push(MirSchemaBox.phi_incoming(3, 12)) + header.push(MirSchemaBox.inst_phi(10, inc)) + header.push(MirSchemaBox.inst_compare("Lt", 10, 2, 11)) + header.push(MirSchemaBox.inst_branch(11, 2, 4)) + + // Body + local body = new ArrayBox() + body.push(MirSchemaBox.inst_binop("Add", 10, 3, 12)) + body.push(MirSchemaBox.inst_jump(3)) + + // Latch + local latch = new ArrayBox() + latch.push(MirSchemaBox.inst_jump(1)) + + // Exit + local exit = new ArrayBox() + exit.push(MirSchemaBox.inst_ret(10)) + + local blocks = new ArrayBox() + blocks.push(MirSchemaBox.block(0, pre)) + blocks.push(MirSchemaBox.block(1, header)) + blocks.push(MirSchemaBox.block(2, body)) + blocks.push(MirSchemaBox.block(3, latch)) + blocks.push(MirSchemaBox.block(4, exit)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } + + // loop_count_param — counting loop with init/step parameters + // Returns final i value, starting from init, incremented by step while i < limit + loop_count_param(init, limit, step) { + // Preheader + local pre = new ArrayBox() + pre.push(MirSchemaBox.inst_const(1, init)) + pre.push(MirSchemaBox.inst_const(2, limit)) + pre.push(MirSchemaBox.inst_const(3, step)) + pre.push(MirSchemaBox.inst_jump(1)) + + // Header + local header = new ArrayBox() + local inc = new ArrayBox(); inc.push(MirSchemaBox.phi_incoming(0, 1)); inc.push(MirSchemaBox.phi_incoming(3, 12)) + header.push(MirSchemaBox.inst_phi(10, inc)) + header.push(MirSchemaBox.inst_compare("Lt", 10, 2, 11)) + header.push(MirSchemaBox.inst_branch(11, 2, 4)) + + // Body + local body = new ArrayBox() + body.push(MirSchemaBox.inst_binop("Add", 10, 3, 12)) + body.push(MirSchemaBox.inst_jump(3)) + + // Latch + local latch = new ArrayBox(); latch.push(MirSchemaBox.inst_jump(1)) + + // Exit + local exit = new ArrayBox(); exit.push(MirSchemaBox.inst_ret(10)) + + local blocks = new ArrayBox() + blocks.push(MirSchemaBox.block(0, pre)) + blocks.push(MirSchemaBox.block(1, header)) + blocks.push(MirSchemaBox.block(2, body)) + blocks.push(MirSchemaBox.block(3, latch)) + blocks.push(MirSchemaBox.block(4, exit)) + return MirSchemaBox.module(MirSchemaBox.fn_main(blocks)) + } + + // Unified entry — build(mode, limit, skip_value, break_value) + // mode: + // - "count" : counting loop that returns final i (uses loop_count) + // - "sum_bc" : sum with break/continue sentinels (uses loop_counter) + build(mode, limit, skip_value, break_value) { + local m = "" + mode + if m == "count" { + return me.loop_count(limit) + } + if m == "count_param" { + // Here, skip_value carries init and break_value carries step (temporary param slots) + local init = skip_value + local step = break_value + if init == null { init = 0 } + if step == null { step = 1 } + return me.loop_count_param(init, limit, step) + } + if m == "sum_bc" { + if skip_value == null { skip_value = 2 } + if break_value == null { break_value = limit } + return me.loop_counter(limit, skip_value, break_value) + } + print("[loopform/unsupported-mode] " + m) + return null + } + + // Map-based builder: build2({ mode, init, limit, step, skip, break }) + build2(opts) { + if opts == null { return null } + local mode = "" + opts.get("mode") + local init = opts.get("init") + local limit = opts.get("limit") + local step = opts.get("step") + local skip_v = opts.get("skip") + local break_v = opts.get("break") + if mode == "count" { if init == null { init = 0 } if step == null { step = 1 } return me.loop_count_param(init, limit, step) } + if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) } + print("[loopform/unsupported-mode] " + mode) + return null + } } diff --git a/lang/src/shared/mir/mir_schema_box.hako b/lang/src/shared/mir/mir_schema_box.hako index 48223914..fd0478ac 100644 --- a/lang/src/shared/mir/mir_schema_box.hako +++ b/lang/src/shared/mir/mir_schema_box.hako @@ -84,6 +84,22 @@ static box MirSchemaBox { m.set("value", this.i(val)) return m } + inst_const_f64(dst, fval) { + local m = new MapBox() + m.set("op", "const") + m.set("dst", this.i(dst)) + local v = new MapBox(); v.set("type", "f64"); v.set("value", fval) + m.set("value", v) + return m + } + inst_const_str(dst, sval) { + local m = new MapBox() + m.set("op", "const") + m.set("dst", this.i(dst)) + local v = new MapBox(); v.set("type", "string"); v.set("value", sval) + m.set("value", v) + return m + } inst_ret(val) { local m = new MapBox() m.set("op", "ret") diff --git a/lang/src/vm/README.md b/lang/src/vm/README.md index d82c6bc3..53d78da5 100644 --- a/lang/src/vm/README.md +++ b/lang/src/vm/README.md @@ -5,6 +5,30 @@ Current - `lang/src/vm/boxes/` — Shared helpers (op_handlers, scanners, compare, etc.) - Mini‑VM minimal executor lives as boxes (e.g., `boxes/mir_vm_min.hako`) +Mini VM vs Hakorune VM (Roles) +- Mini VM (Hako): reference semantic executor for MIR(JSON v0). Scope is the + minimal instruction set (const/compare/branch/jump/ret/phi). Its job in the + system is verification: given MIR(JSON v0), compute the return value and + map it to an exit code (Int → value, Bool → 1/0). It must not depend on + env/get or include; inputs are passed as inline JSON strings. +- Hakorune VM (Rust): production runtime with Boxes/Plugins/Externs and wider + semantics. Used for day‑to‑day execution and integration. Mini VM validates + meanings; Hakorune VM executes applications. + +Verify Pipeline (hakovm primary) +1) Emit MIR(JSON v0) as a single JSON string (noise trimmed in runner). +2) Runner embeds JSON into a tiny Hako driver: + `using selfhost.vm.entry as MiniVmEntryBox; return MiniVmEntryBox.run_min(j)` +3) The driver prints the numeric return; the runner converts it into a process + exit code for canaries. No env.get or file I/O is required. + +Resolver Policy (Modules) +- Prefer `using alias.name` with workspaces declared in `hako_module.toml` and + aliases in `nyash.toml`. +- Implement transitive resolution (bounded depth, cycle detection, caching). +- Dev profile may allow quoted file paths ("lang/…") for bring‑up only; prod + profile uses aliases exclusively. + Target (post‑20.12b, gradual) - `engines/hakorune/` — mainline nyvm engine - `engines/mini/` — Mini‑VM engine (educational/minimal) diff --git a/lang/src/vm/boxes/arithmetic.hako b/lang/src/vm/boxes/arithmetic.hako index 6ecea174..9c557328 100644 --- a/lang/src/vm/boxes/arithmetic.hako +++ b/lang/src/vm/boxes/arithmetic.hako @@ -2,7 +2,7 @@ // Responsibility: safe decimal Add/Sub/Mul helpers and simple i64 adapters. // Non-responsibility: VM execution, JSON parsing, compare semantics. -using "lang/src/shared/common/string_helpers.hako" as StringHelpers +using selfhost.shared.common.string_helpers as StringHelpers static box ArithmeticBox { // Internal helpers operate on decimal strings to avoid overflow. diff --git a/lang/src/vm/boxes/cfg_navigator.hako b/lang/src/vm/boxes/cfg_navigator.hako index 38d327b0..12eaae23 100644 --- a/lang/src/vm/boxes/cfg_navigator.hako +++ b/lang/src/vm/boxes/cfg_navigator.hako @@ -1,7 +1,7 @@ // cfg_navigator.hako — CfgNavigatorBox(ブロックの先頭/末尾シーク) using selfhost.shared.common.string_ops as StringOps -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox static box CfgNavigatorBox { // Provide index_of_from to avoid tail-based fallback/ambiguous resolution diff --git a/lang/src/vm/boxes/compare_scan_box.hako b/lang/src/vm/boxes/compare_scan_box.hako index 48863dff..d9d7e004 100644 --- a/lang/src/vm/boxes/compare_scan_box.hako +++ b/lang/src/vm/boxes/compare_scan_box.hako @@ -4,8 +4,8 @@ // - v1: operation:"==" maps to Eq (CompareOpsBox) // Returns: map({ dst:int|null, lhs:int|null, rhs:int|null, kind:String }) or null -using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox -using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.vm.helpers.compare_ops as CompareOpsBox static box CompareScanBox { parse(seg) { diff --git a/lang/src/vm/boxes/instruction_scanner.hako b/lang/src/vm/boxes/instruction_scanner.hako index fca47912..80de2ad0 100644 --- a/lang/src/vm/boxes/instruction_scanner.hako +++ b/lang/src/vm/boxes/instruction_scanner.hako @@ -1,8 +1,8 @@ // instruction_scanner.hako — InstructionScannerBox // Minimal JSON v0 instruction object scanner with tolerant parsing. -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox -using "lang/src/vm/boxes/cfg_navigator.hako" as CfgNavigatorBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.vm.helpers.cfg_navigator as CfgNavigatorBox static box InstructionScannerBox { _tprint(msg) { if msg.indexOf("[ERROR]") >= 0 { print(msg) } } diff --git a/lang/src/vm/boxes/mir_vm_min.hako b/lang/src/vm/boxes/mir_vm_min.hako index 1deb531c..b8772709 100644 --- a/lang/src/vm/boxes/mir_vm_min.hako +++ b/lang/src/vm/boxes/mir_vm_min.hako @@ -1,19 +1,19 @@ // mir_vm_min.hako — Ny製の最小MIR(JSON v0)実行器(const/compare/copy/branch/jump/ret の最小) -using "lang/src/vm/gc/gc_hooks.hako" as GcHooks -using "lang/src/vm/boxes/op_handlers.hako" as OpHandlersBox -using "lang/src/shared/common/string_helpers.hako" as StringHelpers +using selfhost.vm.helpers.gc_hooks as GcHooks +using selfhost.vm.helpers.op_handlers as OpHandlersBox +using selfhost.shared.common.string_helpers as StringHelpers using selfhost.shared.common.string_ops as StringOps -using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox -using "lang/src/vm/boxes/operator_box.hako" as OperatorBox -using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox -using "lang/src/vm/boxes/compare_scan_box.hako" as CompareScanBox -using "lang/src/vm/boxes/phi_apply_box.hako" as PhiApplyBox -using "lang/src/vm/boxes/guard_box.hako" as GuardBox -using "lang/src/vm/boxes/result_box.hako" as Result -using "lang/src/vm/boxes/phi_decode_box.hako" as PhiDecodeBox -using "lang/src/vm/boxes/ret_resolve_simple.hako" as RetResolveSimpleBox -using "lang/src/vm/boxes/instruction_scanner.hako" as InstructionScannerBox +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.vm.helpers.operator as OperatorBox +using selfhost.vm.helpers.compare_ops as CompareOpsBox +using selfhost.vm.helpers.compare_scan as CompareScanBox +using selfhost.vm.helpers.phi_apply as PhiApplyBox +using selfhost.vm.helpers.guard as GuardBox +using selfhost.vm.helpers.result as Result +using selfhost.vm.helpers.phi_decode as PhiDecodeBox +using selfhost.vm.helpers.ret_resolve as RetResolveSimpleBox +using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox // Minimal map for Mini‑VM registers (avoid dependency on provider MapBox) box MiniMap { @@ -70,18 +70,29 @@ static box MirVmMin { _d(msg, trace) { if trace == 1 { print(msg) } } _parse_callee_name(seg) { // naive scan: '"callee":{"name":""' - local key = '"callee":{"name":"' + local key = "\"callee\":{\"name\":\"" local p = seg.indexOf(key) if p < 0 { return "" } p = p + key.length() local rest = seg.substring(p, seg.length()) - local q = rest.indexOf('"') + local q = rest.indexOf("\"") + if q < 0 { return "" } + return rest.substring(0, q) + } + _parse_method_name(seg) { + // naive scan: '"callee":{"type":"Method","method":""' + local key = "\"callee\":{\"type\":\"Method\",\"method\":\"" + local p = seg.indexOf(key) + if p < 0 { return "" } + p = p + key.length() + local rest = seg.substring(p, seg.length()) + local q = rest.indexOf("\"") if q < 0 { return "" } return rest.substring(0, q) } _parse_first_arg(seg) { // naive scan: '"args":[ ' - local key = '"args":' + local key = "\"args\":" local p = seg.indexOf(key) if p < 0 { return null } p = p + key.length() @@ -108,6 +119,39 @@ static box MirVmMin { local name = me._parse_callee_name(seg) local arg0id = me._parse_first_arg(seg) if name == "" { + // Try Method callee + local mname = me._parse_method_name(seg) + if mname != "" { + // Optional: minimal stateful bridge for size/len/length/push + // Enabled by HAKO_VM_MIRCALL_SIZESTATE=1 (default OFF). When OFF, emit stub tag and dst=0. + local size_state = env.get("HAKO_VM_MIRCALL_SIZESTATE") + if size_state == null { size_state = "0" } + if ("" + size_state) != "1" { + local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" } + if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") } + local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.set("" + dst0, "0") } + return + } + // Stateful branch + // Keep a simple per-run length counter in regs["__vm_len"]. Default 0. + local cur_len_raw = regs.getField("__vm_len"); if cur_len_raw == null { cur_len_raw = "0" } + local cur_len = JsonFragBox._str_to_int(cur_len_raw) + if mname == "push" { + cur_len = cur_len + 1 + regs.set("__vm_len", "" + cur_len) + // push returns void/0 in this minimal path + local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.set("" + d1, "0") } + return + } + if mname == "len" || mname == "length" || mname == "size" { + local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.set("" + d2, "" + cur_len) } + return + } + // Others: no-op but keep stub tag for observability + print("[vm/method/stub:" + mname + "]") + local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.set("" + d3, "0") } + return + } me._tprint("[ERROR] mir_call: missing callee") return } @@ -398,7 +442,7 @@ else if op == "ret" { return rv } } - # (typed const fallback moved above) + // (typed const fallback moved above) if false { // Grab first two "value":{"type":"i64","value":X} local first = "" @@ -447,13 +491,11 @@ else if op == "ret" { if rid4 != null { return cv } } } - { local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) if r != null { if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } return r } - } // Final fallback: return first const (dst=1) if present local first = me._load_reg(regs, 1) return first diff --git a/lang/src/vm/boxes/op_handlers.hako b/lang/src/vm/boxes/op_handlers.hako index b40e836b..1d59d737 100644 --- a/lang/src/vm/boxes/op_handlers.hako +++ b/lang/src/vm/boxes/op_handlers.hako @@ -1,10 +1,10 @@ // op_handlers.hako — OpHandlersBox // Minimal handlers for const/compare/ret etc. with loose JSON key parsing. -using "lang/src/vm/boxes/arithmetic.hako" as ArithmeticBox -using "lang/src/shared/common/string_helpers.hako" as StringHelpers -using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox -using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox +using selfhost.vm.helpers.arithmetic as ArithmeticBox +using selfhost.shared.common.string_helpers as StringHelpers +using selfhost.vm.helpers.compare_ops as CompareOpsBox +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox // Prefer logical module names to avoid file-path using in strict profiles static box OpHandlersBox { _tprint(msg) { diff --git a/lang/src/vm/boxes/phi_decode_box.hako b/lang/src/vm/boxes/phi_decode_box.hako index 9f8ac205..c0c0f843 100644 --- a/lang/src/vm/boxes/phi_decode_box.hako +++ b/lang/src/vm/boxes/phi_decode_box.hako @@ -4,9 +4,9 @@ // Output: [dst:int, vin:int] when resolvable; otherwise [null,null] // Non-goals: MIR execution, register I/O (caller applies values), full JSON parser. -using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox -using "lang/src/vm/boxes/result_box.hako" as Result +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.vm.helpers.result as Result static box PhiDecodeBox { // tiny helpers to avoid raw quote/backslash sequences in source (prelude‑safe) diff --git a/lang/src/vm/boxes/result_box.hako b/lang/src/vm/boxes/result_box.hako index 2689295a..afa873cd 100644 --- a/lang/src/vm/boxes/result_box.hako +++ b/lang/src/vm/boxes/result_box.hako @@ -2,7 +2,37 @@ // 責務: 処理結果の統一表現(成功値 or エラーメッセージ) // 使い方: Result.Ok(val) / Result.Err(msg) → ResultBox -@enum Result { - Ok(value) - Err(error) +box ResultBox { + _tag: StringBox + _value: InstanceBox + + birth(tag, val) { + me._tag = tag + me._value = val + } + + is_Ok() { + return me._tag == "Ok" + } + + is_Err() { + return me._tag == "Err" + } + + as_Ok() { + return me._value + } + + as_Err() { + return me._value + } +} + +static box Result { + Ok(value) { + return new ResultBox("Ok", value) + } + Err(error) { + return new ResultBox("Err", error) + } } diff --git a/lang/src/vm/boxes/ret_resolve_simple.hako b/lang/src/vm/boxes/ret_resolve_simple.hako index 2f1039a0..2e4ee63f 100644 --- a/lang/src/vm/boxes/ret_resolve_simple.hako +++ b/lang/src/vm/boxes/ret_resolve_simple.hako @@ -3,9 +3,9 @@ // Inputs: inst_seg (string of objects), regs (MapBox), last_cmp_dst/val (ints) // Output: i64 value or null when no ret is present -using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox -using "lang/src/shared/common/string_helpers.hako" as StringHelpers -using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.common.string_helpers as StringHelpers +using selfhost.shared.json.core.json_cursor as JsonCursorBox static box RetResolveSimpleBox { _to_i64(s) { return StringHelpers.to_i64(s) } diff --git a/lang/src/vm/hakorune-vm/boxcall_handler.hako b/lang/src/vm/hakorune-vm/boxcall_handler.hako index d1f436bd..c7e58ce3 100644 --- a/lang/src/vm/hakorune-vm/boxcall_handler.hako +++ b/lang/src/vm/hakorune-vm/boxcall_handler.hako @@ -81,12 +81,16 @@ static box BoxCallHandlerBox { result_val = receiver.length() } else if method_sig == "isEmpty/0" { result_val = receiver.isEmpty() + } else if method_sig == "substring/1" { + result_val = receiver.substring(args_array.get(0)) } else if method_sig == "substring/2" { result_val = receiver.substring(args_array.get(0), args_array.get(1)) } else if method_sig == "charAt/1" { result_val = receiver.charAt(args_array.get(0)) } else if method_sig == "indexOf/1" { result_val = receiver.indexOf(args_array.get(0)) + } else if method_sig == "indexOf/2" { + result_val = receiver.indexOf(args_array.get(0), args_array.get(1)) } // ArrayBox methods diff --git a/lang/src/vm/hakorune-vm/extern_call_handler.hako b/lang/src/vm/hakorune-vm/extern_call_handler.hako index e40ef34e..bd7c86cb 100644 --- a/lang/src/vm/hakorune-vm/extern_call_handler.hako +++ b/lang/src/vm/hakorune-vm/extern_call_handler.hako @@ -34,6 +34,15 @@ static box ExternCallHandlerBox { if me._policy_allowlist_on() == 1 { if me._allow(name) == 0 { return Result.Err("extern not allowed: " + name) } } + // Fast-path: handle common small externs locally (avoid hostbridge dependency) + if name == "env.get" { + local key = null + if args_array != null && args_array.length() >= 1 { key = "" + args_array.get(0) } + local v = null + if key != null { v = env.get(key) } + if dst_reg != null { ValueManagerBox.set(regs, dst_reg, v) } + return Result.Ok(0) + } // Delegation: forward externs to Rust via hostbridge trampoline. if args_array == null { args_array = new ArrayBox() } local ret diff --git a/nyash.toml b/nyash.toml index 215c1344..2f855c96 100644 --- a/nyash.toml +++ b/nyash.toml @@ -148,6 +148,22 @@ path = "lang/src/shared/common/string_helpers.hako" "selfhost.shared.mir.builder" = "lang/src/shared/mir/block_builder_box.hako" "selfhost.shared.mir.io" = "lang/src/shared/mir/mir_io_box.hako" "selfhost.shared.mir.json_emit" = "lang/src/shared/mir/json_emit_box.hako" +"selfhost.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako" +"selfhost.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako" +"selfhost.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako" +"selfhost.vm.helpers.op_handlers" = "lang/src/vm/boxes/op_handlers.hako" +"selfhost.vm.helpers.operator" = "lang/src/vm/boxes/operator_box.hako" +"selfhost.vm.helpers.compare_ops" = "lang/src/vm/boxes/compare_ops.hako" +"selfhost.vm.helpers.compare_scan" = "lang/src/vm/boxes/compare_scan_box.hako" +"selfhost.vm.helpers.phi_apply" = "lang/src/vm/boxes/phi_apply_box.hako" +"selfhost.vm.helpers.guard" = "lang/src/vm/boxes/guard_box.hako" +"selfhost.vm.helpers.result" = "lang/src/vm/boxes/result_box.hako" +"selfhost.vm.helpers.phi_decode" = "lang/src/vm/boxes/phi_decode_box.hako" +"selfhost.vm.helpers.ret_resolve" = "lang/src/vm/boxes/ret_resolve_simple.hako" +"selfhost.vm.helpers.instruction_scanner" = "lang/src/vm/boxes/instruction_scanner.hako" +"selfhost.vm.helpers.gc_hooks" = "lang/src/vm/gc/gc_hooks.hako" +"selfhost.vm.helpers.arithmetic" = "lang/src/vm/boxes/arithmetic.hako" +"selfhost.vm.helpers.cfg_navigator" = "lang/src/vm/boxes/cfg_navigator.hako" "hakorune.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako" "hakorune.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako" "hakorune.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako" @@ -155,6 +171,33 @@ path = "lang/src/shared/common/string_helpers.hako" # Phase 20.34 — Box‑First selfhost build line (aliases for Hako boxes) "hako.mir.builder" = "lang/src/mir/builder/MirBuilderBox.hako" "hako.llvm.emit" = "lang/src/llvm_ir/emit/LLVMEmitBox.hako" +"hako.mir.builder.internal.prog_scan" = "lang/src/mir/builder/internal/prog_scan_box.hako" +"hako.mir.builder.internal.pattern_util" = "lang/src/mir/builder/internal/pattern_util_box.hako" +"hako.mir.builder.internal.lower.logical" = "lang/src/mir/builder/internal/lower_return_logical_box.hako" + +# MirBuilder internal lowers (alias for using) +"hako.mir.builder.internal.lower_if_then_else_following_return" = "lang/src/mir/builder/internal/lower_if_then_else_following_return_box.hako" +"hako.mir.builder.internal.lower_if_nested" = "lang/src/mir/builder/internal/lower_if_nested_box.hako" +"hako.mir.builder.internal.lower_if_compare" = "lang/src/mir/builder/internal/lower_if_compare_box.hako" +"hako.mir.builder.internal.lower_if_compare_fold_binints" = "lang/src/mir/builder/internal/lower_if_compare_fold_binints_box.hako" +"hako.mir.builder.internal.lower_if_compare_fold_varint" = "lang/src/mir/builder/internal/lower_if_compare_fold_varint_box.hako" +"hako.mir.builder.internal.lower_if_compare_varint" = "lang/src/mir/builder/internal/lower_if_compare_varint_box.hako" +"hako.mir.builder.internal.lower_if_compare_varvar" = "lang/src/mir/builder/internal/lower_if_compare_varvar_box.hako" +"hako.mir.builder.internal.lower_loop_sum_bc" = "lang/src/mir/builder/internal/lower_loop_sum_bc_box.hako" +"hako.mir.builder.internal.lower_loop_count_param" = "lang/src/mir/builder/internal/lower_loop_count_param_box.hako" +"hako.mir.builder.internal.lower_loop_simple" = "lang/src/mir/builder/internal/lower_loop_simple_box.hako" +"hako.mir.builder.internal.lower_return_var_local" = "lang/src/mir/builder/internal/lower_return_var_local_box.hako" +"hako.mir.builder.internal.lower_return_string" = "lang/src/mir/builder/internal/lower_return_string_box.hako" +"hako.mir.builder.internal.lower_return_float" = "lang/src/mir/builder/internal/lower_return_float_box.hako" +"hako.mir.builder.internal.lower_return_method_array_map" = "lang/src/mir/builder/internal/lower_return_method_array_map_box.hako" +"hako.mir.builder.internal.lower_return_bool" = "lang/src/mir/builder/internal/lower_return_bool_box.hako" +"hako.mir.builder.internal.lower_return_binop_varint" = "lang/src/mir/builder/internal/lower_return_binop_varint_box.hako" +"hako.mir.builder.internal.lower_return_binop_varvar" = "lang/src/mir/builder/internal/lower_return_binop_varvar_box.hako" +"hako.mir.builder.internal.lower_return_binop" = "lang/src/mir/builder/internal/lower_return_binop_box.hako" +"hako.mir.builder.internal.lower_return_int" = "lang/src/mir/builder/internal/lower_return_int_box.hako" + +# Missing alias for JsonFragBox (used widely in lowers) +"selfhost.shared.json.utils.json_frag" = "lang/src/shared/json/utils/json_frag.hako" # Temporary alias keys removed (Phase‑20.33 TTL reached). Use `selfhost.shared.*` above. diff --git a/src/backend/mir_interpreter/handlers/boxes_string.rs b/src/backend/mir_interpreter/handlers/boxes_string.rs index d11b1583..d55f359c 100644 --- a/src/backend/mir_interpreter/handlers/boxes_string.rs +++ b/src/backend/mir_interpreter/handlers/boxes_string.rs @@ -61,13 +61,49 @@ pub(super) fn try_handle_string_box( return Ok(true); } "indexOf" => { - // indexOf(substr) -> first index or -1 + // 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(VMError::InvalidInstruction( + "indexOf expects 1 or 2 args (search [, fromIndex])".into(), + )); + } + }; + + // 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); + + if let Some(d) = dst { this.regs.insert(d, VMValue::Integer(idx)); } + return Ok(true); + } + "contains" => { + // contains(search) -> boolean (true if found, false otherwise) + // Implemented as indexOf(search) >= 0 if args.len() != 1 { - return Err(VMError::InvalidInstruction("indexOf expects 1 arg".into())); + return Err(VMError::InvalidInstruction("contains expects 1 arg".into())); } let needle = this.reg_load(args[0])?.to_string(); - let idx = sb_norm.value.find(&needle).map(|i| i as i64).unwrap_or(-1); - if let Some(d) = dst { this.regs.insert(d, VMValue::Integer(idx)); } + let found = sb_norm.value.contains(&needle); + if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(found)); } return Ok(true); } "lastIndexOf" => { @@ -102,13 +138,26 @@ pub(super) fn try_handle_string_box( return Ok(true); } "substring" => { - if args.len() != 2 { - return Err(VMError::InvalidInstruction( - "substring expects 2 args (start, end)".into(), - )); - } - let s_idx = this.reg_load(args[0])?.as_integer().unwrap_or(0); - let e_idx = this.reg_load(args[1])?.as_integer().unwrap_or(0); + // 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(VMError::InvalidInstruction( + "substring expects 1 or 2 args (start [, end])".into(), + )); + } + }; 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; diff --git a/src/backend/mir_interpreter/handlers/calls.rs b/src/backend/mir_interpreter/handlers/calls.rs index cd7e168d..200f94b7 100644 --- a/src/backend/mir_interpreter/handlers/calls.rs +++ b/src/backend/mir_interpreter/handlers/calls.rs @@ -133,9 +133,13 @@ impl MirInterpreter { VMValue::String(ref s) => s.clone(), other => other.to_string(), }; + if std::env::var("HAKO_DEBUG_LEGACY_CALL").ok().as_deref() == Some("1") { + eprintln!("[vm-debug] legacy-call raw='{}' argc={}", raw, args.len()); + } // Minimal builtin bridge: support print-like globals in legacy form // Accept: "print", "nyash.console.log", "env.console.log", "nyash.builtin.print" + // Also bridge hostbridge.extern_invoke to the extern handler (legacy form) match raw.as_str() { "print" | "nyash.console.log" | "env.console.log" | "nyash.builtin.print" => { if let Some(a0) = args.get(0) { @@ -146,6 +150,12 @@ impl MirInterpreter { } return Ok(VMValue::Void); } + name if name == "hostbridge.extern_invoke" || name.starts_with("hostbridge.extern_invoke/") => { + return self.execute_extern_function("hostbridge.extern_invoke", args); + } + name if name == "env.get" || name.starts_with("env.get/") || name.contains("env.get") => { + return self.execute_extern_function("env.get", args); + } _ => {} } @@ -337,6 +347,77 @@ impl MirInterpreter { args: &[ValueId], ) -> Result { match func_name { + name if name == "env.get" || name.starts_with("env.get/") => { + // Route env.get global to extern handler + return self.execute_extern_function("env.get", args); + } + name if name == "hostbridge.extern_invoke" || name.starts_with("hostbridge.extern_invoke/") => { + // Treat as extern_invoke in legacy/global-resolved form + if args.len() < 3 { + return Err(VMError::InvalidInstruction( + "hostbridge.extern_invoke expects 3 args".into(), + )); + } + let name = self.reg_load(args[0])?.to_string(); + let method = self.reg_load(args[1])?.to_string(); + let v = self.reg_load(args[2])?; + let mut first_arg_str: Option = None; + match v { + VMValue::BoxRef(b) => { + 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); + first_arg_str = Some(elem.to_string_box().value); + } else { + first_arg_str = Some(b.to_string_box().value); + } + } + _ => first_arg_str = Some(v.to_string()), + } + match (name.as_str(), method.as_str()) { + ("env.mirbuilder", "emit") => { + if let Some(s) = first_arg_str { + match crate::host_providers::mir_builder::program_json_to_mir_json(&s) { + Ok(out) => Ok(VMValue::String(out)), + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.mirbuilder.emit: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.mirbuilder.emit expects 1 arg".into(), + )) + } + } + ("env.codegen", "emit_object") => { + 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), + 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) { + Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.codegen.emit_object: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.codegen.emit_object expects 1 arg".into(), + )) + } + } + _ => Err(VMError::InvalidInstruction(format!( + "hostbridge.extern_invoke unsupported for {}.{}", + name, method + ))), + } + } "nyash.builtin.print" | "print" | "nyash.console.log" => { if let Some(arg_id) = args.get(0) { let val = self.reg_load(*arg_id)?; @@ -591,6 +672,80 @@ impl MirInterpreter { }; panic!("{}", msg); } + "hostbridge.extern_invoke" => { + // Legacy global-call form: hostbridge.extern_invoke(name, method, args?) + if args.len() < 2 { + return Err(VMError::InvalidInstruction( + "extern_invoke expects at least 2 args".into(), + )); + } + let name = self.reg_load(args[0])?.to_string(); + let method = self.reg_load(args[1])?.to_string(); + + // Extract first arg as string when a third argument exists (ArrayBox or primitive) + let mut first_arg_str: Option = None; + if let Some(a2) = args.get(2) { + let v = self.reg_load(*a2)?; + match v { + VMValue::BoxRef(b) => { + 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); + first_arg_str = Some(elem.to_string_box().value); + } else { + first_arg_str = Some(b.to_string_box().value); + } + } + _ => first_arg_str = Some(v.to_string()), + } + } + + match (name.as_str(), method.as_str()) { + ("env.mirbuilder", "emit") => { + if let Some(s) = first_arg_str { + match crate::host_providers::mir_builder::program_json_to_mir_json(&s) { + Ok(out) => Ok(VMValue::String(out)), + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.mirbuilder.emit: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.mirbuilder.emit expects 1 arg".into(), + )) + } + } + ("env.codegen", "emit_object") => { + 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), + 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) { + Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.codegen.emit_object: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.codegen.emit_object expects 1 arg".into(), + )) + } + } + _ => Err(VMError::InvalidInstruction(format!( + "hostbridge.extern_invoke unsupported for {}.{}", + name, method + ))), + } + } _ => Err(VMError::InvalidInstruction(format!( "Unknown extern function: {}", extern_name diff --git a/src/backend/mir_interpreter/handlers/externals.rs b/src/backend/mir_interpreter/handlers/externals.rs index 9c3f6d53..7460e5a1 100644 --- a/src/backend/mir_interpreter/handlers/externals.rs +++ b/src/backend/mir_interpreter/handlers/externals.rs @@ -9,6 +9,21 @@ impl MirInterpreter { args: &[ValueId], ) -> Result<(), VMError> { match (iface, method) { + ("env", "get") => { + if let Some(a0) = args.get(0) { + let key = self.reg_load(*a0)?.to_string(); + let val = std::env::var(&key).ok(); + if let Some(d) = dst { + if let Some(s) = val { + self.regs.insert(d, VMValue::String(s)); + } else { + // Represent missing env as null-equivalent (Void) + self.regs.insert(d, VMValue::Void); + } + } + } + Ok(()) + } ("env.console", "log") => { if let Some(a0) = args.get(0) { let v = self.reg_load(*a0)?; @@ -115,6 +130,144 @@ impl MirInterpreter { } Ok(()) } + ("env", "get") => { + // env.get(key) - get environment variable + if let Some(a0) = args.get(0) { + let k = self.reg_load(*a0)?.to_string(); + let val = std::env::var(&k).ok(); + if let Some(d) = dst { + if let Some(v) = val { + self.regs.insert(d, VMValue::String(v)); + } else { + self.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::VoidBox::new()))); + } + } + } + Ok(()) + } + ("env.mirbuilder", "emit") => { + // program_json -> mir_json (delegate provider) + if let Some(a0) = args.get(0) { + let program_json = self.reg_load(*a0)?.to_string(); + match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { + Ok(s) => { + if let Some(d) = dst { self.regs.insert(d, VMValue::String(s)); } + Ok(()) + } + Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))), + } + } else { + Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into())) + } + } + ("env.codegen", "emit_object") => { + // mir_json -> object path (ny-llvmc or harness) + if let Some(a0) = args.get(0) { + let mir_json = 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(), timeout_ms: None }; + match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { + Ok(p) => { + if let Some(d) = dst { self.regs.insert(d, VMValue::String(p.to_string_lossy().into_owned())); } + Ok(()) + } + Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))), + } + } else { + Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into())) + } + } + ("hostbridge", "extern_invoke") => { + // hostbridge.extern_invoke(name, method, args?) + if args.len() < 2 { + return Err(VMError::InvalidInstruction( + "extern_invoke expects at least 2 args".into(), + )); + } + let name = self.reg_load(args[0])?.to_string(); + let method = self.reg_load(args[1])?.to_string(); + + // Extract first payload argument as string if provided. + // MirBuilder uses: extern_invoke("env.mirbuilder","emit", [program_json]) + let mut first_arg_str: Option = None; + if let Some(a2) = args.get(2) { + let v = self.reg_load(*a2)?; + match v { + VMValue::BoxRef(b) => { + // If it's an ArrayBox, read element[0] + 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); + first_arg_str = Some(elem.to_string_box().value); + } else { + // Fallback: stringify the box + first_arg_str = Some(b.to_string_box().value); + } + } + // For primitive VM values, use their string form + _ => first_arg_str = Some(v.to_string()), + } + } + + // Dispatch to known providers + match (name.as_str(), method.as_str()) { + ("env.mirbuilder", "emit") => { + if let Some(s) = first_arg_str { + match crate::host_providers::mir_builder::program_json_to_mir_json(&s) { + Ok(out) => { + if let Some(d) = dst { + self.regs.insert(d, VMValue::String(out)); + } + Ok(()) + } + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.mirbuilder.emit: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.mirbuilder.emit expects 1 arg".into(), + )) + } + } + ("env.codegen", "emit_object") => { + 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), + 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) { + Ok(p) => { + if let Some(d) = dst { + self.regs.insert( + d, + VMValue::String(p.to_string_lossy().into_owned()), + ); + } + Ok(()) + } + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.codegen.emit_object: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.codegen.emit_object expects 1 arg".into(), + )) + } + } + _ => Err(VMError::InvalidInstruction(format!( + "hostbridge.extern_invoke unsupported for {}.{}", + name, method + ))), + } + } _ => Err(VMError::InvalidInstruction(format!( "ExternCall {}.{} not supported", iface, method diff --git a/src/host_providers/llvm_codegen.rs b/src/host_providers/llvm_codegen.rs index aadad1e2..8114e7c6 100644 --- a/src/host_providers/llvm_codegen.rs +++ b/src/host_providers/llvm_codegen.rs @@ -21,6 +21,11 @@ fn resolve_ny_llvmc() -> PathBuf { /// Compile MIR(JSON v0) to an object file (.o) using ny-llvmc. Returns the output path. /// Fail‑Fast: prints stable tags and returns Err with the same message. pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result { + // Optional provider selection (default: ny-llvmc) + match std::env::var("HAKO_LLVM_EMIT_PROVIDER").ok().as_deref() { + Some("llvmlite") => return mir_json_to_object_llvmlite(mir_json, &opts), + _ => {} + } // Basic shape check for MIR(JSON v0) if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") { let tag = "[llvmemit/input/invalid] missing functions/blocks keys"; @@ -73,3 +78,70 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result Ok(out_path) } +fn resolve_python3() -> Option { + 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); } + } + let p = PathBuf::from("tools/llvmlite_harness.py"); + 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); } + None +} + +/// Compile via llvmlite harness (opt-in provider). Returns output path or tagged error. +fn mir_json_to_object_llvmlite(mir_json: &str, opts: &Opts) -> Result { + if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") { + let tag = "[llvmemit/input/invalid] missing functions/blocks keys"; + eprintln!("{}", tag); + return Err(tag.into()); + } + let py = resolve_python3().ok_or_else(|| { + let tag = String::from("[llvmemit/llvmlite/python-not-found]"); + eprintln!("{}", tag); + tag + })?; + let harness = resolve_llvmlite_harness().ok_or_else(|| { + let tag = String::from("[llvmemit/llvmlite/harness-not-found] tools/llvmlite_harness.py"); + eprintln!("{}", tag); + tag + })?; + + // Write MIR JSON to temp + 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 out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") }; + if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); } + + // Run: python3 tools/llvmlite_harness.py --in --out + let status = Command::new(&py) + .arg(&harness) + .arg("--in").arg(&in_path) + .arg("--out").arg(&out_path) + .status() + .map_err(|e| format!("[llvmemit/llvmlite/spawn/error] {}", e))?; + if !status.success() { + let code = status.code().unwrap_or(1); + let tag = format!("[llvmemit/llvmlite/failed status={}]", code); + eprintln!("{}", tag); + return Err(tag); + } + if !out_path.exists() { + let tag = format!("[llvmemit/output/missing] {}", out_path.display()); + eprintln!("{}", tag); + return Err(tag); + } + Ok(out_path) +} diff --git a/src/lib.rs b/src/lib.rs index 115ad53c..762e6a58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,9 @@ pub mod syntax; // syntax sugar config and helpers pub mod runner; pub mod using; // using resolver scaffolding (Phase 15) +// Host providers (extern bridge for Hako boxes) +pub mod host_providers; + // Expose the macro engine module under a raw identifier; the source lives under `src/macro/`. #[path = "macro/mod.rs"] pub mod r#macro; diff --git a/src/mir/builder/calls/extern_calls.rs b/src/mir/builder/calls/extern_calls.rs index e11d58f1..9ba52b1f 100644 --- a/src/mir/builder/calls/extern_calls.rs +++ b/src/mir/builder/calls/extern_calls.rs @@ -124,6 +124,14 @@ pub fn get_env_method_spec( true, )), + // Direct env access + ("env", "get") => Some(( + "env".to_string(), + "get".to_string(), + EffectMask::READ, + true, + )), + // Unknown _ => None, } diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 34e434a0..211c3244 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -21,9 +21,9 @@ pub(crate) mod child_env; mod cli_directives; mod demos; mod dispatch; -mod json_v0_bridge; +pub mod json_v0_bridge; mod json_v1_bridge; -mod mir_json_emit; +pub mod mir_json_emit; pub mod modes; mod pipe_io; mod core_executor; diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index a9d9b525..6ae7ac1b 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -61,18 +61,31 @@ pub fn collect_using_and_strip( } else { (rest0.to_string(), None) }; - let is_path = target.starts_with('"') - || target.starts_with("./") - || target.starts_with('/') - || target.ends_with(".nyash") - || target.ends_with(".hako"); + // Strip quotes from target for alias/module lookup + let target_unquoted = target.trim_matches('"').to_string(); + + // 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.packages.contains_key(&target_unquoted); + + let is_path = if is_known_alias_or_module { + // Known alias/module - don't treat as file path even if quoted + false + } else { + // Only treat as file path if not a known alias/module + target.starts_with("./") + || target.starts_with('/') + || target.ends_with(".nyash") + || target.ends_with(".hako") + }; if is_path { // SSOT: Disallow file-using at top-level; allow only for sources located // under a declared package root (internal package wiring), so that packages // can organize their modules via file paths. if (prod || !crate::config::env::allow_using_file()) && !inside_pkg { return Err(format!( - "{}:{}: using: file paths are disallowed in this profile. Add it to nyash.toml [using] (packages/aliases) and reference by name: {}", + "{}:{}: using: file paths are disallowed in this profile. Add it to nyash.toml [using]/[modules] and reference by name: {}\n suggestions: using \"alias.name\" as Name | dev/test: set NYASH_PREINCLUDE=1 to expand includes ahead of VM\n docs: see docs/reference/using.md", filename, line_no, target @@ -159,8 +172,8 @@ pub fn collect_using_and_strip( // Resolve namespaces/packages if prod { // prod: only allow names present in aliases/packages (toml) - let mut pkg_name: String = target.clone(); - if let Some(v) = using_ctx.aliases.get(&target) { + let mut pkg_name: String = target_unquoted.clone(); + if let Some(v) = using_ctx.aliases.get(&target_unquoted) { pkg_name = v.clone(); } if let Some(pkg) = using_ctx.packages.get(&pkg_name) { @@ -222,16 +235,16 @@ pub fn collect_using_and_strip( } } else { return Err(format!( - "{}:{}: using: '{}' not found in nyash.toml [using]. Define a package or alias and use its name (prod profile)", + "{}:{}: using: '{}' not found in nyash.toml [using]/[modules]. Define a package or alias and use its name (prod profile)\n suggestions: add an alias in nyash.toml and use 'using \"alias.name\" as Name' | dev/test: NYASH_PREINCLUDE=1", filename, line_no, - target + target_unquoted )); } } else { // dev/ci: allow broader resolution via resolver match crate::runner::pipeline::resolve_using_target( - &target, + &target_unquoted, false, &using_ctx.pending_modules, &using_ctx.using_paths, diff --git a/src/runner/modes/mir_interpreter.rs b/src/runner/modes/mir_interpreter.rs index cc774637..9be8b297 100644 --- a/src/runner/modes/mir_interpreter.rs +++ b/src/runner/modes/mir_interpreter.rs @@ -53,44 +53,60 @@ impl NyashRunner { let mut interp = MirInterpreter::new(); match interp.execute_module(&module_interp) { Ok(result) => { - println!("✅ MIR interpreter execution completed!"); - // Pretty-print using MIR return type when available - if let Some(func) = module_interp.functions.get("main") { - use nyash_rust::mir::MirType; - use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox}; - use nyash_rust::boxes::FloatBox; - let (ety, sval) = match &func.signature.return_type { - MirType::Float => { - if let Some(fb) = result.as_any().downcast_ref::() { - ("Float", format!("{}", fb.value)) - } else if let Some(ib) = result.as_any().downcast_ref::() { - ("Float", format!("{}", ib.value as f64)) - } else { ("Float", result.to_string_box().value) } - } - MirType::Integer => { - if let Some(ib) = result.as_any().downcast_ref::() { - ("Integer", ib.value.to_string()) - } else { ("Integer", result.to_string_box().value) } - } - MirType::Bool => { - if let Some(bb) = result.as_any().downcast_ref::() { - ("Bool", bb.value.to_string()) - } else if let Some(ib) = result.as_any().downcast_ref::() { - ("Bool", (ib.value != 0).to_string()) - } else { ("Bool", result.to_string_box().value) } - } - MirType::String => { - if let Some(sb) = result.as_any().downcast_ref::() { - ("String", sb.value.clone()) - } else { ("String", result.to_string_box().value) } - } - _ => { (result.type_name(), result.to_string_box().value) } - }; - println!("ResultType(MIR): {}", ety); - println!("Result: {}", sval); + use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox}; + + // Extract exit code from return value + let exit_code = 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 } } else { - println!("Result: {:?}", result); + // For non-integer/bool returns, default to 0 (success) + 0 + }; + + // Pretty-print using MIR return type when available (only in verbose mode) + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("✅ MIR interpreter execution completed!"); + if let Some(func) = module_interp.functions.get("main") { + use nyash_rust::mir::MirType; + use nyash_rust::boxes::FloatBox; + let (ety, sval) = match &func.signature.return_type { + MirType::Float => { + if let Some(fb) = result.as_any().downcast_ref::() { + ("Float", format!("{}", fb.value)) + } else if let Some(ib) = result.as_any().downcast_ref::() { + ("Float", format!("{}", ib.value as f64)) + } else { ("Float", result.to_string_box().value) } + } + MirType::Integer => { + if let Some(ib) = result.as_any().downcast_ref::() { + ("Integer", ib.value.to_string()) + } else { ("Integer", result.to_string_box().value) } + } + MirType::Bool => { + if let Some(bb) = result.as_any().downcast_ref::() { + ("Bool", bb.value.to_string()) + } else if let Some(ib) = result.as_any().downcast_ref::() { + ("Bool", (ib.value != 0).to_string()) + } else { ("Bool", result.to_string_box().value) } + } + MirType::String => { + if let Some(sb) = result.as_any().downcast_ref::() { + ("String", sb.value.clone()) + } else { ("String", result.to_string_box().value) } + } + _ => { (result.type_name(), result.to_string_box().value) } + }; + println!("ResultType(MIR): {}", ety); + println!("Result: {}", sval); + } else { + println!("Result: {:?}", result); + } } + + // Exit with the return value as exit code + process::exit(exit_code); } Err(e) => { eprintln!("❌ MIR interpreter error: {}", e); diff --git a/src/runner/modes/vm_fallback.rs b/src/runner/modes/vm_fallback.rs index 898f8af4..636d9c5c 100644 --- a/src/runner/modes/vm_fallback.rs +++ b/src/runner/modes/vm_fallback.rs @@ -258,7 +258,22 @@ impl NyashRunner { } } match vm.execute_module(&module_vm) { - Ok(_ret) => { /* interpreter already prints via println/console in program */ } + Ok(ret) => { + use crate::box_trait::{NyashBox, IntegerBox, BoolBox}; + + // 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 } + } else { + // For non-integer/bool returns, default to 0 (success) + 0 + }; + + // Exit with the return value as exit code + process::exit(exit_code); + } Err(e) => { eprintln!("❌ VM fallback error: {}", e); process::exit(1); diff --git a/src/runtime/extern_registry.rs b/src/runtime/extern_registry.rs index 8a8ffee5..edf3369d 100644 --- a/src/runtime/extern_registry.rs +++ b/src/runtime/extern_registry.rs @@ -147,6 +147,37 @@ static EXTERNS: Lazy> = Lazy::new(|| { max_arity: 255, slot: Some(50), }, + // basic env access + ExternSpec { + iface: "env", + method: "get", + min_arity: 1, + max_arity: 1, + slot: Some(60), + }, + // host providers (delegate path) + ExternSpec { + iface: "env.mirbuilder", + method: "emit", + min_arity: 1, + max_arity: 1, + slot: Some(70), + }, + ExternSpec { + iface: "env.codegen", + method: "emit_object", + min_arity: 1, + max_arity: 2, + slot: Some(71), + }, + // hostbridge trampoline (dev/testing) + ExternSpec { + iface: "hostbridge", + method: "extern_invoke", + min_arity: 2, + max_arity: 3, + slot: Some(80), + }, ] }); diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs index 6814397b..5430244b 100644 --- a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -214,7 +214,11 @@ fn handle_codegen(method_name: &str, args: &[Box]) -> BidResult Ok(Some(Box::new(StringBox::new(&p.to_string_lossy())) as Box)), + Ok(p) => { + // Convert PathBuf → String via lossy conversion (owned) + let s = p.to_string_lossy().into_owned(); + Ok(Some(Box::new(StringBox::new(s)) as Box)) + }, Err(_e) => Ok(None), } } diff --git a/tools/dev/check_lang_includes.sh b/tools/dev/check_lang_includes.sh new file mode 100644 index 00000000..8f9cca4c --- /dev/null +++ b/tools/dev/check_lang_includes.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +TARGET_DIR="$ROOT_DIR/lang/src" + +if [ ! -d "$TARGET_DIR" ]; then + echo "[check] target dir not found: $TARGET_DIR" >&2 + exit 2 +fi + +echo "[check] scanning for include statements under lang/src ..." >&2 +if rg -n '^\s*include\s+"' "$TARGET_DIR" >/tmp/hako_include_hits_$$.txt; then + echo "[FAIL] include statements found (VM backend does not support include):" >&2 + cat /tmp/hako_include_hits_$$.txt >&2 + echo "[hint] Prefer 'using "alias" as Name' with nyash.toml [modules]." >&2 + echo "[hint] For dev/tests, set NYASH_PREINCLUDE=1 to expand includes temporarily." >&2 + rm -f /tmp/hako_include_hits_$$.txt + exit 1 +else + echo "[OK] no include statements found under lang/src" >&2 +fi +rm -f /tmp/hako_include_hits_$$.txt +exit 0 + diff --git a/tools/dev/hako_debug_run.sh b/tools/dev/hako_debug_run.sh new file mode 100644 index 00000000..08603d15 --- /dev/null +++ b/tools/dev/hako_debug_run.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# hako_debug_run.sh — Stable wrapper to run .hako with Stage‑3 +# Usage: +# tools/dev/hako_debug_run.sh [--internal|--delegate] [--core] [--print-env] +# tools/dev/hako_debug_run.sh [--internal|--delegate] -c '' +# Notes: +# - Enables Stage‑3 + semicolon tolerance(smokes runner と同等) +# - 実行モード: +# --raw (既定): 直実行。inline Ny コンパイラ有効(timeoutは延長)。include が必要な時はこちら。 +# --safe : ランナー経由。inline Ny コンパイラ無効化+ノイズフィルタ。 +# --no-compiler : inline Ny コンパイラを明示的に無効化(--raw と併用可)。 +# - Uses tools/smokes/v2/lib/test_runner.sh under the hood(safe モード時) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env >/dev/null || exit 2 + +MODE_CODE=0 +CODE="" +FILE="" +USE_INTERNAL=0 +USE_DELEGATE=0 +USE_CORE=0 +PRINT_ENV=0 +USE_RAW=1 +NO_COMPILER=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -c|--code) + MODE_CODE=1 + CODE="${2:-}" + shift 2 + ;; + --internal) + USE_INTERNAL=1; shift ;; + --delegate) + USE_DELEGATE=1; shift ;; + --core) + USE_CORE=1; shift ;; + --raw) + USE_RAW=1; shift ;; + --safe) + USE_RAW=0; shift ;; + --no-compiler) + NO_COMPILER=1; shift ;; + --print-env) + PRINT_ENV=1; shift ;; + -h|--help) + echo "Usage: $0 [--internal|--delegate] [--core] [--print-env] | -c ''"; exit 0 ;; + *) + FILE="$1"; shift ;; + esac +done + +if [[ "$MODE_CODE" -eq 0 && -z "$FILE" ]]; then + echo "[ERR] No file or -c '' specified" >&2 + exit 2 +fi + +# Base env (Stage-3 + tolerance) +export NYASH_PARSER_STAGE3=1 +export HAKO_PARSER_STAGE3=1 +export NYASH_PARSER_ALLOW_SEMICOLON=1 +export NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 + +# Compiler policy (default: enabled, longer timeout). Use --no-compiler to disable. +export NYASH_NY_COMPILER_TIMEOUT_MS="${NYASH_NY_COMPILER_TIMEOUT_MS:-8000}" +if [[ "$NO_COMPILER" -eq 1 ]]; then + export NYASH_DISABLE_NY_COMPILER=1 + export HAKO_DISABLE_NY_COMPILER=1 +fi + +if [[ "$USE_INTERNAL" -eq 1 ]]; then export HAKO_MIR_BUILDER_INTERNAL=1; fi +if [[ "$USE_DELEGATE" -eq 1 ]]; then export HAKO_MIR_BUILDER_DELEGATE=1; fi +if [[ "$USE_CORE" -eq 1 ]]; then export NYASH_GATE_C_CORE=1; export HAKO_GATE_C_CORE=1; fi + +if [[ "$PRINT_ENV" -eq 1 ]]; then + echo "[ENV] NYASH_BIN=$NYASH_BIN" + env | grep -E '^(NYASH_|HAKO_)' | sort +fi + +if [[ "$USE_RAW" -eq 1 ]]; then + # Direct run (inline compiler allowed unless --no-compiler) + if [[ "$MODE_CODE" -eq 1 ]]; then + tmpf="/tmp/hako_debug_run_$$.hako"; printf "%s\n" "$CODE" > "$tmpf" + "$NYASH_BIN" --backend vm "$tmpf"; rc=$?; rm -f "$tmpf"; exit $rc + else + "$NYASH_BIN" --backend vm "$FILE" + fi +else + # Safe run via test runner (inline compiler disabled; noise filtered) + if [[ "$MODE_CODE" -eq 1 ]]; then + run_nyash_vm -c "$CODE" + else + run_nyash_vm "$FILE" + fi +fi diff --git a/tools/dev/hako_preinclude.sh b/tools/dev/hako_preinclude.sh new file mode 100644 index 00000000..0f1308f9 --- /dev/null +++ b/tools/dev/hako_preinclude.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# hako_preinclude.sh — Expand include "path" directives into inlined content. +# Usage: tools/dev/hako_preinclude.sh +# +# Env (optional): +# HAKO_PREINCLUDE_CACHE=1 # enable cache (default: 1) +# HAKO_PREINCLUDE_CACHE_DIR=/tmp/... # cache dir (default: /tmp/hako_preinclude_cache) +# HAKO_PREINCLUDE_MAX_DEPTH=10 # include nesting max (default: 12) +# HAKO_PREINCLUDE_MAX_SIZE=1048576 # expanded size bytes (default: 1 MiB) + +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +IN="$1" +OUT="$2" + +ROOT="${NYASH_ROOT:-}" +if [[ -z "$ROOT" ]]; then + if ROOT_GIT=$(git -C "$(dirname "$IN")" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" + else + ROOT="$(pwd)" + fi +fi + +declare -A SEEN +declare -A IMPORT_SEEN + +MAX_DEPTH="${HAKO_PREINCLUDE_MAX_DEPTH:-12}" +MAX_SIZE="${HAKO_PREINCLUDE_MAX_SIZE:-1048576}" +USE_CACHE="${HAKO_PREINCLUDE_CACHE:-1}" +CACHE_DIR="${HAKO_PREINCLUDE_CACHE_DIR:-/tmp/hako_preinclude_cache}" +mkdir -p "$CACHE_DIR" >/dev/null 2>&1 || true + +sum_file() { sha256sum "$1" 2>/dev/null | awk '{print $1}' || echo ""; } +cache_key() { + local key + key="$(sum_file "$1")" + echo "$key" +} + +expand_file() { + local file="$1" + local abspath="$file" + if [[ "$abspath" != /* ]]; then + abspath="$ROOT/$abspath" + fi + if [[ -n "${SEEN[$abspath]:-}" ]]; then + echo "// [preinclude] Skipping already included: $abspath"; return + fi + SEEN[$abspath]=1 + if [[ ! -f "$abspath" ]]; then + echo "// [preinclude][ERROR] Not found: $abspath" >&2 + return 1 + fi + local depth="${2:-0}" + if (( depth > MAX_DEPTH )); then + echo "// [preinclude][ERROR] max depth exceeded at: $abspath" >&2 + return 1 + fi + local current_size=0 + while IFS='' read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^[[:space:]]*include[[:space:]]+\"([^\"]+)\" ]]; then + inc="${BASH_REMATCH[1]}" + expand_file "$inc" $((depth+1)) + elif [[ "$line" =~ ^[[:space:]]*using[[:space:]].* ]]; then + key="${line//[[:space:]]/ }" # normalize spaces a bit + if [[ -n "${IMPORT_SEEN[$key]:-}" ]]; then + echo "// [preinclude] Skip duplicate using: $line" + else + IMPORT_SEEN[$key]=1 + printf '%s\n' "$line" + fi + else + printf '%s\n' "$line" + fi + # crude size guard + current_size=$((current_size + ${#line} + 1)) + if (( current_size > MAX_SIZE )); then + echo "// [preinclude][ERROR] expanded size exceeded at: $abspath" >&2 + return 1 + fi + done <"$abspath" +} +if [[ "$USE_CACHE" = "1" ]]; then + key=$(cache_key "$IN") + if [[ -n "$key" && -f "$CACHE_DIR/$key.hako" ]]; then + cp "$CACHE_DIR/$key.hako" "$OUT" + echo "[preinclude/cache-hit] $IN" >&2 + exit 0 + fi +fi + +expand_file "$IN" >"$OUT" +if [[ "$USE_CACHE" = "1" && -n "${key:-}" ]]; then + cp "$OUT" "$CACHE_DIR/$key.hako" 2>/dev/null || true +fi +echo "[preinclude] Wrote: $OUT" >&2 diff --git a/tools/smokes/v2/lib/test_runner.sh b/tools/smokes/v2/lib/test_runner.sh index 042f6e85..3dd0655c 100644 --- a/tools/smokes/v2/lib/test_runner.sh +++ b/tools/smokes/v2/lib/test_runner.sh @@ -181,13 +181,25 @@ run_nyash_vm() { sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$tmpfile" || true fi # プラグイン初期化メッセージを除外 + # Optional preinclude for include-based code + local runfile="$tmpfile" + if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then + local prefile="/tmp/nyash_pre_$$.nyash" + "$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$tmpfile" "$prefile" >/dev/null || true + runfile="$prefile" + fi + # Optional hint for include lines when preinclude is OFF + if grep -q '^include\s\"' "$tmpfile" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then + echo "[WARN] VM backend does not support include. Prefer using+alias, or set NYASH_PREINCLUDE=1 for dev." >&2 + fi NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ "${ENV_PREFIX[@]}" \ - "$NYASH_BIN" --backend vm "$tmpfile" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise + "$NYASH_BIN" --backend vm "$runfile" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise local exit_code=${PIPESTATUS[0]} - rm -f "$tmpfile" + # prefile may be unset when preinclude is OFF; use default expansion to avoid set -u errors + rm -f "$tmpfile" "${prefile:-}" 2>/dev/null || true return $exit_code else # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 @@ -195,12 +207,93 @@ run_nyash_vm() { sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true fi # プラグイン初期化メッセージを除外 + # Optional preinclude + local runfile2="$program" + if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then + local prefile2="/tmp/nyash_pre_$$.nyash" + "$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$program" "$prefile2" >/dev/null || true + runfile2="$prefile2" + fi + # Optional hint for include lines when preinclude is OFF + if [ -f "$program" ] && grep -q '^include\s\"' "$program" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then + echo "[WARN] VM backend does not support include. Prefer using+alias, or set NYASH_PREINCLUDE=1 for dev." >&2 + fi NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ "${ENV_PREFIX[@]}" \ - "$NYASH_BIN" --backend vm "$program" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise - return ${PIPESTATUS[0]} + "$NYASH_BIN" --backend vm "$runfile2" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise + local exit_code=${PIPESTATUS[0]} + # prefile2 may be unset when preinclude is OFF + rm -f "${prefile2:-}" 2>/dev/null || true + return $exit_code + fi +} + +# Verify MIR JSON rc using selected primary (Core or Hakorune VM) +verify_mir_rc() { + local json_path="$1" + local primary="${HAKO_VERIFY_PRIMARY:-core}" + if [ "$primary" = "hakovm" ]; then + # Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded + if [ ! -f "$json_path" ]; then + echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2 + return 2 + fi + # Escape JSON as a single string literal via jq -Rs (preserves newlines) + local json_literal + json_literal="$(jq -Rs . < "$json_path")" + build_and_run_driver_alias() { + local header="$1" + local code=$(cat </dev/null | tr -d '\r' | tail -n 1 + } + build_and_run_driver_include() { + local inc_path="$1" + local code=$(cat </dev/null | tr -d '\r' | tail -n 1 + } + # Try alias header first; fallback to dev-file header; final fallback: include+preinclude + local out + out="$(build_and_run_driver_alias 'using selfhost.vm.entry as MiniVmEntryBox')" + if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then + out="$(build_and_run_driver_alias 'using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox')" + fi + if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then + out="$(build_and_run_driver_include 'lang/src/vm/boxes/mini_vm_entry.hako')" + fi + if [[ "$out" =~ ^-?[0-9]+$ ]]; then + local n=$out + # normalize into [0,255] + if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi + return $n + fi + # Fallback: core primary when MiniVM resolution is unavailable + NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1 + return $? + else + NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1 + return $? fi } diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_llvmlite_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_llvmlite_canary_vm.sh new file mode 100644 index 00000000..60429001 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_llvmlite_canary_vm.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# llvmemit llvmlite canary — opt-in provider; SKIP if python/llvmlite/harness not present + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/llvmemit_llvmlite_canary_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/llvm_ir/emit/LLVMEmitBox.hako" +static box Main { method main(args) { + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"ret\",\"value\":1}] }] }},\"blocks\":1}"; + local argsA = new ArrayBox(); argsA.push(mir) + local out = hostbridge.extern_invoke("env.codegen", "emit_object", argsA) + if out == null { return 0 } + print("" + out) + return 1 +} } +HAKO + +set +e +out="$(HAKO_LLVM_EMIT_PROVIDER=llvmlite "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +path="$(echo "$out" | tail -n1 | tr -d '\r')" +if [ "$rc" -eq 1 ] && [ -n "$path" ] && [ -f "$path" ]; then + echo "[PASS] llvmemit_llvmlite_canary_vm"; rm -f "$path" || true; exit 0 +fi +if echo "$out" | grep -q "\[llvmemit/llvmlite/\(python-not-found\|harness-not-found\|failed\)"; then + echo "[SKIP] llvmemit_llvmlite (provider missing)"; exit 0 +fi +echo "[FAIL] llvmemit_llvmlite_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_binop_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_binop_canary_vm.sh new file mode 100644 index 00000000..e876ce58 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_binop_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# mirbuilder_internal_binop_canary_vm.sh — Program(JSON v0) → MIR(JSON) internal box binop canary + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_internal_binop_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Program(JSON v0) with Return(Binary(Int,Int)) — 1 + 2 + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + // Must contain two consts for 1 and 2, a binop with operation "+", and a ret of dst 3 + if s.indexOf("\"op\":\"const\"") >= 0 && s.indexOf("\"operation\":\"+\"") >= 0 && s.indexOf("\"op\":\"binop\"") >= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { + return 1 + } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ + "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" 2>/dev/null || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] mirbuilder_internal_binop_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_binop_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_canary_vm.sh new file mode 100644 index 00000000..78895bd5 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_canary_vm.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# mirbuilder_internal_canary_vm.sh — Program(JSON v0) → MIR(JSON) internal box canary + +set -euo pipefail + +# Default ON in quick: runs internal Return(Int) path with local toggle only for this test. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_internal_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Minimal Program(JSON v0) with Return(Int) + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":7}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"functions\"") >= 0 && s.indexOf("\"blocks\"") >= 0 && s.indexOf("\"value\":7") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ + "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" 2>/dev/null || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] mirbuilder_internal_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_canary_vm (rc=$rc)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh new file mode 100644 index 00000000..c46d076d --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# MirBuilder internal → Gate‑C/Core exec canary — rc verification +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_emit_$$.hako" +tmp_json="/tmp/mirbuilder_emit_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Program: if (1 < 2) return 10; else return 20; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +# 1) Emit MIR(JSON) to a temp file +set +e +out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then + echo "[FAIL] mirbuilder_internal_core_exec_canary_vm (no MIR JSON)" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +# 2) Core‑Direct exec and rc check (expect rc=10) +set +e +HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_hako" "$tmp_json" || true + +if [ "$rc" -eq 10 ]; then + echo "[PASS] mirbuilder_internal_core_exec_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_core_exec_canary_vm (rc=$rc, expect 10)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_canary_vm.sh new file mode 100644 index 00000000..b5470103 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_canary_vm.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# mirbuilder_internal_if_canary_vm.sh — Program(JSON v0) If/Compare → MIR(JSON) internal box canary + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_internal_if_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Program(JSON v0): if (1 < 2) return 10; else return 20; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + // Verify compare+branch presence and two return blocks + if s.indexOf("\"op\":\"compare\"") >= 0 && s.indexOf("\"op\":\"branch\"") >= 0 && s.indexOf("\"label\":\"bb1\"") >= 0 && s.indexOf("\"label\":\"bb2\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ + "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" 2>/dev/null || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] mirbuilder_internal_if_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_if_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_eq_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_eq_canary_vm.sh new file mode 100644 index 00000000..c78f20b5 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_eq_canary_vm.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# If(Compare ==) internal canary — structure check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp="/tmp/mirbuilder_if_eq_$$.hako" +cat > "$tmp" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":1}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"operation\":\"==\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e; HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp" >/dev/null 2>&1; rc=$?; set -e +rm -f "$tmp" || true +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_eq_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_eq_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ge_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ge_canary_vm.sh new file mode 100644 index 00000000..8acaeff1 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ge_canary_vm.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# If(Compare >=) internal canary — structure check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp="/tmp/mirbuilder_if_ge_$$.hako" +cat > "$tmp" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\">=\",\"lhs\":{\"type\":\"Int\",\"value\":2},\"rhs\":{\"type\":\"Int\",\"value\":1}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"operation\":\">=\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e; HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp" >/dev/null 2>&1; rc=$?; set -e +rm -f "$tmp" || true +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_ge_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_ge_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_le_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_le_canary_vm.sh new file mode 100644 index 00000000..d3ca2f41 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_le_canary_vm.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# If(Compare <=) internal canary — structure check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp="/tmp/mirbuilder_if_le_$$.hako" +cat > "$tmp" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<=\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"compare\"") >= 0 || s.indexOf("\"op\":\"compare\"") >= 0 { } + if s.indexOf("\"operation\":\"<=\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e; HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp" >/dev/null 2>&1; rc=$?; set -e +rm -f "$tmp" || true +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_le_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_le_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ne_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ne_canary_vm.sh new file mode 100644 index 00000000..d64667ca --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_ne_canary_vm.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# If(Compare !=) internal canary — structure check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp="/tmp/mirbuilder_if_ne_$$.hako" +cat > "$tmp" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"!=\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"operation\":\"!=\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e; HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp" >/dev/null 2>&1; rc=$?; set -e +rm -f "$tmp" || true +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_ne_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_ne_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varint_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varint_canary_vm.sh new file mode 100644 index 00000000..a9faedd2 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varint_canary_vm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# If(Compare Var/Int or Int/Var) with prior Local Int → compare+branch+ret +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_if_varint_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local a=5; if (a >= 3) return 1; else return 0; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"a\",\"expr\":{\"type\":\"Int\",\"value\":5}}," + + "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\">=\",\"lhs\":{\"type\":\"Var\",\"name\":\"a\"},\"rhs\":{\"type\":\"Int\",\"value\":3}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":1}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"compare\"") >= 0 && s.indexOf("\"op\":\"branch\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_varint_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_varint_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varvar_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varvar_canary_vm.sh new file mode 100644 index 00000000..83804fce --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_compare_varvar_canary_vm.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# If(Compare Var vs Var) with prior Local Ints → compare+branch+ret +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_if_varvar_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local a=1, b=2; if (a < b) return 7; else return 9; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"a\",\"expr\":{\"type\":\"Int\",\"value\":1}}," + + "{\"type\":\"Local\",\"name\":\"b\",\"expr\":{\"type\":\"Int\",\"value\":2}}," + + "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"a\"},\"rhs\":{\"type\":\"Var\",\"name\":\"b\"}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":7}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":9}}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"compare\"") >= 0 && s.indexOf("\"op\":\"branch\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_compare_varvar_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_compare_varvar_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_nested_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_nested_canary_vm.sh new file mode 100644 index 00000000..ae089476 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_nested_canary_vm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Nested If: if (1<2) return 3; else if (2<1) return 4; else return 5; +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_if_nested_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":3}}],\"else\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":2},\"rhs\":{\"type\":\"Int\",\"value\":1}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":4}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":5}}]}]}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"compare\"") >= 0 && s.indexOf("\"label\":\"bb2\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_nested_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_nested_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_then_follow_return_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_then_follow_return_canary_vm.sh new file mode 100644 index 00000000..713f7225 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_if_then_follow_return_canary_vm.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# If(then Return Int) and following Return(Int) — else-omitted lowering canary +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_if_then_follow_ret_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Program: if (1 < 2) return 7; return 9; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":7}}]},{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":9}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"compare\"") >= 0 && s.indexOf("\"op\":\"branch\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_if_then_follow_return_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_if_then_follow_return_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh new file mode 100644 index 00000000..d0edfe6b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Loop internal → Core exec canary — rc should equal limit (3) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_emit_loop_$$.hako" +tmp_json="/tmp/mirbuilder_emit_loop_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Canonical loop Program(JSON): i=0; s=0; loop (i<3) { s=s+1; i=i+1 }; return s; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":0}},{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Int\",\"value\":0}},{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":3}},\"body\":[{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"s\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}},{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}}]},{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"s\"}}]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +set +e +out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then + echo "[FAIL] mirbuilder_internal_loop_core_exec_canary_vm (no MIR JSON)" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +set +e +HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_hako" "$tmp_json" || true + +if [ "$rc" -eq 3 ]; then + echo "[PASS] mirbuilder_internal_loop_core_exec_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_loop_core_exec_canary_vm (rc=$rc, expect 3)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh new file mode 100644 index 00000000..57bb747c --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Loop count param: i=2; loop(i<7){ i=i+2 }; return i; → rc=6 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_emit_loop_count_param_$$.hako" +tmp_json="/tmp/mirbuilder_emit_loop_count_param_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":2}}," + + "{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":7}},\"body\":[" + + "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}}}" + + "]}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"i\"}}" + + "]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +set +e +out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then + echo "[FAIL] mirbuilder_internal_loop_count_param_core_exec_canary_vm (no MIR JSON)" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +set +e +HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_hako" "$tmp_json" || true + +if [ "$rc" -eq 6 ]; then + echo "[PASS] mirbuilder_internal_loop_count_param_core_exec_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_loop_count_param_core_exec_canary_vm (rc=$rc, expect 6)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh new file mode 100644 index 00000000..7b087870 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Loop(sum with break/continue) internal → Core exec canary — expect 0+1+3+4 = 8 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_emit_loop_bc_$$.hako" +tmp_json="/tmp/mirbuilder_emit_loop_bc_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Program: i=0; s=0; loop(true){ s=s+1; if(i==4) break; if(i==2) continue; i=i+1 } return s + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":0}}," + + "{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Int\",\"value\":0}}," + + "{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":5}},\"body\":[" + + "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":4}},\"then\":[{\"type\":\"Break\"}]}," + + "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Continue\"}]}," + + "{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"s\"},\"rhs\":{\"type\":\"Var\",\"name\":\"i\"}}}," + + "{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}}" + + "]}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"s\"}}" + + "]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +set +e +out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then + echo "[FAIL] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm (no MIR JSON)" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +set +e +HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_hako" "$tmp_json" || true + +if [ "$rc" -eq 8 ]; then + echo "[PASS] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm (rc=$rc, expect 8)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_method_set_string_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_method_set_string_canary_vm.sh new file mode 100644 index 00000000..c583dd89 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_method_set_string_canary_vm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Return(Method set with String value) → const string + mir_call structure +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_method_set_str_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local m = new MapBox(); return m.set(1, "x"); + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"m\",\"expr\":{\"type\":\"New\",\"class\":\"MapBox\",\"args\":[]}}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Method\",\"recv\":{\"type\":\"Var\",\"name\":\"m\"},\"method\":\"set\",\"args\":[{\"type\":\"Int\",\"value\":1},{\"type\":\"String\",\"value\":\"x\"}]}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"type\":\"string\"") >= 0 && s.indexOf("\"op\":\"mir_call\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_method_set_string_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_method_set_string_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_binop_varint_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_binop_varint_canary_vm.sh new file mode 100644 index 00000000..e70eb91a --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_binop_varint_canary_vm.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Return(Binary Var/Int) with Local Int → const+const+binop+ret +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_binop_varint_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local x=5; return x + 3; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Local\",\"name\":\"x\",\"expr\":{\"type\":\"Int\",\"value\":5}},{\"type\":\"Return\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"x\"},\"rhs\":{\"type\":\"Int\",\"value\":3}}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"binop\"") >= 0 && s.indexOf("\"operation\":\"+\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_binop_varint_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_binop_varint_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_bool_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_bool_canary_vm.sh new file mode 100644 index 00000000..5431c87b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_bool_canary_vm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Return(Bool) → const 0/1 + ret canary +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_bool_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Bool\",\"value\":true}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"type\":\"i64\"") >= 0 && s.indexOf("\"value\":1") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_bool_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_bool_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_float_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_float_canary_vm.sh new file mode 100644 index 00000000..d712cfcd --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_float_canary_vm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Return(Float) → const f64 + ret (structure check only) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_float_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Float\",\"value\":3.14}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"type\":\"f64\"") >= 0 && s.indexOf("\"value\":3.14") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_float_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_float_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_canary_vm.sh new file mode 100644 index 00000000..453f5b3c --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_canary_vm.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Return(Logical &&/|| with Bool literal lhs/rhs) → branch+ret structure +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_logical_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Return(true && false) + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"&&\",\"lhs\":{\"type\":\"Bool\",\"value\":true},\"rhs\":{\"type\":\"Bool\",\"value\":false}}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"branch\"") >= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_logical_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_logical_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_var_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_var_canary_vm.sh new file mode 100644 index 00000000..b8978ac6 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_var_canary_vm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Return(Logical Var || Bool) with prior Local Bool → branch+ret structure +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_logical_var_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local b=true; return b || false; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"b\",\"expr\":{\"type\":\"Bool\",\"value\":true}}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"||\",\"lhs\":{\"type\":\"Var\",\"name\":\"b\"},\"rhs\":{\"type\":\"Bool\",\"value\":false}}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"branch\"") >= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_logical_var_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_logical_var_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_canary_vm.sh new file mode 100644 index 00000000..6f39e5ad --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_canary_vm.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Return(Logical Var && Var) with prior Local Bool → branch+ret +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +gen_case() { + local lhs="$1"; local rhs="$2"; local op="$3" + local tmp_hako="/tmp/mirbuilder_return_logical_varvar_${op}_$$.hako" + cat > "$tmp_hako" <= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { return 1 } + return 0 +} } +HAKO + set +e + # Preinclude to avoid VM include limitation + pre="/tmp/pre_$$.hako"; "$ROOT/tools/dev/hako_preinclude.sh" "$tmp_hako" "$pre" >/dev/null + out="$(HAKO_MIR_BUILDER_INTERNAL=1 NYASH_PREINCLUDE=0 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ + run_nyash_vm "$pre" 2>&1)"; rc=$? + set -e + rm -f "$tmp_hako" "$pre" || true + # Success if output contains branch+ret markers (compact or spaced JSON) + if echo "$out" | grep -qiE 'Invalid instruction|call unresolved|panic|Undefined variable'; then return 0; fi + local has_branch=0; local has_ret=0 + if echo "$out" | grep -q '"op":"branch"' || echo "$out" | grep -q '"op": "branch"'; then has_branch=1; fi + if echo "$out" | grep -q '"op":"ret"' || echo "$out" | grep -q '"op": "ret"'; then has_ret=1; fi + if [ $has_branch -eq 1 ] && [ $has_ret -eq 1 ]; then return 1; fi + return 0 +} + +# Test case 1: true && false → should emit branch+ret +gen_case true false "&&"; rc1=$? +if [ "$rc1" -ne 1 ]; then + echo "[FAIL] mirbuilder_internal_return_logical_varvar_canary_vm (case 1: rc=$rc1)" >&2 + exit 1 +fi + +# Test case 2: false || true → should emit branch+ret +gen_case false true "||"; rc2=$? +if [ "$rc2" -ne 1 ]; then + echo "[FAIL] mirbuilder_internal_return_logical_varvar_canary_vm (case 2: rc=$rc2)" >&2 + exit 1 +fi + +echo "[PASS] mirbuilder_internal_return_logical_varvar_canary_vm" +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_core_exec_canary_vm.sh new file mode 100644 index 00000000..3b23f75b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_core_exec_canary_vm.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Core exec: Return(b1 && b2) / Return(b1 || b2) with Var/Var → rc check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +run_case() { + local lhs_tf="$1" # true|false + local rhs_tf="$2" # true|false + local op="$3" # && or || + local expect_rc="$4" + local tmp_hako="/tmp/mirbuilder_logical_core_${op}_$$.hako" + local tmp_json="/tmp/mirbuilder_logical_core_${op}_$$.json" + cat > "$tmp_hako" <&1)"; rc=$? + set -e + if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then + echo "[FAIL] logical_varvar_core (emit)" >&2; rm -f "$tmp_hako" "$tmp_json" || true; return 1 + fi + set +e + HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 + rc2=$? + set -e + rm -f "$tmp_hako" "$tmp_json" || true + if [ "$rc2" -ne "$expect_rc" ]; then + echo "[FAIL] logical_varvar_core ${lhs_tf} ${op} ${rhs_tf}: rc=$rc2 expect=$expect_rc" >&2 + return 1 + fi + return 0 +} + +run_case true false "&&" 0 || exit 1 +run_case true false "||" 1 || exit 1 +run_case false true "&&" 0 || exit 1 +run_case false true "||" 1 || exit 1 +run_case false false "||" 0 || exit 1 +echo "[PASS] mirbuilder_internal_return_logical_varvar_core_exec_canary_vm" +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_lower_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_lower_canary_vm.sh new file mode 100644 index 00000000..b33dbafa --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_logical_varvar_lower_canary_vm.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# mirbuilder_internal_return_logical_varvar_lower_canary_vm.sh +# Purpose: Verify LowerReturnLogicalBox.try_lower directly (bypass MirBuilderBox integration) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_lower_logical_varvar_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/internal/lower_return_logical_box.hako" +static box Main { method main(args) { + // Local b1=true; Local b2=false; return b1 && b2; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"b1\",\"expr\":{\"type\":\"Bool\",\"value\":true}}," + + "{\"type\":\"Local\",\"name\":\"b2\",\"expr\":{\"type\":\"Bool\",\"value\":false}}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"&&\",\"lhs\":{\"type\":\"Var\",\"name\":\"b1\"},\"rhs\":{\"type\":\"Var\",\"name\":\"b2\"}}}]}"; + local out = LowerReturnLogicalBox.try_lower(j); + if out == null { return 0 } + // Structural check using Map API + local fns = out.get("functions"); local ver = out.get("version") + if fns == null || ver == null { return 0 } + return 1 +} } +HAKO + +set +e +pre="/tmp/pre_$$.hako"; "$ROOT/tools/dev/hako_preinclude.sh" "$tmp_hako" "$pre" >/dev/null +out="$(NYASH_PREINCLUDE=0 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_NY_COMPILER_TIMEOUT_MS=20000 \ + run_nyash_vm "$pre" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" "$pre" || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] mirbuilder_internal_return_logical_varvar_lower_canary_vm" + exit 0 +fi +echo "$out" >&2 +echo "[FAIL] mirbuilder_internal_return_logical_varvar_lower_canary_vm (rc=$rc)" >&2 +exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_method_array_map_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_method_array_map_canary_vm.sh new file mode 100644 index 00000000..529762e9 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_method_array_map_canary_vm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Return(Method recv Var, method=size/get/set/push) → mir_call structure check +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_method_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Local a = new ArrayBox(); return a.size(); (shape only, not executed here) + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"a\",\"expr\":{\"type\":\"New\",\"class\":\"ArrayBox\",\"args\":[]}}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Method\",\"recv\":{\"type\":\"Var\",\"name\":\"a\"},\"method\":\"size\",\"args\":[]}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"mir_call\"") >= 0 && s.indexOf("\"method\":\"size\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_method_array_map_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_method_array_map_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_string_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_string_canary_vm.sh new file mode 100644 index 00000000..26a4a1f7 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_string_canary_vm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Return(String) → const string + ret (structure check only) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_string_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"String\",\"value\":\"hello\"}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"type\":\"string\"") >= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_string_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_string_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_var_local_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_var_local_canary_vm.sh new file mode 100644 index 00000000..b661cd9c --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_return_var_local_canary_vm.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Return Var(Local Int) → const+ret canary +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_return_var_local_$$.hako" +cat > "$tmp_hako" <<'HAKO' +include "lang/src/mir/builder/MirBuilderBox.hako" +static box Main { method main(args) { + // Program: local x=7; return x; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Local\",\"name\":\"x\",\"expr\":{\"type\":\"Int\",\"value\":7}},{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"x\"}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"op\":\"const\"") >= 0 && s.indexOf("\"op\":\"ret\"") >= 0 { return 1 } + return 0 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_INTERNAL=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +rm -f "$tmp_hako" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] mirbuilder_internal_return_var_local_canary_vm"; exit 0; fi +echo "[FAIL] mirbuilder_internal_return_var_local_canary_vm (rc=$rc)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_varvar_delegate_core_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_varvar_delegate_core_canary_vm.sh new file mode 100644 index 00000000..7d14f288 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_varvar_delegate_core_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Verify Logical Var/Var via Delegate + Core direct (structure lock) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mirbuilder_varvar_delegate_core_$$.hako" +tmp_json="/tmp/mirbuilder_varvar_delegate_core_$$.json" +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Local b1=true; Local b2=false; return b1 && b2; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + + "{\"type\":\"Local\",\"name\":\"b1\",\"expr\":{\"type\":\"Bool\",\"value\":true}}," + + "{\"type\":\"Local\",\"name\":\"b2\",\"expr\":{\"type\":\"Bool\",\"value\":false}}," + + "{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"&&\",\"lhs\":{\"type\":\"Var\",\"name\":\"b1\"},\"rhs\":{\"type\":\"Var\",\"name\":\"b2\"}}}]}"; + // Call provider via extern directly (avoid MirBuilderBox toggles) + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 0 } + print("" + out) + return 1 +} } +HAKO + +set +e +out="$(HAKO_MIR_BUILDER_DELEGATE=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 run_nyash_vm "$tmp_hako" 2>&1)"; rc=$? +set -e +if [ "$rc" -ne 1 ]; then echo "$out" >&2; echo "[FAIL] varvar_delegate_core (emit)" >&2; rm -f "$tmp_hako" "$tmp_json"; exit 1; fi +# Be tolerant of pretty-printed JSON (multi-line). Validate and capture all. +echo "$out" | jq -e . > "$tmp_json" || { echo "$out" >&2; echo "[FAIL] varvar_delegate_core (no MIR)" >&2; rm -f "$tmp_hako" "$tmp_json"; exit 1; } + +set +e +NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$tmp_json" >/dev/null 2>&1 +rc2=$? +set -e +rm -f "$tmp_hako" "$tmp_json" || true +if [ "$rc2" -eq 0 ]; then + echo "[FAIL] varvar_delegate_core rc=0 (expected non-zero for && with true,false)" >&2 + exit 1 +fi +echo "[PASS] mirbuilder_varvar_delegate_core_canary_vm" +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/string_vm_api_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/string_vm_api_canary_vm.sh new file mode 100644 index 00000000..bacb0900 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/string_vm_api_canary_vm.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# string_vm_api_canary_vm.sh — Verify VM String API parity with Rust + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp="/tmp/string_vm_api_canary_$$.hako" +cat > "$tmp" <<'HAKO' +static box Main { method main(args) { + local s = "hello" + // substring(start,end) + local a = s.substring(1,4) // "ell" + if (""+a) != "ell" { return 0 } + + // indexOf(search) + local p1 = s.indexOf("l") + if (""+p1) != "2" { return 0 } + local p2 = s.indexOf("z") + if (""+p2) != "-1" { return 0 } + + // charAt via substring(i,i+1) + local c = s.substring(0,1) + if (""+c) != "h" { return 0 } + return 1 +} } +HAKO + +set +e +out="$(NYASH_PREINCLUDE=0 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 run_nyash_vm "$tmp" 2>&1)"; rc=$? +set -e +rm -f "$tmp" || true + +if [ "$rc" -eq 1 ]; then echo "[PASS] string_vm_api_canary_vm"; exit 0; fi +echo "$out" >&2 +echo "[FAIL] string_vm_api_canary_vm (rc=$rc)" >&2; exit 1