Phase 20.34: expand MirBuilder internal library with comprehensive lowering boxes; add pattern registry and program scanning infrastructure; implement internal lowerers for if/loop/return patterns; add dev tools and comprehensive canary tests; update VM boxes and host providers for internal delegation; wire phase2034 test suite with 30+ canary scripts covering internal lowering scenarios

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
nyash-codex
2025-11-03 16:09:19 +09:00
parent 8827b8d416
commit a4f30ae827
89 changed files with 4125 additions and 115 deletions

View File

@ -908,13 +908,92 @@ Toggles既定OFF
- HAKO_CORE_DIRECT=1 / HAKO_CORE_DIRECT_INPROC=1 — MIR(JSON) を Core 直行で実行
- SMOKES_ENABLE_SELFHOST=1 — EXE カナリアを有効化
Nearterm Tasks
- docs: phase20.34README/PLAN/CHECKLISTを追加(本ファイル含む)
- scaffold: MirBuilderBox.hako / LLVMEmitBox.hako の最小 I/F(未対応は FailFast
- smokes: Program→MIRreturn/binop/if、.o 生成SKIP許容、EXEoptin
- provider: plugin spec ドキュメントny-llvmc/llvmlite どちらも裏側として許容)
Nearterm Tasks(進捗)
- docs: phase20.34README/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 カナリア追加 — DONEMirBuilder=PASS、LLVMEmit=ny-llvmc不在/未解決はSKIP
- next: LLVMCodegenBoxHako ABI プラグインBox実装hako.toml登録 → extern TTL 撤退
- next: MirBuilderBox 内蔵実装const/binop/ret → compare/branch/jump/phiを段階導入委譲縮小
Acceptance
- quick: 新規カナリア PASSSKIPはポリシー通り
- integration: 既存緑維持、ノイズ増加なし
- docs: Env/TTL/FailFast を明文化
Update — 20251103 (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→Cdocs first, code later
A) LLVM provider 明確化docs運用
- 方針: LLVMEmit は providerfirst。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]` を維持。
- 受理条件: canaryreturn/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 Reference20.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 wrapup → 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=devonly
- 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 — 20251103)
- A) LLVM provider 明確化: DONEenv `HAKO_LLVM_EMIT_PROVIDER`、タグ: `[llvmemit/provider/missing]` ほか)
- B) MirBuilder 内蔵化(1st): INPROGRESSReturn/Int・Binary(Int,Int)・Var/Int・Var/Var 等の lowers は内蔵パスで起動、追加の JsonFragBox/PatternUtilBox 適用は段階導入中)
- C) Canary 運用: PASSmirbuilder_canary_vm / binop/varint、logical/mixed。var/var core 直行の emit 検証は追跡中非既定optin
Next (small, structurefirst)
- 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` 経路の fileread 受け口を追加し、Core fallback を段階縮小。
Update — 2025-11-03Phase 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 他。
- P2loop_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 primaryHAKO_VERIFY_PRIMARY=hakovmで実施。
- ランナー/テスト安定化
- test_runner 未割当変数バグ修正prefile
- emit 出力の JSON 抽出に jq を導入(先頭ノイズ除去)。
Notes
- 仕様不変・既定挙動不変トグルは既定OFF
- include は lang内の一部compiler_stageb/core_bridgeのみ残存別PRで移行

View File

@ -0,0 +1,29 @@
# Phase 20.34 — P1/P2 Sweep (JsonFrag/PatternUtil, loop_form using)
Status: Completed (2025-11-03)
Scope
- P1: Replace adhoc 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 (failfast). New toggles default OFF. Minimal, localized diffs only.
Next
- Continue registry migration for MirBuilder (toggleguarded).
- Migrate remaining noninternal `include` sites in a separate PR.

View File

@ -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 → FailFast 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" {

View File

@ -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. FailFast 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<limit) counting loopi=0..limit LoopFormBox.loop_count に委譲
- `lower_loop_sum_bc_box.hako` Loop sum + break/continue 哲学i==break break / i==skip continue)→ LoopFormBox.build("sum_bc", ...)。
Policy
- No execution or I/O; emit-only. Keep shapes minimal and obvious. For unsupported inputs, print
`[mirbuilder/internal/unsupported] ...` and return null.
LoopForm param
- LoopFormBox.build(mode, limit, skip, break)
- mode: "count"i を返す/ "sum_bc"sum の合流を Exit PHI で返す
- limit/skip/break i64 として扱うskip/break null の場合`skip=2` / `break=limit` を既定とする

View File

@ -0,0 +1,66 @@
// lower_if_compare_box.hako — If(Compare(Int,Int), then Return(Int), else Return(Int)) → compare+branch+ret×2
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box LowerIfCompareBox {
try_lower(program_json) {
local s = "" + program_json
local k_if = s.indexOf("\"type\":\"If\"")
if k_if < 0 { return null }
// cond Compare
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 = JsonFragBox.read_string_after(s, k_op + 5)
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 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
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -0,0 +1,54 @@
// lower_loop_count_param_box.hako — Loop(Compare i<limit) with Local i=init and i+=step → LoopForm.build("count", init, step)
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.mir.loopform as LoopFormBox
static box LowerLoopCountParamBox {
try_lower(program_json) {
local s = "" + program_json
// Local i = Int init
local k_local_i = s.indexOf("\"type\":\"Local\"")
if k_local_i < 0 { return null }
if s.indexOf("\"name\":\"i\"", k_local_i) < 0 { return null }
local k_init = s.indexOf("\"type\":\"Int\"", k_local_i)
if k_init < 0 { return null }
local init = Scan.read_value_int_after(s, k_init)
if init == null { return null }
// Loop Compare i < Int limit
local k_loop = s.indexOf("\"type\":\"Loop\"", k_local_i)
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
if s.indexOf("\"op\":\"<\"", k_cmp) < 0 { return null }
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_cmp) < 0 { return null }
local k_lim_t = s.indexOf("\"type\":\"Int\"", k_cmp)
if k_lim_t < 0 { return null }
local limit = Scan.read_value_int_after(s, k_lim_t)
if limit == null { return null }
// Body increment: Local i = Binary('+', Var i, Int step)
local k_body_i = s.indexOf("\"name\":\"i\"", k_loop)
if k_body_i < 0 { return null }
local k_bop = s.indexOf("\"type\":\"Binary\"", k_body_i)
if k_bop < 0 { return null }
if s.indexOf("\"op\":\"+\"", k_bop) < 0 { return null }
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_bop) < 0 { return null }
local k_step_t = s.indexOf("\"type\":\"Int\"", k_bop)
if k_step_t < 0 { return null }
local step = Scan.read_value_int_after(s, k_step_t)
if step == null { return null }
// Build via LoopFormBox (extend build to accept param init/step in future; use loop_count then adjust by init/step)
// For now, synthesize by composing loop_count(limit') with pre-increment of i, but since we return i, we can directly emit param loop
// Implement dedicated param path in LoopFormBox: loop_count(limit, init, step)
if step == 1 && init == 0 { return LoopFormBox.build("count", limit, null, null) }
// Fallback to parametric count when available
if ("" + step) != "" || ("" + init) != "" {
// Call loop_count(limit) is incorrect when init/step differ; prefer loop_count when extension exists.
// Use build("count_param", limit, init, step) when mode supported.
local out = LoopFormBox.build("count_param", limit, init, step)
if out != null { return out }
}
return null
}
}

View File

@ -0,0 +1,38 @@
// lower_loop_simple_box.hako — Loop(Compare i < N) → counting loop (i from 0 to N) returning i
// Notes: minimal scanner that extracts limit N from Program(JSON v0) Loop cond rhs Int.
using selfhost.shared.mir.loopform as LoopFormBox
static box LowerLoopSimpleBox {
try_lower(program_json) {
local s = "" + program_json
// Find Loop with cond Compare op '<' and rhs Int value
local k_loop = s.indexOf("\"type\":\"Loop\"")
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
// op must be '<'
local k_op = s.indexOf("\"op\":\"<\"", k_cmp)
if k_op < 0 { return null }
// rhs Int value
local k_rhs = s.indexOf("\"rhs\":{", k_cmp)
if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs)
if k_ti < 0 { return null }
// Scan numeric after "value":
local k_v = s.indexOf("\"value\":", k_ti)
if k_v < 0 { return null }
local i = k_v + 8
// skip spaces
loop(i < s.length()) { if s.substring(i,i+1) != " " { break } i = i + 1 }
local j = i
if j < s.length() && s.substring(j,j+1) == "-" { j = j + 1 }
local had = 0
loop(j < s.length()) { local ch = s.substring(j,j+1); if ch >= "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)
}
}

View File

@ -0,0 +1,75 @@
// lower_loop_sum_bc_box.hako — Loop with Compare(i<limit) and body Break/Continue sentinels → LoopFormBox.loop_counter
// Pattern (naive):
// Local i=0; Local s=0;
// Loop(cond=Compare(op:"<", lhs=Var("i"), rhs=Int limit), body=[
// If(cond=Compare(op:"==", lhs=Var("i"), rhs=Int break_value)) then [Break]
// If(cond=Compare(op:"==", lhs=Var("i"), rhs=Int skip_value)) then [Continue]
// ...
// ])
// Return Var("s")
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.mir.loopform as LoopFormBox
static box LowerLoopSumBcBox {
try_lower(program_json) {
local s = "" + program_json
// Loop and Compare(i < Int limit)
local k_loop = s.indexOf("\"type\":\"Loop\"")
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
// op "<"
local op = Scan.read_quoted_after_key(s, k_cmp, "op")
if op == null || op != "<" { return null }
// lhs must mention Var("i"); we check weakly by searching name:"i"
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_cmp) < 0 { return null }
// rhs Int limit
local k_rhs = s.indexOf("\"rhs\":{", k_cmp)
if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs)
if k_ti < 0 { return null }
local limit = Scan.read_value_int_after(s, k_ti)
if limit == null { return null }
// Break sentinel: If(cond Compare i==X) then Break
local break_value = null
{
local kb = s.indexOf("\"type\":\"Break\"", k_loop)
if kb >= 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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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
}
}

View File

@ -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))
}
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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<nn){ if s.substring(jj,jj+1)=="\"" {break} jj=jj+1 } if s.substring(ii,jj)==name { last=k } } pos=k+1 }
if last<0 { return null }
local ki=s.indexOf("\"type\":\"Int\"",last); if ki<0||ki>=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<nn){ if s.substring(jj,jj+1)=="\"" {break} jj=jj+1 } if s.substring(ii,jj)==name { last=k } } pos=k+1 }
if last<0 { return null }
local kb=s.indexOf("\"type\":\"Bool\"",last); if kb<0||kb>=before_pos { return null }
local kv=s.indexOf("\"value\":",kb); if kv<0 { return null }
return JsonFragBox.read_bool_after(s, kv+8)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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")

View File

@ -5,6 +5,30 @@ Current
- `lang/src/vm/boxes/` — Shared helpers (op_handlers, scanners, compare, etc.)
- MiniVM 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 daytoday 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 bringup only; prod
profile uses aliases exclusively.
Target (post20.12b, gradual)
- `engines/hakorune/` — mainline nyvm engine
- `engines/mini/` — MiniVM engine (educational/minimal)

View File

@ -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.

View File

@ -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

View File

@ -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) {

View File

@ -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) } }

View File

@ -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 MiniVM 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":"<sym>"'
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":"<sym>"'
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":[ <int>'
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

View File

@ -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) {

View File

@ -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 (preludesafe)

View File

@ -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)
}
}

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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 — BoxFirst 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 (Phase20.33 TTL reached). Use `selfhost.shared.*` above.

View File

@ -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 {
// 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 2 args (start, end)".into(),
"substring expects 1 or 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);
};
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;

View File

@ -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<VMValue, VMError> {
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<String> = None;
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx: Box<dyn crate::box_trait::NyashBox> =
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<String> = 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::<crate::boxes::array::ArrayBox>() {
let idx: Box<dyn crate::box_trait::NyashBox> =
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

View File

@ -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<String> = 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::<crate::boxes::array::ArrayBox>() {
let idx: Box<dyn crate::box_trait::NyashBox> =
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

View File

@ -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.
/// FailFast: prints stable tags and returns Err with the same message.
pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String> {
// 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<PathBuf, String>
Ok(out_path)
}
fn resolve_python3() -> Option<PathBuf> {
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<PathBuf> {
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<PathBuf, String> {
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 <json> --out <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)
}

View File

@ -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;

View File

@ -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,
}

View File

@ -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;

View File

@ -61,18 +61,31 @@ pub fn collect_using_and_strip(
} else {
(rest0.to_string(), None)
};
let is_path = target.starts_with('"')
|| target.starts_with("./")
// 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");
|| 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,

View File

@ -53,11 +53,23 @@ impl NyashRunner {
let mut interp = MirInterpreter::new();
match interp.execute_module(&module_interp) {
Ok(result) => {
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::<IntegerBox>() {
ib.value as i32
} else if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
if bb.value { 1 } else { 0 }
} else {
// 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!");
// 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 => {
@ -92,6 +104,10 @@ impl NyashRunner {
println!("Result: {:?}", result);
}
}
// Exit with the return value as exit code
process::exit(exit_code);
}
Err(e) => {
eprintln!("❌ MIR interpreter error: {}", e);
process::exit(1);

View File

@ -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::<IntegerBox>() {
ib.value as i32
} else if let Some(bb) = ret.as_any().downcast_ref::<BoolBox>() {
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);

View File

@ -147,6 +147,37 @@ static EXTERNS: Lazy<Vec<ExternSpec>> = 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),
},
]
});

View File

@ -214,7 +214,11 @@ fn handle_codegen(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Op
let nyrt = std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from);
let opts = crate::host_providers::llvm_codegen::Opts { out, nyrt, opt_level, timeout_ms: None };
match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
Ok(p) => Ok(Some(Box::new(StringBox::new(&p.to_string_lossy())) as Box<dyn NyashBox>)),
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<dyn NyashBox>))
},
Err(_e) => Ok(None),
}
}

View File

@ -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

102
tools/dev/hako_debug_run.sh Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env bash
# hako_debug_run.sh — Stable wrapper to run .hako with Stage3
# Usage:
# tools/dev/hako_debug_run.sh [--internal|--delegate] [--core] [--print-env] <file.hako>
# tools/dev/hako_debug_run.sh [--internal|--delegate] -c '<code>'
# Notes:
# - Enables Stage3 + semicolon tolerancesmokes runner と同等)
# - 実行モード:
# --raw (既定): 直実行。inline Ny コンパイラ有効timeoutは延長。include が必要な時はこちら。
# --safe : ランナー経由。inline Ny コンパイラ無効化+ノイズフィルタ。
# --no-compiler : inline Ny コンパイラを明示的に無効化(--raw と併用可)。
# - Uses tools/smokes/v2/lib/test_runner.sh under the hoodsafe モード時)
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] <file.hako> | -c '<code>'"; exit 0 ;;
*)
FILE="$1"; shift ;;
esac
done
if [[ "$MODE_CODE" -eq 0 && -z "$FILE" ]]; then
echo "[ERR] No file or -c '<code>' 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

View File

@ -0,0 +1,102 @@
#!/usr/bin/env bash
# hako_preinclude.sh — Expand include "path" directives into inlined content.
# Usage: tools/dev/hako_preinclude.sh <in.hako> <out.hako>
#
# 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 <in.hako> <out.hako>" >&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

View File

@ -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 <<HCODE
$header
static box Main { method main(args) {
local j = __MIR_JSON__;
local rc = MiniVmEntryBox.run_min(j)
print(MiniVmEntryBox.int_to_str(rc))
return rc
} }
HCODE
)
code="${code/__MIR_JSON__/$json_literal}"
NYASH_USING_AST=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | tail -n 1
}
build_and_run_driver_include() {
local inc_path="$1"
local code=$(cat <<HCODE
include "$inc_path"
static box Main { method main(args) {
local j = __MIR_JSON__;
local rc = MiniVmEntryBox.run_min(j)
print(MiniVmEntryBox.int_to_str(rc))
return rc
} }
HCODE
)
code="${code/__MIR_JSON__/$json_literal}"
NYASH_PREINCLUDE=1 run_nyash_vm -c "$code" 2>/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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,44 @@
#!/bin/bash
# MirBuilder internal → GateC/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) CoreDirect 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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" <<HAKO
static box Main { method main(args) {
// Local b1=${lhs}; Local b2=${rhs}; return b1 ${op} b2;
local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" +
"{\"type\":\"Local\",\"name\":\"b1\",\"expr\":{\"type\":\"Bool\",\"value\":${lhs}}}," +
"{\"type\":\"Local\",\"name\":\"b2\",\"expr\":{\"type\":\"Bool\",\"value\":${rhs}}}," +
"{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"${op}\",\"lhs\":{\"type\":\"Var\",\"name\":\"b1\"},\"rhs\":{\"type\":\"Var\",\"name\":\"b2\"}}}]}";
// Use delegate extern to avoid builder toggles/env.get
local arr = new ArrayBox(); arr.push(j)
local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr)
if out == null { return 0 }
local s = "" + out
print(s)
if s.indexOf("\"op\":\"branch\"") >= 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

View File

@ -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" <<HAKO
static box Main { method main(args) {
// Local b1=${lhs_tf}; Local b2=${rhs_tf}; return b1 ${op} b2;
local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" +
"{\"type\":\"Local\",\"name\":\"b1\",\"expr\":{\"type\":\"Bool\",\"value\":${lhs_tf}}}," +
"{\"type\":\"Local\",\"name\":\"b2\",\"expr\":{\"type\":\"Bool\",\"value\":${rhs_tf}}}," +
"{\"type\":\"Return\",\"expr\":{\"type\":\"Logical\",\"op\":\"${op}\",\"lhs\":{\"type\":\"Var\",\"name\":\"b1\"},\"rhs\":{\"type\":\"Var\",\"name\":\"b2\"}}}]}";
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] 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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