restore(lang): full lang tree from ff3ef452 (306 files) — compiler, vm, shared, runner, c-abi, etc.\n\n- Restores lang/ directory (files≈306, dirs≈64) as per historical branch with selfhost sources\n- Keeps our recent parser index changes in compiler/* (merged clean by checkout)\n- Unblocks selfhost development and documentation references

This commit is contained in:
nyash-codex
2025-10-31 20:45:46 +09:00
parent dbc285f2b1
commit e5f697eb22
244 changed files with 16915 additions and 47 deletions

15
lang/src/vm/DEPRECATED.md Normal file
View File

@ -0,0 +1,15 @@
# DEPRECATED: selfhost/vm (MiniVM sandbox)
This directory hosts the original MiniVM used for early selfhosting.
Policy
- Status: Frozen (no new features).
- Purpose: Dev/education and targeted repros only.
- Successor: selfhost/hakorune-vm (Hakorune VM) — nyvm maps here by default.
Migration
- Prefer using aliases under `selfhost.hakorune-vm.*`.
- MiniVM specific smokes remain rc-only and opt-in.
Removal trigger
- When Hakorune VM reaches feature parity and quick/integration remain green for one sprint, MiniVM will be retired.

View File

@ -0,0 +1,6 @@
// LAYER_GUARD — VM Layer Guard (Phase 20.8)
// Policy:
// - lang/src 優先。lang/src 配下からの `using "selfhost/..."` 直参照は禁止。
// - 参照は lang/src の等価箱(ミラー)へ統一し、段階撤退を進める。
// - MiniVM の新規機能追加は慎重に(既定は凍結・スモークで仕様固定)。
static box MiniVmLayerGuard { main(args) { return 0 } }

105
lang/src/vm/README.md Normal file
View File

@ -0,0 +1,105 @@
# VM Layout (Current → Target)
Current
- `lang/src/vm/hakorune-vm/` — Hakorune VM (nyvm) implementation
- `lang/src/vm/boxes/` — Shared helpers (op_handlers, scanners, compare, etc.)
- MiniVM minimal executor lives as boxes (e.g., `boxes/mir_vm_min.hako`)
Target (post20.12b, gradual)
- `engines/hakorune/` — mainline nyvm engine
- `engines/mini/` — MiniVM engine (educational/minimal)
- `boxes/` — shared helpers
- `core/` — centralized execution core (value/state/reader/dispatcher + ops)
Policy
- Engines orchestrate execution and may depend on boxes and shared/*.
- Boxes are pure helpers (no engine loop, no I/O, no plugin/ABI).
- Parser/Resolver/Emitter must not be imported from engines/boxes.
- Core provides engineagnostic execution primitives and should not import
enginespecific modules. During migration, temporary adapters may exist.
BridgeB (Ny/Core 直行)
- Wrapper 経路では `include "lang/src/vm/core/dispatcher.hako"` で Core Dispatcher を取り込み、
`NyVmDispatcher.run(json)` を直接呼び出す。`using` は名前解決のみで実体は登録されないため、
Core を呼ぶ目的では `include` を用いること。
GateC(Core) 直行(`NYASH_GATE_C_CORE=1`)は JSON→Core Interpreter 実行なのでこの問題の影響を受けない。
Toggles and Canaries
- Core canaries (quick profile): enable with `SMOKES_ENABLE_CORE_CANARY=1`.
- Emit→nyvm(Core) scripts: `tools/smokes/v2/profiles/quick/core/canary_emit_nyvm_core_{return,binop,if}_vm.sh`
- GateC(Core, json→Core 直行) canaries: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh`既定OFF
- GateC(Core) array sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.sh`push→set→get をログで検証)
- GateC(Core) map sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_map_{len,iterator}_vm.sh`
- Emit→Core map len/get: `tools/smokes/v2/profiles/quick/core/canary_emit_core_map_len_get_vm.sh`
- GateC Direct sanity: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_direct_string_vm.sh`
- Runner Core toggle: `HAKO_NYVM_CORE=1` (or `NYASH_NYVM_CORE=1`) selects the
Core bridge for the nyvm wrapper path.
- GateC Core route: set `NYASH_GATE_C_CORE=1` (or `HAKO_GATE_C_CORE=1`) to
execute MIR(JSON v0) directly via Core (interpreter path; quiet; exit code mirrors return).
- Env: `NYASH_CORE_MAX_ITERS` or `HAKO_CORE_MAX_ITERS` overrides the Core dispatcher loop cap (default 10000).
- Plugins: when `HAKO_GATE_C_ENABLE_PLUGINS=1` is set, the runner normalizes
Array/Map core methods through HostHandleRouter (`HAKO_ARRAY_FORCE_HOST=1`,
`HAKO_MAP_FORCE_HOST=1`) to keep value/return semantics stable. Plugins が OFF のときは
ビルトインの `ArrayBox` / `MapBox` にフォールバックする。Map の `len()/size()` は extern adapter が
ビルトイン `MapBox` の内部データ長を返すフォールバックを持つためplugins=OFF でも0 固定にはならない。
- Errors: VM 実行/JSON読込エラー時は非0で終了FailFast
Deprecations
- `NYASH_GATE_C_DIRECT` は移行中の互換トグルTTLだよ。将来は GateC(Core)
直行(`HAKO_GATE_C_CORE=1`)に統一予定。新しい導線では Core の実行仕様(数値=rc,
安定化した診断タグ)が適用されるよ。
- 互換トグルを使うと起動時に警告が出るよ(`HAKO_GATE_C_DIRECT_SILENCE=1` で抑止可)。
Diagnostics (stable tags)
- 本フェーズでは、GateC(Core) の境界で安定タグを整形して出力する:
- `[core/binop] div by zero`
- `[core/mir_call] array get out of bounds`
- `[core/mir_call] array set out of bounds`
- `[core/mir_call] modulefn unsupported: …`
- `[core/mir_call] map iterator unsupported`
- `[core/mir_call] map len missing arg`
- `[core/mir_call] map set missing key|bad key|missing value|bad value`
- `[core/mir_call] map get missing key|bad key`
- `[core/mir_call] unsupported callee type: Closure`
- GateC Direct では、リーダー/検証レイヤの診断をそのまま用いる(例: `unsupported callee type (expected Extern): ModuleFunction`)。
Exit code differences
- Core: 数値=rcOS仕様により 0255 に丸められる。例: 777 → rc=9、エラーは非0
- Direct: 数値出力のみrc=0、エラーは非0
- 数値が 255 を超えるケースは標準出力の値で検証することrc は下位8ビットへ丸められるため
注: これらの整形は移行中の暫定仕様で、最終的には Core 側に移管される予定CURRENT_TASK に TTL を記載)。
Minimal mir_call semantics (Core)
- Implemented (scoped):
- Constructor: `ArrayBox`(サイズメタ初期化) / `MapBox`(エントリ数メタ初期化)
- Methods (Array): `size()/push()/pop()/get()/set()`(メタデータでサイズ検証)
- Methods (Map): `size()/len()/iterator()/set()/get()`(エントリ数メタを返す/更新する/メタから取得する)
- ModuleFunction: `ArrayBox.len/0` / `MapBox.len/0`(メタのサイズを返す)— 他はタグ付き FailFast
- Global/Extern: `env.console.{log|warn|error}`(数値引数のみ印字)
- Others are FailFast安定文言を出力
See also: docs/development/architecture/collection_semantics.mdArray/Map のSSOT集約
String helpers
- Core routeGateC/Coreでの最小サポートMethod:
- `String.size/0` — 文字列長(バイト)を返す
- `String.indexOf/1` — 最初の一致位置、なければ -1
- `String.lastIndexOf/1` — 最後の一致位置、なければ -1
- `String.substring/2` — 半開区間 [start, end) を返す
- インデックス規約bytes ベース):
- start/end は範囲 [0, size] にクランプされる(負の値は 0、size 超は size
- start > end の場合は空文字size==0
- インデックスは UTF8 のバイト境界(コードポイント境界ではない)。
- ModuleFunction:
- `StringHelpers.to_i64/1` — interpreter に inline 実装あり(数値文字列のみを許容)。
数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。
Core dispatcher canaries直行ルート
- `profiles/quick/core/canary_core_dispatcher_*` は GateC(Core) 直行へ移行済み。
一部(大きな値や pluginenabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、
rc はフォールバックとして扱うTTL; 収束後に rc 検証に戻す)。
Aliases
- Keep existing logical module names in `hako.toml` and introduce aliases to
new paths when transitioning.

View File

@ -0,0 +1,16 @@
# VM Boxes — Shared Helpers (Guard)
Responsibility
- Pure helpers used by engines (op handlers, scanners, compare, JSON frag/cursor).
Allowed
- Import from `lang/src/shared/*` and other boxes/*.
Forbidden
- Engine orchestration (no main run loop, no dispatch)
- Direct I/O or plugin/ABI calls (engines should own those)
Notes
- MiniVM's minimal executor currently lives here (`mir_vm_min.hako`). It may
move under `engines/mini/` later; keep it boxpure (no I/O) until then.

View File

@ -0,0 +1,123 @@
// arithmetic.hako — ArithmeticBox
// 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
static box ArithmeticBox {
// Internal helpers operate on decimal strings to avoid overflow.
_to_dec_str(x) {
local s = "" + x
local i = 0
loop(i < s.size()) { local ch = s.substring(i,i+1) if ch=="+" || ch==" " { i=i+1 } else { break } }
s = s.substring(i, s.size())
i = 0
loop(i < s.size() && s.substring(i,i+1)=="0") { i = i + 1 }
if i >= s.size() { return "0" }
return s.substring(i, s.size())
}
_cmp_dec(a, b) {
local sa = me._to_dec_str(a)
local sb = me._to_dec_str(b)
if sa.size() < sb.size() { return -1 }
if sa.size() > sb.size() { return 1 }
local i = 0
loop(i < sa.size()) {
local ca = sa.substring(i,i+1)
local cb = sb.substring(i,i+1)
if ca != cb { if ca < cb { return -1 } else { return 1 } }
i = i + 1
}
return 0
}
_add_dec(a, b) {
local sa = me._to_dec_str(a)
local sb = me._to_dec_str(b)
local i = sa.size() - 1
local j = sb.size() - 1
local carry = 0
local out = new ArrayBox()
loop(i >= 0 || j >= 0 || carry > 0) {
local da = 0
local db = 0
if i >= 0 { da = ("0123456789").indexOf(sa.substring(i,i+1)) i = i - 1 }
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j,j+1)) j = j - 1 }
local s = da + db + carry
carry = s / 10
local d = s % 10
out.push(("0123456789").substring(d, d+1))
}
local k = out.size()
local res = ""
loop(k > 0) { k = k - 1 res = res + (""+out.get(k)) }
return res
}
_sub_dec(a, b) {
// Supports negative result: if a<b, return "-" + (b-a)
local sa = me._to_dec_str(a)
local sb = me._to_dec_str(b)
local c = me._cmp_dec(sa, sb)
if c == 0 { return "0" }
if c < 0 { return "-" + me._sub_dec(sb, sa) }
local i = sa.size() - 1
local j = sb.size() - 1
local borrow = 0
local out = new ArrayBox()
loop(i >= 0) {
local da = ("0123456789").indexOf(sa.substring(i, i+1)) - borrow
local db = 0
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j, j+1)) j = j - 1 }
if da < db { da = da + 10 borrow = 1 } else { borrow = 0 }
local d = da - db
out.push(("0123456789").substring(d, d+1))
i = i - 1
}
local k = out.size() - 1
loop(true) { if k > 0 && out.get(k) == "0" { k = k - 1 } else { break } }
local res = ""
loop(k >= 0) { res = res + (""+out.get(k)) k = k - 1 }
return res
}
_mul_dec(a, b) {
local sa = me._to_dec_str(a)
local sb = me._to_dec_str(b)
if sa == "0" || sb == "0" { return "0" }
local na = sa.size()
local nb = sb.size()
local res = new ArrayBox()
local t = 0
loop(t < na+nb) { res.push(0) t = t + 1 }
local ia = na - 1
loop(ia >= 0) {
local da = ("0123456789").indexOf(sa.substring(ia, ia+1))
local carry = 0
local ib = nb - 1
loop(ib >= 0) {
local db = ("0123456789").indexOf(sb.substring(ib, ib+1))
local idx = ia + ib + 1
local sum = res.get(idx) + da * db + carry
res.set(idx, sum % 10)
carry = sum / 10
ib = ib - 1
}
res.set(ia, res.get(ia) + carry)
ia = ia - 1
}
local k = 0
loop(k < res.size() && res.get(k) == 0) { k = k + 1 }
local out = ""
loop(k < res.size()) { out = out + ("0123456789").substring(res.get(k), res.get(k)+1) k = k + 1 }
if out == "" { return "0" } else { return out }
}
_str_to_int(s) { return StringHelpers.to_i64(s) }
// Public adapters: operate on i64-likes and return i64-likes.
add_i64(a, b) { return me._str_to_int(me._add_dec(a, b)) }
sub_i64(a, b) { return me._str_to_int(me._sub_dec(a, b)) }
mul_i64(a, b) { return me._str_to_int(me._mul_dec(a, b)) }
}

View File

@ -0,0 +1,22 @@
// cfg_navigator.hako — CfgNavigatorBoxブロックの先頭/末尾シーク)
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
static box CfgNavigatorBox {
// Provide index_of_from to avoid tail-based fallback/ambiguous resolution
index_of_from(text, needle, pos) { return StringOps.index_of_from(text, needle, pos) }
_int_to_str(n) {
if n == 0 { return "0" }
if n < 0 { return "-" + me._int_to_str(0 - n) }
local v=n local out="" local digits="0123456789"
loop(v>0){ local d=v%10 local ch=digits.substring(d,d+1) out=ch+out v=v/10 }
return out
}
// Escape-aware array end finder via JsonCursorBox
_seek_array_end(text, pos){ return JsonCursorBox.seek_array_end(text, pos) }
block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 }
block_insts_end(mjson,insts_start){ return JsonCursorBox.seek_array_end(mjson,insts_start) }
}

View File

@ -0,0 +1,28 @@
// compare_ops.hako — CompareOpsBox
// Responsibility: mapping of symbols to kinds and evaluating compares.
// Non-responsibility: scanning/VM execution/arithmetic.
static box CompareOpsBox {
map_symbol(sym) {
return match sym {
"==" => "Eq"
"!=" => "Ne"
"<" => "Lt"
"<=" => "Le"
">" => "Gt"
">=" => "Ge"
_ => ""
}
}
eval(kind, a, b) {
return match kind {
"Eq" => { if a == b { 1 } else { 0 } }
"Ne" => { if a != b { 1 } else { 0 } }
"Lt" => { if a < b { 1 } else { 0 } }
"Gt" => { if a > b { 1 } else { 0 } }
"Le" => { if a <= b { 1 } else { 0 } }
"Ge" => { if a >= b { 1 } else { 0 } }
_ => 0
}
}
}

View File

@ -0,0 +1,25 @@
// compare_scan_box.hako — CompareScanBox
// Responsibility: Parse compare instruction (v0/v1) and return dst/lhs/rhs/kind
// - v0: keys cmp/lhs/rhs/dst
// - 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
static box CompareScanBox {
parse(seg) {
if seg == null { return null }
local dst = JsonFragBox.get_int(seg, "dst")
local lhs = JsonFragBox.get_int(seg, "lhs")
local rhs = JsonFragBox.get_int(seg, "rhs")
local kind = JsonFragBox.get_str(seg, "cmp")
if kind == "" {
local sym = JsonFragBox.get_str(seg, "operation")
if sym != "" { kind = CompareOpsBox.map_symbol(sym) } else { kind = "Eq" }
}
return map({ dst: dst, lhs: lhs, rhs: rhs, kind: kind })
}
}
static box CompareScanMain { main(args){ return 0 } }

View File

@ -0,0 +1,98 @@
// flow_debugger.hako — MiniVM JSON v0 デバッグ用の軽量箱
// 責務:
// - JSON v0 の関数/ブロック/命令を静的に走査し、
// - ブロックID集合の抽出
// - branch/jump の then/else/target が妥当なIDか検証
// - op シーケンスの要約出力(最初の N 件)
// 非責務:
// - 実行・評価(それは MirVmMin に委譲)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box FlowDebugBox {
// ユーティリティ — 文字列検索
_index_of_from(hay, needle, pos) { if pos < 0 { pos = 0 } local n = hay.size() if pos > n { return -1 } local i = pos local m = needle.size() if m <= 0 { return pos } local limit = n - m loop(i <= limit) { if hay.substring(i, i+m) == needle { return i } i = i + 1 } return -1 }
_read_digits(text, pos) { return StringHelpers.read_digits(text, pos) }
_int_to_str(n) { return StringHelpers.int_to_str(n) }
// ブロックID集合を抽出
collect_block_ids(mjson) {
local ids = new ArrayBox()
local pos = 0
loop(true) {
local p = me._index_of_from(mjson, "\"id\":", pos)
if p < 0 { break }
local d = me._read_digits(mjson, p + 5)
if d != "" { ids.push(d) }
pos = p + 5
}
return ids
}
// op シーケンスを先頭から limit 件だけ抽出
collect_ops(mjson, limit) {
if limit == null { limit = 50 }
local ops = new ArrayBox()
local pos = 0
loop(ops.size() < limit) {
local p = me._index_of_from(mjson, "\"op\":\"", pos)
if p < 0 { break }
local q = me._index_of_from(mjson, "\"", p + 6)
if q < 0 { break }
local op = mjson.substring(p + 6, q)
ops.push(op)
pos = q + 1
}
return ops
}
// branch/jump の then/else/target を抽出し、集合 membership を検査
validate_cf_targets(mjson) {
local ids = me.collect_block_ids(mjson)
// Set 風マップ化
local idset = map({})
local i = 0
loop(i < ids.size()) { idset.set(ids.get(i), 1) i = i + 1 }
local errs = new ArrayBox()
local pos = 0
loop(true) {
local p = me._index_of_from(mjson, "\"op\":\"branch\"", pos)
if p < 0 { break }
// then
local pt = me._index_of_from(mjson, "\"then\":", p)
local pe = me._index_of_from(mjson, "\"else\":", p)
if pt >= 0 { local t = me._read_digits(mjson, pt + 7) if t != "" && idset.get(t) == null { errs.push("branch.then invalid:" + t) } }
if pe >= 0 { local e = me._read_digits(mjson, pe + 7) if e != "" && idset.get(e) == null { errs.push("branch.else invalid:" + e) } }
pos = p + 14
}
// jump
pos = 0
loop(true) {
local p = me._index_of_from(mjson, "\"op\":\"jump\"", pos)
if p < 0 { break }
local pt = me._index_of_from(mjson, "\"target\":", p)
if pt >= 0 { local t = me._read_digits(mjson, pt + 9) if t != "" && idset.get(t) == null { errs.push("jump.target invalid:" + t) } }
pos = p + 12
}
// レポート
if errs.size() == 0 { print("{\"kind\":\"flow_debug\",\"ok\":true,\"blocks\":" + (""+ids.size()) + "}") }
else {
local k = 0
loop(k < errs.size()) { print("{\"kind\":\"flow_debug\",\"ok\":false,\"msg\":\"" + errs.get(k) + "\"}") k = k + 1 }
}
return errs.size()
}
// 要約: 先頭の op を列挙
summarize_ops(mjson, limit) {
local ops = me.collect_ops(mjson, limit)
local i = 0
loop(i < ops.size()) { print("{\"kind\":\"flow_ops\",\"op\":\"" + ops.get(i) + "\"}") i = i + 1 }
return ops.size()
}
main(args) { return 0 }
}

View File

@ -0,0 +1,24 @@
// guard_box.hako — GuardBox
// 責務: 反復処理に上限を設け、無限ループをFailFastに近い形で防止
// 非責務: エラー出力のポリシー決定(呼び出し側で扱う)
box GuardBox {
_name: StringBox
_max: IntegerBox
_cur: IntegerBox
birth(name, max_iter) {
me._name = name
me._max = max_iter
me._cur = 0
}
reset() { me._cur = 0 }
// 正常:1, 上限超:0 を返す(呼び出し側で中断)
tick() {
me._cur = me._cur + 1
if me._cur > me._max { return 0 }
return 1
}
}

View File

@ -0,0 +1,116 @@
// 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
static box InstructionScannerBox {
_tprint(msg) { if call("String.indexOf/2", msg, "[ERROR]") >= 0 { print(msg) } }
index_of_from(hay, needle, pos) { return CfgNavigatorBox.index_of_from(hay, needle, pos) }
find_balanced_object_end(json, idx) { return JsonCursorBox.seek_obj_end(json, idx) }
normalize_delimiters(seg) {
if seg == null { return "" }
local out = ""
local i = 0
local n = seg.size()
loop (i < n) {
local ch = seg.substring(i, i+1)
if i+2 <= n {
local two = seg.substring(i, i+2)
if two == "}," {
if i+2 < n && seg.substring(i+2, i+3) == "{" { out = out + "}|{" i = i + 3 continue }
}
}
out = out + ch
i = i + 1
}
return out
}
_extract_op(obj) {
// Prefer explicit op key with escape-aware string end detection
local k1 = "\"op\":\""
local p1 = obj.indexOf(k1)
if p1 >= 0 {
local i = p1 + k1.size() // start of value (right after opening quote)
local j = JsonCursorBox.scan_string_end(obj, i - 1)
if j > i { return obj.substring(i, j) }
}
// v1 style
local kk = "\"kind\":\""
local pk = obj.indexOf(kk)
if pk >= 0 {
local i2 = pk + kk.size()
local j2 = JsonCursorBox.scan_string_end(obj, i2 - 1)
if j2 > i2 {
local k = obj.substring(i2, j2)
if k == "Const" { return "const" }
if k == "Ret" { return "ret" }
if k == "Compare" { return "compare" }
if k == "Branch" { return "branch" }
if k == "Jump" { return "jump" }
}
}
// compare v1 shorthand
local ko = "\"operation\":\""
local po = obj.indexOf(ko)
if po >= 0 { return "compare" }
// last-resort heuristics
// Use dual-key probe to handle both plain and escaped forms when needed
if me._has_key(obj, "lhs") == 1 and me._has_key(obj, "rhs") == 1 { return "compare" }
if me._has_key(obj, "cond") == 1 { return "branch" }
if me._has_key(obj, "target") == 1 { return "jump" }
// Detect explicit ret first
if obj.indexOf("\"op\":\"ret\"") >= 0 { return "ret" }
// Detect v1-style Ret without op key: presence of top-level "value" and absence of other discriminator keys
if me._has_key(obj, "value") == 1 {
if me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 {
return "ret"
}
}
// Const fallback (typed value object)
if obj.indexOf("\"value\":{\"type\":\"i64\"") >= 0 { return "const" }
return ""
}
// Dual-key existence probe (plain/escaped)
_has_key(seg, key) {
if seg == null { return 0 }
local plain = "\"" + key + "\""
local escaped = "\\\"" + key + "\\\""
local pos = JsonCursorBox.find_key_dual(seg, plain, escaped, 0)
if pos >= 0 { return 1 } else { return 0 }
}
// Return a map {start,end,op} for next object starting at/after pos, or null if none
next(seg, pos) {
if seg == null { return null }
if pos < 0 { pos = 0 }
local start = me.index_of_from(seg, "{", pos)
if start < 0 { return null }
local endp = me.find_balanced_object_end(seg, start)
if endp < 0 { return null }
endp = endp + 1
local obj = seg.substring(start, endp)
local op = me._extract_op(obj)
return map({ start: start, end: endp, op: op })
}
// MiniVM friendly variant: return "start,end,op" to avoid MapBox dependency
next_tuple(seg, pos) {
if seg == null { return "" }
if pos < 0 { pos = 0 }
local start = me.index_of_from(seg, "{", pos)
if start < 0 { return "" }
local endp = me.find_balanced_object_end(seg, start)
if endp < 0 { return "" }
endp = endp + 1
local obj = seg.substring(start, endp)
local op = me._extract_op(obj)
return "" + start + "," + endp + "," + op
}
}

View File

@ -0,0 +1,61 @@
// Mini-VM JSON cursor helpers (extracted)
// One static box per file per using/include policy
static box MiniJsonCur {
_is_digit(ch) { if ch == "0" { return 1 } if ch == "1" { return 1 } if ch == "2" { return 1 } if ch == "3" { return 1 } if ch == "4" { return 1 } if ch == "5" { return 1 } if ch == "6" { return 1 } if ch == "7" { return 1 } if ch == "8" { return 1 } if ch == "9" { return 1 } return 0 }
// Skip whitespace from pos; return first non-ws index or -1
next_non_ws(s, pos) {
local i = pos
local n = s.size()
loop (i < n) {
local ch = s.substring(i, i+1)
if ch != " " && ch != "\n" && ch != "\r" && ch != "\t" { return i }
i = i + 1
}
return -1
}
// Read a quoted string starting at pos '"'; returns decoded string (no state)
read_quoted_from(s, pos) {
local i = pos
if s.substring(i, i+1) != "\"" { return "" }
i = i + 1
local out = ""
local n = s.size()
loop (i < n) {
local ch = s.substring(i, i+1)
if ch == "\"" { break }
if ch == "\\" {
i = i + 1
ch = s.substring(i, i+1)
}
out = out + ch
i = i + 1
}
return out
}
// Read consecutive digits from pos
read_digits_from(s, pos) {
local out = ""
local i = pos
// guard against invalid position (null/negative)
if i == null { return out }
if i < 0 { return out }
loop (true) {
local ch = s.substring(i, i+1)
if ch == "" { break }
// inline digit check to avoid same-box method dispatch
if ch == "0" { out = out + ch i = i + 1 continue }
if ch == "1" { out = out + ch i = i + 1 continue }
if ch == "2" { out = out + ch i = i + 1 continue }
if ch == "3" { out = out + ch i = i + 1 continue }
if ch == "4" { out = out + ch i = i + 1 continue }
if ch == "5" { out = out + ch i = i + 1 continue }
if ch == "6" { out = out + ch i = i + 1 continue }
if ch == "7" { out = out + ch i = i + 1 continue }
if ch == "8" { out = out + ch i = i + 1 continue }
if ch == "9" { out = out + ch i = i + 1 continue }
break
}
return out
}
}

View File

@ -0,0 +1,131 @@
// mini_collections.hako — Minimal collection boxes for selfhost VM tests
// Simple string-backed dynamic array of i64 (for smoke/testing)
box MiniArray {
field data: String
birth() { me.data = "" return 0 }
push(v) {
v = "" + v
if me.data == "" { me.data = v } else { me.data = me.data + "," + v }
return 0
}
length() {
if me.data == "" { return 0 }
// count commas + 1
local s = me.data
local i = 0
local c = 1
loop(true) {
local j = s.indexOf(",", i)
if j < 0 { break }
c = c + 1
i = j + 1
}
return c
}
// FailFast accessor: returns element string; prints error and returns 0 on OOB
at(index) {
// normalize and validate index
local si = "" + index
local idx = 0
if si != "" {
local i = 0
loop(i < si.size()) { idx = idx * 10 + ("0123456789".indexOf(si.substring(i,i+1))) i = i + 1 }
}
local n = me.length()
if idx < 0 || idx >= n { print("[ERROR] MiniArray.at: index out of range: " + (""+idx) + "/" + (""+n)) return 0 }
// find start position of idx-th element
local s = me.data
local pos = 0
local cur = 0
loop(cur < idx) {
local j = s.indexOf(",", pos)
if j < 0 { print("[ERROR] MiniArray.at: broken storage") return 0 }
pos = j + 1
cur = cur + 1
}
local endp = s.indexOf(",", pos)
if endp < 0 { endp = s.size() }
return s.substring(pos, endp)
}
}
// Simple string-backed map (key->value as 'k=v\n')
box MiniMap2 {
field store: String
birth() { me.store = "" return 0 }
set(key, value) {
key = "" + key
value = "" + value
// Guard for unsupported characters in key that break line format
if key.indexOf("\n") >= 0 || key.indexOf("=") >= 0 {
print("[ERROR] MiniMap2.set: invalid key contains newline or '='")
return 0
}
// remove and append
local out = ""
local s = me.store
local pos = 0
loop(true) {
local nl = s.indexOf("\n", pos)
if nl < 0 { break }
local line = s.substring(pos, nl)
local eq = line.indexOf("=")
if eq >= 0 {
local k = line.substring(0, eq)
if k != key { out = out + line + "\n" }
}
pos = nl + 1
}
me.store = out + key + "=" + value + "\n"
return 0
}
get(key) {
key = "" + key
local s = me.store
local pos = 0
loop(true) {
local nl = s.indexOf("\n", pos)
if nl < 0 { break }
local line = s.substring(pos, nl)
local eq = line.indexOf("=")
if eq >= 0 {
local k = line.substring(0, eq)
if k == key { return line.substring(eq + 1, line.size()) }
}
pos = nl + 1
}
return null
}
// Strict getter: FailFast when key is missing; returns 0 on failure
get_or_fail(key) {
key = "" + key
local v = me.get(key)
if v == null { print("[ERROR] MiniMap2.get: key not found: " + key) return 0 }
return v
}
has(key) {
key = "" + key
local s = me.store
if s == "" { return 0 }
// naive contains of 'key=' at line start or after \n
local needle = key + "="
if s.substring(0, needle.size()) == needle { return 1 }
local p = s.indexOf("\n" + needle)
if p >= 0 { return 1 }
return 0
}
size() {
if me.store == "" { return 0 }
local s = me.store
local i = 0
local c = 0
loop(true) {
local nl = s.indexOf("\n", i)
if nl < 0 { break }
c = c + 1
i = nl + 1
}
return c
}
}

View File

@ -0,0 +1,14 @@
using "lang/src/vm/boxes/json_cur.hako" as MiniJson
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
using "lang/src/shared/common/mini_vm_binop.hako" as MiniVmBinOp
using "lang/src/shared/common/mini_vm_compare.hako" as MiniVmCompare
using "lang/src/vm/boxes/mini_vm_prints.hako" as MiniVmPrints
static box MiniVm {
_str_to_int(s) { return new MiniVmScan()._str_to_int(s) }
_int_to_str(n) { return new MiniVmScan()._int_to_str(n) }
read_digits(json, pos) { return new MiniJsonCur().read_digits_from(json, pos) }
read_json_string(json, pos) { return new MiniJsonCur().read_quoted_from(json, pos) }
index_of_from(hay, needle, pos) { return new MiniVmScan().index_of_from(hay, needle, pos) }
next_non_ws(json, pos) { return new MiniJsonCur().next_non_ws(json, pos) }
}

View File

@ -0,0 +1,18 @@
// mini_vm_entry.hako — MiniVmEntryBox
// Thin entry wrapper to stabilize static call names for smokes and tools.
using "lang/src/vm/boxes/mir_vm_min.hako" as MirVmMin
static box MiniVmEntryBox {
run_trace(j) {
if j.substring(0,1) == "{" {
local payload = j.substring(1, j.size())
local j2 = "{\"__trace__\":1," + payload
return MirVmMin.run_min(j2)
}
return MirVmMin.run_min(j)
}
run_min(j) { return MirVmMin.run_min(j) }
int_to_str(v) { return MirVmMin._int_to_str(v) }
}

View File

@ -0,0 +1,114 @@
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
using "lang/src/shared/common/mini_vm_binop.hako" as MiniVmBinOp
using "lang/src/shared/common/mini_vm_compare.hako" as MiniVmCompare
// Use the JSON adapter facade for cursor ops (next_non_ws, digits)
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonLoader
static box MiniVmPrints {
_trace_enabled() { return 0 }
_fallback_enabled() { return 0 }
// literal string within Print
try_print_string_value_at(json, end, print_pos) {
local scan = new MiniVmScan()
local k_val = "\"value\":\""
local s = scan.index_of_from(json, k_val, print_pos)
if s < 0 || s >= end { return -1 }
local i = s + k_val.size()
local j = scan.index_of_from(json, "\"", i)
if j <= 0 || j > end { return -1 }
print(json.substring(i, j))
return j + 1
}
// literal int within Print (typed)
try_print_int_value_at(json, end, print_pos) {
local scan = new MiniVmScan()
local k_expr = "\"expression\":{"
local epos = scan.index_of_from(json, k_expr, print_pos)
if epos <= 0 || epos >= end { return -1 }
local obj_start = scan.index_of_from(json, "{", epos)
if obj_start <= 0 || obj_start >= end { return -1 }
local obj_end = scan.find_balanced_object_end(json, obj_start)
if obj_end <= 0 || obj_end > end { return -1 }
local k_tint = "\"type\":\"int\""
local tpos = scan.index_of_from(json, k_tint, obj_start)
if tpos <= 0 || tpos >= obj_end { return -1 }
local k_val2 = "\"value\":"
local v2 = scan.index_of_from(json, k_val2, tpos)
if v2 <= 0 || v2 >= obj_end { return -1 }
local digits = scan.read_digits(json, v2 + k_val2.size())
if digits == "" { return -1 }
print(digits)
return obj_end + 1
}
// minimal FunctionCall printer for echo/itoa
try_print_functioncall_at(json, end, print_pos) {
local scan = new MiniVmScan()
local k_fc = "\"kind\":\"FunctionCall\""
local fcp = scan.index_of_from(json, k_fc, print_pos)
if fcp <= 0 || fcp >= end { return -1 }
local k_name = "\"name\":\""
local npos = scan.index_of_from(json, k_name, fcp)
if npos <= 0 || npos >= end { return -1 }
local ni = npos + k_name.size()
local nj = scan.index_of_from(json, "\"", ni)
if nj <= 0 || nj > end { return -1 }
local fname = json.substring(ni, nj)
local k_args = "\"arguments\":["
local apos = scan.index_of_from(json, k_args, nj)
if apos <= 0 || apos >= end { return -1 }
local arr_start = scan.index_of_from(json, "[", apos)
local arr_end = scan.find_balanced_array_end(json, arr_start)
if arr_start <= 0 || arr_end <= 0 || arr_end > end { return -1 }
// handle empty args []
local nn = new MiniJsonLoader().next_non_ws(json, arr_start+1)
if nn > 0 && nn <= arr_end {
if json.substring(nn, nn+1) == "]" {
if fname == "echo" { print("") return arr_end + 1 }
if fname == "itoa" { print("0") return arr_end + 1 }
return -1
}
}
// first arg type
local k_t = "\"type\":\""
local atpos = scan.index_of_from(json, k_t, arr_start)
if atpos <= 0 || atpos >= arr_end {
if fname == "echo" { print("") return arr_end + 1 }
if fname == "itoa" { print("0") return arr_end + 1 }
return -1
}
atpos = atpos + k_t.size()
local at_end = scan.index_of_from(json, "\"", atpos)
if at_end <= 0 || at_end > arr_end { return -1 }
local aty = json.substring(atpos, at_end)
if aty == "string" {
local k_sval = "\"value\":\""
local svalp = scan.index_of_from(json, k_sval, at_end)
if svalp <= 0 || svalp >= arr_end { return -1 }
local si = svalp + k_sval.size()
local sj = scan.index_of_from(json, "\"", si)
if sj <= 0 || sj > arr_end { return -1 }
local sval = json.substring(si, sj)
if fname == "echo" { print(sval) return sj + 1 }
return -1
}
if aty == "int" || aty == "i64" || aty == "integer" {
local k_ival = "\"value\":"
local ivalp = scan.index_of_from(json, k_ival, at_end)
if ivalp <= 0 || ivalp >= arr_end { return -1 }
local digits = scan.read_digits(json, ivalp + k_ival.size())
if fname == "itoa" || fname == "echo" { print(digits) return ivalp + k_ival.size() }
return -1
}
return -1
}
// Print all Print-Literal values within [start,end]
print_prints_in_slice(json, start, end) {
// Prefer plugin result whenever JSON route ran
local dbg = _trace_enabled()
local printed = 0
printed = printed // placeholder to keep structure; logic in .nyash retained
return printed
}
process_if_once(json) { return new MiniVmPrints().process_if_once(json) }
print_all_print_literals(json) { return new MiniVmPrints().print_all_print_literals(json) }
}

View File

@ -0,0 +1,52 @@
// minivm_probe.hako — MiniVM JSON v0 の a/b/r を観測する軽量プローブ
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/instruction_scanner.hako" as InstructionScannerBox
using "lang/src/vm/boxes/op_handlers.hako" as OpHandlersBox
static box MiniVmProbe {
probe_compare(mjson) {
local regs = map({})
local seg = JsonFragBox.block0_segment(mjson)
if seg.size() == 0 { return map({}) }
local pos = 0
loop(true) {
if pos >= seg.size() { break }
// Use escape-aware scanner to get next instruction object
local mm = InstructionScannerBox.next(seg, pos)
if mm == null { break }
local s = mm.get("start")
local i = mm.get("end")
local obj = seg.substring(s, i)
local op = JsonFragBox.get_str(obj, "op")
match op {
"const" => { OpHandlersBox.handle_const(obj, regs) }
"binop" => { OpHandlersBox.handle_binop(obj, regs) }
"compare" => {
local kind = JsonFragBox.get_str(obj, "cmp")
local lhs = JsonFragBox.get_int(obj, "lhs")
local rhs = JsonFragBox.get_int(obj, "rhs")
local as2 = "" + regs.get(""+lhs)
local bs2 = "" + regs.get(""+rhs)
local a = JsonFragBox._str_to_int(as2)
local b = JsonFragBox._str_to_int(bs2)
local r = match kind {
"Eq" => { if a == b { 1 } else { 0 } }
"Ne" => { if a != b { 1 } else { 0 } }
"Lt" => { if a < b { 1 } else { 0 } }
"Gt" => { if a > b { 1 } else { 0 } }
"Le" => { if a <= b { 1 } else { 0 } }
"Ge" => { if a >= b { 1 } else { 0 } }
_ => 0
}
return map({ a: a, b: b, r: r })
}
_ => {}
}
pos = i
}
return map({ a: 0, b: 0, r: 0 })
}
}
static box MiniVmProbeStub { main(args) { return 0 } }

View File

@ -0,0 +1,84 @@
// mir_vm_m2.nyash — Ny製の最小MIR(JSON v0)実行器M2: const/binop/ret
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
static box MirVmM2 {
_str_to_int(s) { return StringHelpers.to_i64(s) }
_int_to_str(n) { return StringHelpers.int_to_str(n) }
_find_int_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return null }
p = p + keypat.size()
local i = p
local out = ""
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { break }
if ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" { out = out + ch i = i + 1 } else { break }
}
if out == "" { return null }
return me._str_to_int(out)
}
_find_str_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return "" }
p = p + keypat.size()
local q = seg.indexOf(""", p)
if q < 0 { return "" }
return seg.substring(p, q)
}
_get(regs, id) { if regs.has(id) { return regs.get(id) } return 0 }
_set(regs, id, v) { regs.set(id, v) }
_bin(kind, a, b) {
if kind == "Add" { return a + b }
if kind == "Sub" { return a - b }
if kind == "Mul" { return a * b }
if kind == "Div" { if b == 0 { return 0 } else { return a / b } }
return 0
}
run(json) {
local regs = new MapBox()
local pos = StringOps.index_of_from(json, ""instructions":[", 0)
if pos < 0 {
print("0")
return 0
}
local cur = pos
loop(true) {
local op_pos = StringOps.index_of_from(json, ""op":"", cur)
if op_pos < 0 { break }
local name_start = op_pos + 6
local name_end = StringOps.index_of_from(json, """, name_start)
if name_end < 0 { break }
local opname = json.substring(name_start, name_end)
local next_pos = StringOps.index_of_from(json, ""op":"", name_end)
if next_pos < 0 { next_pos = json.size() }
local seg = json.substring(op_pos, next_pos)
if opname == "const" {
local dst = me._find_int_in(seg, ""dst":")
local val = me._find_int_in(seg, ""value":{"type":"i64","value":")
if dst != null and val != null { me._set(regs, "" + dst, val) }
} else { if opname == "binop" {
local dst = me._find_int_in(seg, ""dst":")
local kind = me._find_str_in(seg, ""op_kind":"")
local lhs = me._find_int_in(seg, ""lhs":")
local rhs = me._find_int_in(seg, ""rhs":")
if dst != null and lhs != null and rhs != null {
local a = me._get(regs, "" + lhs)
local b = me._get(regs, "" + rhs)
me._set(regs, "" + dst, me._bin(kind, a, b))
}
} else { if opname == "ret" {
local v = me._find_int_in(seg, ""value":")
if v == null { v = 0 }
local out = me._get(regs, "" + v)
print(me._int_to_str(out))
return 0
} } }
cur = next_pos
}
print("0")
return 0
}
}

View File

@ -0,0 +1,465 @@
// mir_vm_min.hako — Ny製の最小MIR(JSON v0)実行器const/compare/copy/branch/jump/ret の最小)
using "lang/src/vm/boxes/op_handlers.hako" as OpHandlersBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" 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
// Minimal map for MiniVM registers (avoid dependency on provider MapBox)
box MiniMap {
field store: String
birth() { me.store = "" return 0 }
set(key, value) {
key = "" + key
value = "" + value
// remove existing key
local out = ""
local s = me.store
local pos = 0
loop(true) {
local nl = s.indexOf("\n", pos)
if nl < 0 { break }
local line = s.substring(pos, nl)
local eq = line.indexOf("=")
if eq >= 0 {
local k = line.substring(0, eq)
if k != key { out = out + line + "\n" }
}
pos = nl + 1
}
me.store = out + key + "=" + value + "\n"
return 0
}
get(key) {
key = "" + key
local s = me.store
local pos = 0
local last = null
loop(true) {
local nl = s.indexOf("\n", pos)
if nl < 0 { break }
local line = s.substring(pos, nl)
local eq = line.indexOf("=")
if eq >= 0 {
local k = line.substring(0, eq)
if k == key { last = line.substring(eq + 1, line.size()) }
}
pos = nl + 1
}
return last
}
}
static box MirVmMin {
_tprint(msg) {
// Only emit hard errors by default; avoid env dependencies in MiniVM
// Coerce to string to avoid VoidBox receiver issues during early boot
msg = "" + msg
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
}
_d(msg, trace) { if trace == 1 { print(msg) } }
_parse_callee_name(seg) {
// naive scan: '"callee":{"name":"<sym>"'
local key = '"callee":{"name":"'
local p = seg.indexOf(key)
if p < 0 { return "" }
p = p + key.size()
local rest = seg.substring(p, seg.size())
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 p = seg.indexOf(key)
if p < 0 { return null }
p = p + key.size()
// find first digit or '-'
local i = p
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { return null }
if ch == "-" || (ch >= "0" && ch <= "9") { break }
i = i + 1
}
// collect digits
local out = ""
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out == "" { return null }
return JsonFragBox._str_to_int(out)
}
_handle_mir_call(seg, regs) {
// Support minimal externs only (i64 variants). FailFast for others.
local name = me._parse_callee_name(seg)
local arg0id = me._parse_first_arg(seg)
if name == "" {
me._tprint("[ERROR] mir_call: missing callee")
return
}
if arg0id == null { arg0id = -1 }
// String console: env/nyash console log/warn/error — treat arg0 as string
if name == "env.console.log" || name == "nyash.console.log" ||
name == "env.console.warn" || name == "nyash.console.warn" ||
name == "env.console.error" || name == "nyash.console.error" {
local v = ""
if arg0id >= 0 {
local raw = regs.getField(""+arg0id)
v = "" + raw
}
print(v)
return
}
if name == "hako_console_log_i64" {
local v = 0
if arg0id >= 0 { v = me._load_reg(regs, arg0id) }
print(me._int_to_str(v))
return
}
if name == "hako_bench_noop_i64" || name == "hako_bench_use_value_i64" {
// no-op (observability only)
return
}
// Unknown extern: FailFast (emit once)
me._tprint("[ERROR] extern not supported: " + name)
}
// Compatibility runner (prints and returns). Prefer run_min for quiet return-only.
run(mjson) { local v = me._run_min(mjson) print(me._int_to_str(v)) return v }
// New: quiet entry that returns the result without printing.
run_min(mjson) { return me._run_min(mjson) }
// Thin-mode runner (ret resolution simplified)
run_thin(mjson) {
// Inject a lightweight marker into JSON to toggle thin mode inside _run_min
if mjson.substring(0,1) == "{" {
local payload = mjson.substring(1, mjson.size())
local j2 = "{\"__thin__\":1," + payload
local v = me._run_min(j2)
print(me._int_to_str(v))
return v
}
// Fallback: no-op when input is unexpected
local v = me._run_min(mjson)
print(me._int_to_str(v))
return v
}
// helpers
_int_to_str(n) { return StringHelpers.int_to_str(n) }
_is_numeric_str(s){ if s==null {return 0} local n=s.size() if n==0 {return 0} local i=0 if s.substring(0,1)=="-" { if n==1 {return 0} i=1 } loop(i<n){ local ch=s.substring(i,i+1) if ch<"0"||ch>"9" {return 0} i=i+1 } return 1 }
_load_reg(regs,id){ local v=regs.getField(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return JsonFragBox._str_to_int(s) } return 0 }
// block helpers
_block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 }
// local copy handler
_handle_copy(seg, regs){ local dst=JsonFragBox.get_int(seg,"dst") local src=JsonFragBox.get_int(seg,"src") if dst==null || src==null {return} local v=regs.getField(""+src) regs.setField(""+dst, v) }
_run_min(mjson) {
// Normalize input as string to guarantee String methods availability
mjson = "" + mjson
// thin_mode=0: legacy heuristics互換; thin_mode=1: simplified ret
local thin_mode = 0
if mjson.indexOf("\"__thin__\":1") >= 0 { thin_mode = 1 }
// trace mode for dev: prints [DEBUG] messages
local trace = 0
if mjson.indexOf("\"__trace__\":1") >= 0 { trace = 1 }
// gc trace (miniVM専用マーカー; 環境依存を避ける)
local gc_trace = 0
if mjson.indexOf("\"__gc_trace__\":1") >= 0 { gc_trace = 1 }
// Safety first: handle simple compare→ret or const→ret in Block 0
// without allocating any runtime boxes. This avoids interpreter-level
// recursion during early startup (observed as Rust stack overflow) and
// covers our selfhost M2 minimal cases.
local b0 = JsonFragBox.block0_segment(mjson)
if b0 != "" {
// 1) Pure const→ret (only when const appears before ret and no other ops present)
if b0.indexOf("\"op\":\"compare\"") < 0 && b0.indexOf("\"op\":\"binop\"") < 0 && b0.indexOf("\"op\":\"copy\"") < 0 && b0.indexOf("\"op\":\"jump\"") < 0 && b0.indexOf("\"op\":\"branch\"") < 0 {
local ret_pos = b0.indexOf("\"op\":\"ret\"")
local const_pos = b0.indexOf("\"op\":\"const\"")
if ret_pos >= 0 and const_pos >= 0 and const_pos < ret_pos {
// Grab the first typed const value and return it
local key = "\"value\":{\"type\":\"i64\",\"value\":"
local p = StringOps.index_of_from(b0, key, 0)
if p >= 0 {
local ds = b0.substring(p + key.size(), b0.size())
// read consecutive digits
local i = 0
local out = ""
loop(true) {
local ch = ds.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out != "" { return JsonFragBox._str_to_int(out) }
}
}
}
// 2) compare→ret fast-path skipped (handled by main scanner)
// Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies
local regs = new MiniMap()
local last_cmp_dst = -1
local last_cmp_val = 0
// Optional: adopt OperatorBox for parity checks when JSON has a special marker
local op_adopt = 0
if mjson.indexOf("\"__op_adopt__\":1") >= 0 { op_adopt = 1 }
// Track last binop materialization within the current scan window
local last_binop_dst = -1
local last_binop_val = 0
local bb = 0
local prev_bb = -1
local steps = 0
local max_steps = 200000
loop(true){
steps = steps + 1
if steps > max_steps { return 0 }
local start = me._block_insts_start(mjson, bb)
me._d("[DEBUG] start="+me._int_to_str(start), trace)
if start < 0 { return 0 }
local endp = JsonCursorBox.seek_array_end(mjson, start)
me._d("[DEBUG] endp="+me._int_to_str(endp), trace)
if endp <= start { return 0 }
local inst_seg = mjson.substring(start, endp)
me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.size()), trace)
// scan objects in this block
local scan_pos = 0
local inst_count = 0
local moved = 0
loop(true){
if scan_pos >= inst_seg.size() { break }
local tup = InstructionScannerBox.next_tuple(inst_seg, scan_pos)
if tup == "" { break }
// parse "start,end,op"
local c1 = StringOps.index_of_from(tup, ",", 0)
local c2 = StringOps.index_of_from(tup, ",", c1+1)
if c1 < 0 || c2 < 0 { break }
local obj_start = JsonFragBox._str_to_int(tup.substring(0, c1))
local obj_end = JsonFragBox._str_to_int(tup.substring(c1+1, c2))
local op = tup.substring(c2+1, tup.size())
local seg = inst_seg.substring(obj_start, obj_end)
if op == null { op = "" } if op == "null" { op = "" } if op == 0 { op = "" }
if op == "" {
if seg.indexOf("op_kind") >= 0 { op = "binop" } else {
if seg.indexOf("lhs") >= 0 and seg.indexOf("rhs") >= 0 { op = "compare" } else {
if seg.indexOf("cond") >= 0 { op = "branch" } else {
if seg.indexOf("target") >= 0 { op = "jump" } else {
if seg.indexOf("src") >= 0 and seg.indexOf("dst") >= 0 { op = "copy" } else { op = "const" }
}
}
}
}
}
if op == "const" {
OpHandlersBox.handle_const(seg, regs)
}
else if op == "copy" { me._handle_copy(seg, regs) }
else if op == "binop" {
OpHandlersBox.handle_binop(seg, regs)
local bdst = JsonFragBox.get_int(seg, "dst")
if bdst != null { last_binop_dst = bdst last_binop_val = me._load_reg(regs, bdst) }
}
else if op == "compare" {
me._d("[DEBUG] compare seg=" + seg, trace)
// Safe fast-path: if this block later contains a ret of this dst,
// evaluate compare directly from current regs and return immediately.
// This avoids deeper handler chains that could recurse in edge cases.
local rec = CompareScanBox.parse(seg)
local kdst_fast = rec.get("dst")
local klhs_fast = rec.get("lhs")
local krhs_fast = rec.get("rhs")
local kcmp_fast = rec.get("kind")
// Determine if a ret exists after this compare in the same block
local tail = inst_seg.substring(obj_end, inst_seg.size())
local ridt = JsonFragBox.get_int(tail, "value")
if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast {
local a = me._load_reg(regs, klhs_fast)
local b = me._load_reg(regs, krhs_fast)
local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b)
// Store result to keep regs consistent for subsequent ops if any
regs.set("" + kdst_fast, "" + cv_fast)
last_cmp_dst = kdst_fast
last_cmp_val = cv_fast
me._d("[DEBUG] compare early return with val=" + me._int_to_str(cv_fast), trace)
return cv_fast
}
// Fallback to standard handler when early path is not applicable
OpHandlersBox.handle_compare(seg, regs)
local kdst = rec.get("dst")
me._d("[DEBUG] compare kdst=" + me._int_to_str(kdst), trace)
if kdst != null {
last_cmp_dst = kdst
last_cmp_val = me._load_reg(regs, kdst)
me._d("[DEBUG] compare last_cmp_dst=" + me._int_to_str(last_cmp_dst) + " last_cmp_val=" + me._int_to_str(last_cmp_val), trace)
me._d("[DEBUG] compare reg["+me._int_to_str(kdst)+"]=" + regs.get(""+kdst), trace)
}
// Secondary optimization: if a ret follows and targets this dst, return now
if ridt != null && kdst != null && ridt == kdst { return last_cmp_val }
}
else if op == "mir_call" {
me._handle_mir_call(seg, regs)
}
else if op == "branch" {
local c = JsonFragBox.get_int(seg, "cond")
local t = JsonFragBox.get_int(seg, "then")
local e = JsonFragBox.get_int(seg, "else")
local cv = 0
if c != null {
if c == last_cmp_dst { cv = last_cmp_val } else { cv = me._load_reg(regs, c) }
}
prev_bb = bb
if cv != 0 { bb = t } else { bb = e }
moved = 1
// GC v0 safepoint: back-edge or control transfer
using "lang/src/vm/gc/gc_hooks.hako" as GcHooks
GcHooks.safepoint()
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
scan_pos = obj_end
break
}
else if op == "jump" {
local tgt = JsonFragBox.get_int(seg, "target")
if tgt == null { return 0 }
prev_bb = bb
bb = tgt
moved = 1
// GC v0 safepoint: back-edge or control transfer
using "lang/src/vm/gc/gc_hooks.hako" as GcHooks
GcHooks.safepoint()
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
scan_pos = obj_end
break
}
else if op == "phi" {
local res = PhiDecodeBox.decode_result(seg, prev_bb)
if res.is_Ok() == 1 {
local pair = res.as_Ok()
PhiApplyBox.apply(pair.get(0), pair.get(1), regs)
}
}
else if op == "throw" {
me._tprint("[ERROR] Throw terminator encountered")
return -2
}
else if op == "ret" {
local v = JsonFragBox.get_int(seg, "value")
if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 }
if v == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local sval = "" + regs.get(""+v)
if me._is_numeric_str(sval) == 1 {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return me._load_reg(regs, v)
}
me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v))
return -1
}
// advance
inst_count = inst_count + 1
scan_pos = obj_end
}
if moved == 1 { continue }
// Prefer recent compare result when a ret exists targeting it (no recompute)
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
local rstartX = inst_seg.indexOf("\"op\":\"ret\"")
local rsegX = inst_seg.substring(rstartX, inst_seg.size())
local ridX = JsonFragBox.get_int(rsegX, "value")
if ridX != null { if ridX == last_cmp_dst { return last_cmp_val } }
}
// Fallbacks: prefer recent compare result, else resolve explicit/v1 ret if present, else first const
if last_cmp_dst >= 0 { return last_cmp_val }
// Detect explicit ret in this block and resolve
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
local rstart = inst_seg.indexOf("\"op\":\"ret\"")
local rseg = inst_seg.substring(rstart, inst_seg.size())
local rid = JsonFragBox.get_int(rseg, "value")
if rid != null {
if rid == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local rv = me._load_reg(regs, rid)
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return rv
}
}
# (typed const fallback moved above)
if false {
// Grab first two "value":{"type":"i64","value":X}
local first = ""
local second = ""
local search = inst_seg
local key = "\"value\":{\"type\":\"i64\",\"value\":"
local pos = 0
loop(true) {
local k = StringOps.index_of_from(search, key, pos)
if k < 0 { break }
local ds = search.substring(k + key.size(), search.size())
// read consecutive digits as number
local i = 0
local out = ""
loop(true) {
local ch = ds.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out != "" { if first == "" { first = out } else { second = out } }
if second != "" { break }
pos = k + key.size() + i
}
if first != "" && second != "" {
local lv = 0
local rv = 0
// simple to_i64
local i0 = 0
loop(i0 < first.size()) { lv = lv * 10 + ("0123456789".indexOf(first.substring(i0,i0+1))) i0 = i0 + 1 }
local i1 = 0
loop(i1 < second.size()) { rv = rv * 10 + ("0123456789".indexOf(second.substring(i1,i1+1))) i1 = i1 + 1 }
// cmp: parse cmp op
local cmp_key = "\"cmp\":\""
local pk = inst_seg.indexOf(cmp_key)
local cmp = "Eq"
if pk >= 0 {
local i = pk + cmp_key.size()
local j = StringOps.index_of_from(inst_seg, "\"", i)
if j > i { cmp = inst_seg.substring(i, j) }
}
local cv = CompareOpsBox.eval(cmp, lv, rv)
// ret id
local rstart4 = inst_seg.indexOf("\"op\":\"ret\"")
local rseg4 = inst_seg.substring(rstart4, inst_seg.size())
local rid4 = JsonFragBox.get_int(rseg4, "value")
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
}
return 0
}
}

View File

@ -0,0 +1,165 @@
// 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
// Prefer logical module names to avoid file-path using in strict profiles
static box OpHandlersBox {
_tprint(msg) {
// Only emit hard errors by default; avoid env dependencies in MiniVM
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
}
_is_numeric_str(s) { return StringHelpers.is_numeric_str(s) }
_str_to_int(s) { return JsonFragBox._str_to_int(s) }
// --- Safe decimal arithmetic on strings (non-negative integers only) ---
_find_int_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return null }
p = p + keypat.size()
local i = p
local out = ""
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { break }
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { i = i + 1 continue }
if ch == "-" { out = out + ch i = i + 1 continue }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out == "" { return null }
return me._str_to_int(out)
}
_find_str_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return "" }
p = p + keypat.size()
// Use substring to work around indexOf not supporting start position
local rest = seg.substring(p, seg.size())
local q = rest.indexOf("\"")
if q < 0 { return "" }
return rest.substring(0, q)
}
_find_kv_int(seg, key) {
// pattern: "key":<num>
local pat = "\"" + key + "\":"
return me._find_int_in(seg, pat)
}
_find_kv_str(seg, key) {
// pattern: "key":"..."
local pat = "\"" + key + "\":\""
return me._find_str_in(seg, pat)
}
_load_reg(regs, id) {
local v = regs.getField("" + id)
if v == null { return 0 }
local s = "" + v
if me._is_numeric_str(s) == 1 { return me._str_to_int(s) }
return 0
}
handle_const(seg, regs) {
// Prefer internal scanner to avoid resolver differences in strict profiles
local dst = me._find_kv_int(seg, "dst")
if dst == null { return }
// Try direct value first
local val = me._find_kv_int(seg, "value")
if val == null {
// Nested pattern (i64): "value":{"type":"i64","value":N}
local pat_i64 = "\"value\":{\"type\":\"i64\",\"value\":"
local p = seg.indexOf(pat_i64)
if p >= 0 {
// Minimal digit read (inline)
local i = p + pat_i64.size()
local out = ""
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out != "" { val = me._str_to_int(out) }
}
}
if val != null {
regs.setField("" + dst, val)
return
}
// String literal support: "value":{"type":"string","value":"..."}
{
local pat_str = "\"value\":{\"type\":\"string\",\"value\":\""
local ps = seg.indexOf(pat_str)
if ps >= 0 {
local start = ps + pat_str.size()
// Use escape-aware scanner to find the string end at the matching quote
local vend = JsonCursorBox.scan_string_end(seg, start - 1)
if vend > start {
local s = seg.substring(start, vend)
regs.setField("" + dst, s)
return
}
}
}
// Fallback: direct string value pattern "value":"..."
{
local pat_v = "\"value\":\""
local pv = seg.indexOf(pat_v)
if pv >= 0 {
local start = pv + pat_v.size()
local vend = JsonCursorBox.scan_string_end(seg, start - 1)
if vend > start {
local s2 = seg.substring(start, vend)
regs.setField("" + dst, s2)
return
}
}
}
// Default when nothing matched
regs.setField("" + dst, 0)
}
handle_compare(seg, regs) {
local kind = me._find_kv_str(seg, "cmp")
if kind == "" {
local sym = me._find_kv_str(seg, "operation")
if sym == "" {
print("[ERROR] Missing key: cmp")
return
}
kind = CompareOpsBox.map_symbol(sym)
}
local lhs = me._find_kv_int(seg, "lhs")
local rhs = me._find_kv_int(seg, "rhs")
local dst = me._find_kv_int(seg, "dst")
if lhs == null { print("[ERROR] Missing key: lhs") return }
if rhs == null { print("[ERROR] Missing key: rhs") return }
if dst == null { print("[ERROR] Missing key: dst") return }
local a = me._load_reg(regs, lhs)
local b = me._load_reg(regs, rhs)
local r = CompareOpsBox.eval(kind, a, b)
// Store as numeric string to simplify downstream _load_reg parsing
regs.setField("" + dst, "" + r)
}
handle_binop(seg, regs) {
// Minimal Add/Sub/Mul/Div/Mod for selfhost MiniVM tests
local kind = JsonFragBox.get_str_strict(seg, "op_kind")
local lhs = JsonFragBox.get_int_strict(seg, "lhs")
local rhs = JsonFragBox.get_int_strict(seg, "rhs")
local dst = JsonFragBox.get_int_strict(seg, "dst")
if lhs == null || rhs == null || dst == null { return }
local a = me._load_reg(regs, lhs)
local b = me._load_reg(regs, rhs)
if kind == "Add" { regs.setField(""+dst, ArithmeticBox.add_i64(a, b)) }
else if kind == "Sub" { regs.setField(""+dst, ArithmeticBox.sub_i64(a, b)) }
else if kind == "Mul" { regs.setField(""+dst, ArithmeticBox.mul_i64(a, b)) }
else if kind == "Div" { if b == 0 { regs.setField(""+dst, 0) } else { regs.setField(""+dst, a / b) } }
else if kind == "Mod" { if b == 0 { regs.setField(""+dst, 0) } else { regs.setField(""+dst, a % b) } }
}
}

View File

@ -0,0 +1,36 @@
// operator_box.hako — OperatorBox (debug/parity for MiniVM)
// Responsibility: Provide Compare/Arithmetic/Unary helpers behind a clean API
// for selfhosted (Ny) components. Intended for debugging and parity checks.
// Nonresponsibility: Being called from the Rust VM runtime (nonreentry policy).
using "lang/src/vm/boxes/arithmetic.hako" as ArithmeticBox
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
static box OperatorBox {
// Binary operators on integers (minimal set for MiniVM parity)
apply2(kind, a, b) {
if kind == "Add" { return ArithmeticBox.add_i64(a, b) }
if kind == "Sub" { return ArithmeticBox.sub_i64(a, b) }
if kind == "Mul" { return ArithmeticBox.mul_i64(a, b) }
if kind == "Div" { if b == 0 { return 0 } else { return a / b } }
if kind == "Mod" { if b == 0 { return 0 } else { return a % b } }
// Bit ops (optional)
if kind == "BitAnd" { return a & b }
if kind == "BitOr" { return a | b }
if kind == "BitXor" { return a ^ b }
if kind == "Shl" { return a << b }
if kind == "Shr" { return a >> b }
return 0
}
// Unary operators on integers/bools (minimal set)
unary(kind, a) {
if kind == "Neg" { return 0 - a }
if kind == "Not" { return (a == 0) ? 1 : 0 }
if kind == "BitNot" { return ~a }
return a
}
// Compare returns 0/1
compare(kind, a, b) { return CompareOpsBox.eval(kind, a, b) }
}

View File

@ -0,0 +1,18 @@
// phi_apply_box.hako — PhiApplyBox
// 責務: φ の適用dst レジスタに vin の値をロードして書き込む)
// 非責務: φ のデコードやスキャン(呼び出し元で行う)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box PhiApplyBox {
// 内部: 文字列が整数表現かを緩く判定
_is_numeric_str(s){ if s==null {return 0} return StringHelpers.is_numeric_str(s) }
_str_to_int(s){ return StringHelpers.to_i64(s) }
_load_reg(regs,id){ local v=regs.get(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return me._str_to_int(s) } return 0 }
apply(dst, vin, regs) {
if dst == null || vin == null { return }
local vv = me._load_reg(regs, vin)
regs.set(""+dst, vv)
}
}

View File

@ -0,0 +1,123 @@
// phi_decode_box.hako — PhiDecodeBox
// Responsibility: decode a minimal PHI instruction segment from JSON v0 text.
// Input: seg (string for one instruction object), prev_bb (i64 of predecessor bb)
// 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
static box PhiDecodeBox {
// tiny helpers to avoid raw quote/backslash sequences in source (preludesafe)
_dq() { return "\"" }
_lb() { return "{" }
_rb() { return "}" }
_lsq() { return "[" }
_rsq() { return "]" }
// Decode single incoming (legacy/simple form): {"op":"phi","dst":D,"pred":P,"value":V}
_decode_single(seg) {
local dst = JsonFragBox.get_int(seg, "dst")
local pred = JsonFragBox.get_int(seg, "pred")
local vin = JsonFragBox.get_int(seg, "value")
// single-form requires dst and value
if dst == null { return { type: "err", code: "phi:invalid-object:dst-missing" } }
if vin == null { return { type: "err", code: "phi:invalid-object:value-missing" } }
local out = new ArrayBox()
out.push(dst)
out.push(vin)
// pred may be null in simple form
return { type: "ok", pair: out, pred: pred }
}
// Decode array incoming form: {"op":"phi","dst":D, "values":[ {"pred":P,"value":V}, ... ]}
_decode_array(seg, prev_bb) {
// Find start of values array
local key = me._dq()+"values"+me._dq()+":"+me._lsq()
local p = seg.indexOf(key)
if p < 0 { return null }
local arr_br = p + key.size() - 1 // points at '['
local i = arr_br + 1
local endp = JsonCursorBox.seek_array_end(seg, arr_br)
local n = seg.size()
if endp >= 0 { n = endp }
local best_dst = JsonFragBox.get_int(seg, "dst")
if best_dst == null { return { type: "err", code: "phi:invalid-object:dst-missing" } }
// Iterate objects inside values array; pick first matching pred==prev_bb, else remember the first
local chosen_v = null
local fallback_v = null
local valid_cnt = 0
local invalid_cnt = 0
local guard = 0
local elem_cnt = 0
loop(i < n) {
guard = guard + 1
if guard > 512 { break }
// seek next object head '{'
local ob = JsonFragBox.index_of_from(seg, me._lb(), i)
if ob < 0 || ob >= n { break }
local ob_end = JsonCursorBox.seek_obj_end(seg, ob)
if ob_end < 0 || ob_end > n { invalid_cnt = invalid_cnt + 1 break }
elem_cnt = elem_cnt + 1
local obj = seg.substring(ob, ob_end+1)
// read pred/value from object segment
local pred = JsonFragBox.get_int(obj, "pred")
local vin = JsonFragBox.get_int(obj, "value")
if vin != null {
valid_cnt = valid_cnt + 1
if fallback_v == null { fallback_v = vin }
if pred != null && prev_bb != null && pred == prev_bb { chosen_v = vin i = ob_end + 1 break }
} else {
invalid_cnt = invalid_cnt + 1
}
i = ob_end + 1
}
local vin2 = chosen_v
if vin2 == null { vin2 = fallback_v }
if vin2 == null {
// values[] present but no valid entries
if elem_cnt == 0 { return { type: "err", code: "phi:no-values:empty" } }
if valid_cnt == 0 { return { type: "err", code: "phi:no-values:all-malformed" } }
return { type: "err", code: "phi:no-values" }
}
local pair = new ArrayBox()
pair.push(best_dst)
pair.push(vin2)
return { type: "ok", pair: pair, pred: prev_bb }
}
// Public: decode seg into [dst, vin] using prev_bb when provided
decode(seg, prev_bb) {
// Prefer array form; fallback to single form
local arr = me._decode_array(seg, prev_bb)
if arr != null {
if arr.get != null { return arr.get("pair") }
// If arr is an error map, propagate by returning as-is
return arr
}
local one = me._decode_single(seg)
if one != null {
if one.get != null { return one.get("pair") }
return one
}
local out = new ArrayBox()
out.push(null)
out.push(null)
return out
}
// Public: decode seg and wrap into Result.Ok([dst,vin]) or Result.Err(msg)
decode_result(seg, prev_bb) {
// Prefer array-form; if not present, fall back to single-form
local arr = me._decode_array(seg, prev_bb)
if arr != null {
local typ = arr.get("type")
if typ == "ok" { return Result.Ok(arr.get("pair")) }
return Result.Err(arr.get("code"))
}
local one = me._decode_single(seg)
local typ2 = one.get("type")
if typ2 == "ok" { return Result.Ok(one.get("pair")) }
return Result.Err(one.get("code"))
}
}

View File

@ -0,0 +1,29 @@
// ReleaseManagerBox — explicit release for identity/extern/user instances
// Responsibility: Provide a stable Nyash API to finalize boxes explicitly.
// Implementation: delegates to extern env.runtime.release (besteffort)
box ReleaseManagerBox {
release(obj) {
// Call through runtime externs; no return value
env.runtime.release(obj)
return 0
}
// Release all objects in an array (besteffort)
release_many(arr) {
if arr == null { return 0 }
// Defensive: tolerate both ArrayBox and array-like values
local n = 0
if arr.length != null { n = arr.size() } else { return 0 }
local i = 0
loop(i < n) {
local v = arr.get(i)
env.runtime.release(v)
i = i + 1
}
return 0
}
}
static box ReleaseManagerStub { main(args) { return 0 } }

View File

@ -0,0 +1,8 @@
// result_box.hako — Result / ResultBox
// 責務: 処理結果の統一表現(成功値 or エラーメッセージ)
// 使い方: Result.Ok(val) / Result.Err(msg) → ResultBox
@enum Result {
Ok(value)
Err(error)
}

View File

@ -0,0 +1,21 @@
// result_helpers.hako — Module functions for Result enum (introspection without ArrayBox)
// Responsibility: expose stable module functions for smokes and app code
// ResultHelpers.is_Ok(r: EnumBox) -> 0|1
// ResultHelpers.is_Err(r: EnumBox) -> 0|1
// Implementation detail: uses EnumBox.tag() which is supported by the router
static box ResultHelpers {
is_Ok(r) {
if r == null { return 0 }
// InstanceBox-based enum (macro): tag is stored in field "_tag"
local t = r.getField("_tag")
if t == "Ok" { return 1 }
return 0
}
is_Err(r) {
if r == null { return 0 }
local t = r.getField("_tag")
if t == "Err" { return 1 }
return 0
}
}

View File

@ -0,0 +1,47 @@
// ret_resolve_simple.hako — RetResolveSimpleBox
// Responsibility: Resolve "ret" for a single block instruction segment robustly.
// 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
static box RetResolveSimpleBox {
_to_i64(s) { return StringHelpers.to_i64(s) }
_load_reg(regs, id) {
local v = regs.get("" + id)
if v == null { return 0 }
local s = "" + v
if StringHelpers.is_numeric_str(s) == 1 { return me._to_i64(s) }
return 0
}
// Try explicit op:"ret"
_resolve_explicit(inst_seg, regs, last_cmp_dst, last_cmp_val) {
local rpos = inst_seg.indexOf("\"op\":\"ret\"")
if rpos < 0 { return null }
local rseg = inst_seg.substring(rpos, inst_seg.size())
local rid = JsonFragBox.get_int(rseg, "value")
if rid == null { return null }
if rid == last_cmp_dst { return last_cmp_val }
return me._load_reg(regs, rid)
}
// Try v1-style {"kind":"Ret","value":N} (no op key)
_resolve_kind(inst_seg, regs, last_cmp_dst, last_cmp_val) {
local kpos = inst_seg.indexOf("\"kind\":\"Ret\"")
if kpos < 0 { return null }
local kseg = inst_seg.substring(kpos, inst_seg.size())
local rid = JsonFragBox.get_int(kseg, "value")
if rid == null { return null }
if rid == last_cmp_dst { return last_cmp_val }
return me._load_reg(regs, rid)
}
resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) {
if inst_seg == null { return null }
local v = me._resolve_explicit(inst_seg, regs, last_cmp_dst, last_cmp_val)
if v != null { return v }
return me._resolve_kind(inst_seg, regs, last_cmp_dst, last_cmp_val)
}
}
static box RetResolveSimpleMain { main(args){ return 0 } }

View File

@ -0,0 +1,44 @@
// RuneHostBox — thin placeholder for future rune integration (selfhost)
// Responsibility: provide a stable interface; default is disabled (FailFast)
static box RuneHostBox {
// Return 1 when rune is available in this build (default: 0)
is_available() { return 0 }
// Provider name (mock/wasm/…); default none
provider_name() { return "none" }
// Evaluate a small rune 'code' with optional context map.
// Calls extern nyrt.rune.eval; when disabled it returns -1 (handled by extern side).
eval(code, ctx) {
// Keep behavior explicit and sideeffect free (no prints here).
// Delegate to extern; context is reserved for future providers.
local _ = ctx
// Try extern via env facade (builder maps to nyrt.rune.eval) — may be unavailable in some profiles
// Fallback: tiny mock evaluator (supports "A+B" and integer literal)
// Note: no try/catch yet; keep deterministic and sideeffect free
// Extern path (best-effort)
// Disabled until builder guarantees env facade in all profiles
// local rc = env.rune.eval(code)
// return rc
// Fallback mock: only support literal "1+2" and simple integers for now
if code == "1+2" { return 3 }
// else try simple integer literal (only digits)
local i = 0
local ok = 1
loop(i < code.size()) { local ch = code.substring(i,i+1) if !(ch == "0" or ch == "1" or ch == "2" or ch == "3" or ch == "4" or ch == "5" or ch == "6" or ch == "7" or ch == "8" or ch == "9") { ok = 0 } i = i + 1 }
if ok == 1 {
// very rough: build integer by repeated add (limited)
local n = 0
i = 0
loop(i < code.size()) {
n = n * 10
local ch2 = code.substring(i,i+1)
if ch2 == "1" { n = n + 1 } else { if ch2 == "2" { n = n + 2 } else { if ch2 == "3" { n = n + 3 } else { if ch2 == "4" { n = n + 4 } else { if ch2 == "5" { n = n + 5 } else { if ch2 == "6" { n = n + 6 } else { if ch2 == "7" { n = n + 7 } else { if ch2 == "8" { n = n + 8 } else { if ch2 == "9" { n = n + 9 } } } } } } } }
i = i + 1
}
return n
}
return -2
}
}

View File

@ -0,0 +1,39 @@
// scanner_box.hako — ScannerBox
// 責務: 文字列の安全な逐次走査(境界チェックと前進保証のカプセル化)
// 非責務: JSON構文解析や高レベルのトークナイズ上位箱に委譲
box ScannerBox {
_src: StringBox
_pos: IntegerBox
_max: IntegerBox
birth(source) {
me._src = source
me._pos = 0
me._max = 0
if source != null { me._max = source.size() }
}
at_end() {
if me._pos >= me._max { return 1 }
return 0
}
pos() { return me._pos }
// 先読み(前進しない)。末尾なら null
peek() {
if me._pos >= me._max { return null }
return me._src.substring(me._pos, me._pos + 1)
}
// 1文字進めて、直前の文字を返す。末尾なら nullFailFastは呼び出し側で制御
advance() {
if me._pos >= me._max { return null }
local old = me._pos
me._pos = me._pos + 1
if me._pos <= old { return null }
return me._src.substring(old, old + 1)
}
}

View File

@ -0,0 +1,170 @@
// SeamInspector — analyze inlined code seam and duplicates
// Usage: import and call report(text) or analyze_dump_file(path)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
static box SeamInspector {
_int_to_str(n) { return StringHelpers.int_to_str(n) }
_str_to_int(s) { return StringHelpers.to_i64(s) }
_brace_delta_ignoring_strings(text, start, end) {
@i = start
@n = end
@delta = 0
loop (i < n) {
@ch = call("String.substring/2", text, i, i+1)
if ch == "\"" {
i = i + 1
loop (i < n) {
@c = text.substring(i, i+1)
if c == "\\" { i = i + 2 continue }
if c == "\"" { i = i + 1 break }
i = i + 1
}
continue
}
if ch == "{" { delta = delta + 1 }
if ch == "}" { delta = delta - 1 }
i = i + 1
}
return delta
}
_scan_boxes(text) {
@i = 0
@n = text.size()
@res = new ArrayBox()
@tok = "static box "
loop (i < n) {
@p = StringOps.index_of_from(text, tok, i)
if p < 0 { break }
@j = p + tok.size()
@name = ""
loop (j < n) {
@c = text.substring(j, j+1)
if c == "_" { name = name + c j = j + 1 continue }
if c == "0" or c == "1" or c == "2" or c == "3" or c == "4" or c == "5" or c == "6" or c == "7" or c == "8" or c == "9" { name = name + c j = j + 1 continue }
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c j = j + 1 continue }
break
}
loop (j < n) { @c2 = text.substring(j, j+1) if c2 == "{" { break } j = j + 1 }
@end = MiniVmScan.find_balanced_object_end(text, j)
if end < 0 { end = j }
@obj = map({})
obj.set("name", name)
obj.set("start", _int_to_str(p))
obj.set("end", _int_to_str(end))
res.push(obj)
i = end + 1
}
return res
}
_report_duplicate_boxes(text) {
@boxes = _scan_boxes(text)
@cnt = map({})
@names = new ArrayBox()
@i = 0
loop (i < boxes.size()) {
@name = boxes.get(i).get("name")
@cur = cnt.get(name)
if cur == null { cnt.set(name, "1") names.push(name) } else { cnt.set(name, _int_to_str(_str_to_int(cur) + 1)) }
i = i + 1
}
@j = 0
loop (j < names.size()) {
@k = names.get(j)
@v = cnt.get(k)
if _str_to_int(v) > 1 { print("dup_box " + k + " x" + v) }
j = j + 1
}
return 0
}
_report_duplicate_functions_in_box(text, box_name) {
@boxes = _scan_boxes(text)
@i = 0
@fnmap = map({})
@fnames = new ArrayBox()
loop (i < boxes.size()) {
@b = boxes.get(i)
if b.get("name") == box_name {
@s = _str_to_int(b.get("start"))
@e = _str_to_int(b.get("end"))
@j = s
loop (j < e) {
@k = j
loop (k < e) {
@ch = text.substring(k, k+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { k = k + 1 continue }
break
}
if k >= e { break }
@name = ""
@p = k
@c0 = text.substring(p, p+1)
if (c0 >= "A" and c0 <= "Z") or (c0 >= "a" and c0 <= "z") or c0 == "_" {
loop (p < e) {
@c = text.substring(p, p+1)
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or c == "_" { name = name + c p = p + 1 continue }
break
}
if text.substring(p, p+1) == "(" {
@d = 0
@r = p
loop (r < e) {
@cc = text.substring(r, r+1)
if cc == "(" { d = d + 1 r = r + 1 continue }
if cc == ")" { d = d - 1 r = r + 1 if d <= 0 { break } continue }
if cc == "\"" {
r = r + 1
loop (r < e) {
@c2 = text.substring(r, r+1)
if c2 == "\\" { r = r + 2 continue }
if c2 == "\"" { r = r + 1 break }
r = r + 1
}
continue
}
r = r + 1
}
loop (r < e) { @ws = text.substring(r, r+1) if ws == " " or ws == "\t" or ws == "\r" or ws == "\n" { r = r + 1 continue } break }
if r < e and text.substring(r, r+1) == "{" {
@cur = fnmap.get(name)
if cur == null { fnmap.set(name, "1") fnames.push(name) } else { fnmap.set(name, _int_to_str(_str_to_int(cur) + 1)) }
}
}
}
@nl = StringOps.index_of_from(text, "\n", k+1)
if nl < 0 || nl > e { break }
j = nl + 1
}
break
}
i = i + 1
}
@x = 0
loop (x < fnames.size()) {
@nm = fnames.get(x)
@ct = fnmap.get(nm)
if _str_to_int(ct) > 1 { print("dup_fn " + box_name + "." + nm + " x" + ct) }
x = x + 1
}
return 0
}
report(text) {
@m = StringOps.index_of_from(text, "static box Main {", 0)
@delta = -9999
if m > 0 { delta = _brace_delta_ignoring_strings(text, 0, m) }
print("prelude_brace_delta=" + _int_to_str(delta))
_report_duplicate_boxes(text)
_report_duplicate_functions_in_box(text, "MiniVmPrints")
return 0
}
analyze_dump_file(path) {
@fb = new FileBox()
@f = fb.open(path)
if f == null { print("warn: cannot open " + path) return 0 }
@text = f.read()
f.close()
return me.report(text)
}
}

View File

@ -0,0 +1,67 @@
// step_runner.hako — MiniVM JSON v0 ステップ観測用の軽量箱(実行はしない)
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
static box StepRunnerBox {
// 文字列ヘルパ
// 文字列ヘルパJsonCursorBox に委譲)
_index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
// Delegate digit scanning to JsonCursorBox for consistency
_read_digits(text, pos) { return JsonCursorBox.digits_from(text, pos) }
_int(s) { local t = "" + s if t == "" { return 0 } local i = 0 local neg = 0 if t.substring(0,1) == "-" { neg = 1 i = 1 } local acc = 0 loop(i < t.size()) { local ch = t.substring(i, i+1) if ch < "0" || ch > "9" { break } acc = acc * 10 + (ch == "0" ? 0 : ch == "1" ? 1 : ch == "2" ? 2 : ch == "3" ? 3 : ch == "4" ? 4 : ch == "5" ? 5 : ch == "6" ? 6 : ch == "7" ? 7 : ch == "8" ? 8 : 9) i = i + 1 } if neg == 1 { acc = 0 - acc } return acc }
// block 0 の instructions セグメント抽出
_block0_segment(mjson) {
local key = "\"instructions\":["
local k = me._index_of_from(mjson, key, 0)
if k < 0 { return "" }
local lb = k + key.size() - 1
// Delegate to JsonScanBox for robust end detection (escape-aware)
local rb = JsonCursorBox.seek_array_end(mjson, lb)
if rb < 0 { return "" }
return mjson.substring(lb + 1, rb)
}
// compare の (lhs,rhs,kind,dst) を抽出
parse_compare(seg) {
local p = JsonCursorBox.index_of_from(seg, "\"op\":\"compare\"", 0)
if p < 0 { return map({}) }
local lhs = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"lhs\":", p) + 6))
local rhs = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"rhs\":", p) + 6))
local dst = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"dst\":", p) + 6))
// kind
local kpos = JsonCursorBox.index_of_from(seg, "\"cmp\":\"", p)
local kend = me._index_of_from(seg, "\"", kpos + 6)
local kind = seg.substring(kpos + 6, kend)
return map({ lhs: lhs, rhs: rhs, dst: dst, kind: kind })
}
// branch の cond/then/else を抽出
parse_branch(seg) {
local p = JsonCursorBox.index_of_from(seg, "\"op\":\"branch\"", 0)
if p < 0 { return map({}) }
local cond = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"cond\":", p) + 7))
local then_id = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"then\":", p) + 7))
local else_id = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"else\":", p) + 7))
return map({ cond: cond, then: then_id, else: else_id })
}
// compare を評価し cond==dst のときに bool を返す
eval_branch_bool(mjson) {
local seg = me._block0_segment(mjson)
if seg == "" { return 0 }
local cmp = me.parse_compare(seg)
local br = me.parse_branch(seg)
if cmp.get == null || br.get == null { return 0 }
local kind = "" + cmp.get("kind")
local lhs = cmp.get("lhs")
local rhs = cmp.get("rhs")
local dst = cmp.get("dst")
local cond = br.get("cond")
if cond != dst { return 0 } // 最小仕様: cond は直前 compare の dst
local r = CompareOpsBox.eval(kind, lhs, rhs)
return r
}
main(args) { return 0 }
}

View File

@ -0,0 +1,32 @@
// vm_kernel_box.nyash — NYABI Kernel (skeleton, dev-only; not wired)
// Scope: Provide policy/decision helpers behind an explicit OFF toggle.
// Notes: This box is not referenced by the VM by default.
static box VmKernelBox {
// Report version and supported features.
caps() {
// v0 draft: features are informative only.
return "{\"version\":0,\"features\":[\"policy\"]}"
}
// Decide stringify strategy for a given type.
// Returns: "direct" | "rewrite_stringify" | "fallback"
stringify_policy(typeName) {
if typeName == "VoidBox" { return "rewrite_stringify" }
return "fallback"
}
// Decide equals strategy for two types.
// Returns: "object" | "value" | "fallback"
equals_policy(lhsType, rhsType) {
if lhsType == rhsType { return "value" }
return "fallback"
}
// Batch resolve method dispatch plans.
// Input/Output via tiny JSON strings (draft). Returns "{\"plans\":[]}" for now.
resolve_method_batch(reqs_json) {
return "{\"plans\":[]}"
}
}

View File

@ -0,0 +1,60 @@
// Self-contained dev smoke for FunctionCall empty-args
// Goal: echo() -> empty line, itoa() -> 0
static box MiniVm {
// simple substring find from position
index_of_from(hay, needle, pos) {
if pos < 0 { pos = 0 }
if pos >= hay.size() { return -1 }
local tail = hay.substring(pos, hay.size())
local rel = tail.indexOf(needle)
if rel < 0 { return -1 } else { return pos + rel }
}
// collect only FunctionCall with empty arguments [] for echo/itoa
collect_prints(json) {
local out = new ArrayBox()
local pos = 0
local guard = 0
loop (true) {
guard = guard + 1
if guard > 16 { break }
local k_fc = "\"kind\":\"FunctionCall\""
local p = index_of_from(json, k_fc, pos)
if p < 0 { break }
// name
local k_n = "\"name\":\""
local np = index_of_from(json, k_n, p)
if np < 0 { break }
local ni = np + k_n.size()
local nj = index_of_from(json, "\"", ni)
if nj < 0 { break }
local fname = json.substring(ni, nj)
// args [] detection
local k_a = "\"arguments\":["
local ap = index_of_from(json, k_a, nj)
if ap < 0 { break }
local rb = index_of_from(json, "]", ap)
if rb < 0 { break }
// no content between '[' and ']'
if rb == ap + k_a.size() {
if fname == "echo" { out.push("") }
if fname == "itoa" { out.push("0") }
}
pos = rb + 1
}
return out
}
}
static box Main {
main(args) {
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
local arr = new MiniVm().collect_prints(json)
local i = 0
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
return 0
}
}
// migrated from .nyash to .hako (branding)

View File

@ -0,0 +1,14 @@
using "lang/src/vm/boxes/mini_vm_core.hako" as MiniVm
static box Main {
main(args) {
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
local arr = new MiniVm().collect_prints(json)
print("DEBUG: arr.size=" + arr.size())
local i = 0
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
return 0
}
}
// migrated from .nyash to .hako (branding)

View File

@ -0,0 +1,23 @@
// Minimal self-contained eval (no using): collect the first Print(Literal int) and print it
static box Main {
main(args) {
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
// naive collect: first Print of Literal int
local ki = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local p = json.indexOf(ki)
if p >= 0 {
local i = p + ki.size()
local j = i
loop (true) {
local ch = json.substring(j, j+1)
if ch == "" { break }
if ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" { j = j + 1 continue }
break
}
local digits = json.substring(i, j)
if digits { print(digits) }
}
return 0
}
}
// migrated from .nyash to .hako (branding)

View File

@ -0,0 +1,183 @@
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
static box Main {
// --- minimal helpers ---
read_digits(s, pos) { return StringHelpers.read_digits(s, pos) }
// --- core: collect Print outputs in order ---
collect_prints(json) {
local out = new ArrayBox()
local pos = 0
local guard = 0
local k_print = "\"kind\":\"Print\""
loop (true) {
guard = guard + 1
if guard > 200 { break }
local p = StringOps.index_of_from(json, k_print, pos)
if p < 0 { break }
// bound current Print slice as [current_print, next_print)
local obj_start = p
local next_p = StringOps.index_of_from(json, k_print, p + k_print.size())
local obj_end = json.size()
if next_p > 0 { obj_end = next_p }
// 1) BinaryOp(int '+' int)
{
local k_expr = "\"expression\":{"
local epos = StringOps.index_of_from(json, k_expr, obj_start)
if epos > 0 { if epos < obj_end {
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = StringOps.index_of_from(json, k_bo, epos)
if bpos > 0 { if bpos < obj_end {
local opok = StringOps.index_of_from(json, "\"operator\":\"+\"", bpos)
if opok > 0 {
// typed left/right literal ints
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local lp = StringOps.index_of_from(json, k_l, bpos)
if lp > 0 { if lp < obj_end {
local ld = me.read_digits(json, lp + k_l.size())
if ld != "" {
local rp = StringOps.index_of_from(json, k_r, lp + k_l.size())
if rp > 0 { if rp < obj_end {
local rd = me.read_digits(json, rp + k_r.size())
if rd != "" { out.push(_int_to_str(_str_to_int(ld) + _str_to_int(rd))) pos = p + k_print.size() continue }
}}
}
}}
// fallback: scan two successive '"value":' digits within expression bounds
local k_v = "\"value\":"
local v1 = StringOps.index_of_from(json, k_v, epos)
if v1 > 0 { if v1 < obj_end {
local d1 = me.read_digits(json, v1 + k_v.size())
if d1 != "" {
local v2 = StringOps.index_of_from(json, k_v, v1 + k_v.size())
if v2 > 0 { if v2 < obj_end {
local d2 = me.read_digits(json, v2 + k_v.size())
if d2 != "" { out.push(_int_to_str(_str_to_int(d1) + _str_to_int(d2))) pos = p + k_print.size() continue }
}}
}
}}
}
}}
}}
}
// 2) Compare(lhs/rhs ints): prints 1/0
{
local k_cp = "\"kind\":\"Compare\""
local cpos = StringOps.index_of_from(json, k_cp, obj_start)
if cpos > 0 { if cpos < obj_end {
local k_op = "\"operation\":\""
local opos = StringOps.index_of_from(json, k_op, cpos)
if opos > 0 { if opos < obj_end {
local oi = opos + k_op.size()
local oj = StringOps.index_of_from(json, "\"", oi)
if oj > 0 { if oj <= obj_end {
local op = json.substring(oi, oj)
local k_v = "\"value\":"
local lhs_v = StringOps.index_of_from(json, k_v, oj)
if lhs_v > 0 { if lhs_v < obj_end {
local la = me.read_digits(json, lhs_v + k_v.size())
if la != "" {
local rhs_v = StringOps.index_of_from(json, k_v, lhs_v + k_v.size())
if rhs_v > 0 { if rhs_v < obj_end {
local rb = me.read_digits(json, rhs_v + k_v.size())
if rb != "" {
local ai = _str_to_int(la)
local bi = _str_to_int(rb)
local res = 0
if op == "<" { if ai < bi { res = 1 } }
if op == "==" { if ai == bi { res = 1 } }
if op == "<=" { if ai <= bi { res = 1 } }
if op == ">" { if ai > bi { res = 1 } }
if op == ">=" { if ai >= bi { res = 1 } }
if op == "!=" { if ai != bi { res = 1 } }
out.push(_int_to_str(res))
pos = p + k_print.size()
continue
}
}}
}
}}
}}
}}
}}
}
// 3) FunctionCall echo/itoa (single literal arg)
{
local k_fc = "\"kind\":\"FunctionCall\""
local fcp = StringOps.index_of_from(json, k_fc, obj_start)
if fcp > 0 { if fcp < obj_end {
local kn = "\"name\":\""
local np = StringOps.index_of_from(json, kn, fcp)
if np > 0 { if np < obj_end {
local ni = np + kn.size()
local nj = StringOps.index_of_from(json, "\"", ni)
if nj > 0 { if nj <= obj_end {
local fname = json.substring(ni, nj)
local ka = "\"arguments\":["
local ap = StringOps.index_of_from(json, ka, nj)
if ap > 0 { if ap < obj_end {
// string arg
local ks = "\"type\":\"string\",\"value\":\""
local ps = StringOps.index_of_from(json, ks, ap)
if ps > 0 { if ps < obj_end {
local si = ps + ks.size()
local sj = StringOps.index_of_from(json, "\"", si)
if sj > 0 { if sj <= obj_end {
local sval = json.substring(si, sj)
if fname == "echo" { out.push(sval) pos = p + k_print.size() continue }
}}
}}
// int arg
local ki = "\"type\":\"int\",\"value\":"
local pi = StringOps.index_of_from(json, ki, ap)
if pi > 0 { if pi < obj_end {
local ival = me.read_digits(json, pi + ki.size())
if ival != "" { if fname == "itoa" { out.push(ival) pos = p + k_print.size() continue } else { if fname == "echo" { out.push(ival) pos = p + k_print.size() continue } } }
}}
}}
}}
}}
}}
}
// 4) Literal string
{
local ks = "\"type\":\"string\",\"value\":\""
local ps = StringOps.index_of_from(json, ks, obj_start)
if ps > 0 { if ps < obj_end {
local si = ps + ks.size()
local sj = StringOps.index_of_from(json, "\"", si)
if sj > 0 { if sj <= obj_end { out.push(json.substring(si, sj)) pos = p + k_print.size() continue }}
}}
}
// 5) Literal int
{
local ki = "\"type\":\"int\",\"value\":"
local pi = StringOps.index_of_from(json, ki, obj_start)
if pi > 0 { if pi < obj_end {
local digits = me.read_digits(json, pi + ki.size())
if digits != "" { out.push(digits) pos = p + k_print.size() continue }
}}
}
// Unknown: skip ahead
pos = p + k_print.size()
if pos <= p { pos = p + 1 }
}
return out
}
// int<->str delegation to StringHelpers
_str_to_int(s) { return StringHelpers.to_i64(s) }
_int_to_str(n) { return StringHelpers.int_to_str(n) }
main(args) {
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
local arr = collect_prints(json)
local i = 0
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
return 0
}
}

View File

@ -0,0 +1,12 @@
using "lang/src/vm/boxes/mini_vm_core.hako" as MiniVm
using "lang/src/vm/boxes/mini_vm_prints.hako" as MiniVmPrints
static box Main {
main(args) {
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
new MiniVmPrints().print_prints_in_slice(json, 0, json.size())
return 0
}
}
// migrated from .nyash to .hako (branding)

View File

@ -0,0 +1,19 @@
using "lang/src/vm/mini_vm_lib.hako" as MiniVm
static box Main {
main(args) {
// Mixed shapes (loader-centric)
// 1) echo() with empty args -> ""
// 2) string literal with quotes inside
// 3) itoa() with empty args -> "0"
// 4) BinaryOp 8+9
// 5) int literal 4
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"C\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":8}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":9}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}]}"
local arr = new MiniVm().collect_prints(json)
local i = 0
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
return 0
}
}
// migrated from .nyash to .hako (branding)

View File

@ -0,0 +1,17 @@
VM Core (Skeleton) — Phase 20.12b
Responsibility
- Provide a minimal, centralized execution core for MIR(JSON v0).
- First batch: value/state/extern_iface/json_v0_reader/dispatcher + ops {const, binop, ret}.
- Goal: engines (hakorune, mini) become thin wrappers that call Core.
Scope and Guards
- This is a skeleton to establish structure and entry points.
- Parsing uses simple, escape-aware cursors from shared json cursors.
- Fail-Fast: unknown ops or malformed JSON return -1 and print a stable error.
Migration Notes
- Existing hakorune-vm remains the authoritative engine while Core grows.
- Engines under engines/{hakorune,mini} may delegate to Core as it matures.
- Re-exports or aliases can be added to bridge incrementally without broad refactors.

View File

@ -0,0 +1,201 @@
// dispatcher.hako — NyVmDispatcher (skeleton)
// Minimal linear executor for a single function's first block.
using "lang/src/vm/core/state.hako" as NyVmState
using "lang/src/vm/core/json_v0_reader.hako" as NyVmJsonV0Reader
using "lang/src/vm/core/ops/const.hako" as NyVmOpConst
using "lang/src/vm/core/ops/binop.hako" as NyVmOpBinOp
using "lang/src/vm/core/ops/ret.hako" as NyVmOpRet
using "lang/src/vm/core/ops/compare.hako" as NyVmOpCompare
using "lang/src/vm/core/ops/branch.hako" as NyVmOpBranch
using "lang/src/vm/core/ops/jump.hako" as NyVmOpJump
using "lang/src/vm/core/ops/phi.hako" as NyVmOpPhi
using "lang/src/vm/core/ops/copy.hako" as NyVmOpCopy
using "lang/src/vm/core/ops/unary.hako" as NyVmOpUnary
using "lang/src/vm/core/ops/typeop.hako" as NyVmOpTypeOp
using "lang/src/vm/core/ops/load.hako" as NyVmOpLoad
using "lang/src/vm/core/ops/store.hako" as NyVmOpStore
using "lang/src/vm/core/ops/mir_call.hako" as NyVmOpMirCall
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box NyVmDispatcher {
// High-level entry: run a MIR(JSON v0) string and return numeric result.
run(json) {
if json == null || json == "" { print("[core] empty json") return -1 }
// Locate first function and build block map
local f = NyVmJsonV0Reader.first_function(json)
if f == "" { print("[core] function not found") return -1 }
local block_map = NyVmJsonV0Reader.build_block_map(f)
// Determine entry block id: prefer function.entry, fallback to first block id, else 0
local entry = NyVmJsonV0Reader.read_entry_id(f)
if entry < 0 {
local first_b = NyVmJsonV0Reader.first_block(f)
if first_b == "" { print("[core] no blocks") return -1 }
entry = NyVmJsonV0Reader.read_block_id(first_b)
if entry < 0 { entry = 0 }
}
// Initialize state and control flow
local st = NyVmState.make()
// Initialize last error tag slot (used to surface stable tags as return)
st.set("last_error_tag", "")
// Shared memory map for load/store/mir_call ops
local mem = st.get("mem")
local current_bb = entry
local predecessor = -1
// Iteration cap (ENV override: HAKO_CORE_MAX_ITERS | NYASH_CORE_MAX_ITERS)
local max_iter = 10000
{
local v = call("env.local.get/1", "HAKO_CORE_MAX_ITERS")
if v == null || v == "" { v = call("env.local.get/1", "NYASH_CORE_MAX_ITERS") }
if v != null && v != "" {
local n = StringHelpers.to_i64(v)
if n > 0 { max_iter = n }
}
}
local iter = 0
loop (iter < max_iter) {
iter = iter + 1
local bjson = block_map.get("" + current_bb)
if bjson == null { print("[core] block not found: " + ("" + current_bb)) return -1 }
// Execute instructions in this block
local insts = NyVmJsonV0Reader.block_instructions_json(bjson)
if insts == "" { print("[core] empty instructions at bb" + ("" + current_bb)) return -1 }
// First pass: apply phi nodes
{
local pos0 = 0
loop(true) {
local it0 = NyVmJsonV0Reader.next_instruction(insts, pos0)
if it0 == null { break }
if it0.get != null && it0.get("error") != null { print("[core] json error: " + it0.get("error")) return -1 }
local obj0 = it0.get != null ? it0.get("obj") : null
local next0 = it0.get != null ? it0.get("next") : null
if obj0 == null || obj0 == "" { break }
if obj0.indexOf("\"op\":\"phi\"") >= 0 {
local rc_phi = NyVmOpPhi.handle(obj0, st, predecessor)
if rc_phi < 0 { return rc_phi }
if next0 == null { break }
pos0 = next0
continue
}
break
}
}
// Second pass: execute remaining instructions until a terminator
local pos = 0
local next_bb = null
loop(true) {
local it = NyVmJsonV0Reader.next_instruction(insts, pos)
if it == null { break }
if it.get != null && it.get("error") != null { print("[core] json error: " + it.get("error")) return -1 }
local obj = it.get != null ? it.get("obj") : null
local nextp = it.get != null ? it.get("next") : null
if obj == null || obj == "" { break }
if obj.indexOf("\"op\":\"phi\"") >= 0 { pos = nextp != null ? nextp : pos; continue }
if obj.indexOf("\"op\":\"const\"") >= 0 {
local rc = NyVmOpConst.handle(obj, st)
if rc < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc
}
} else if obj.indexOf("\"op\":\"binop\"") >= 0 {
local rc2 = NyVmOpBinOp.handle(obj, st)
if rc2 < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc2
}
} else if obj.indexOf("\"op\":\"copy\"") >= 0 {
local rc_c = NyVmOpCopy.handle(obj, st)
if rc_c < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_c
}
} else if obj.indexOf("\"op\":\"unop\"") >= 0 || obj.indexOf("\"op\":\"unaryop\"") >= 0 {
local rc_u = NyVmOpUnary.handle(obj, st)
if rc_u < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_u
}
} else if obj.indexOf("\"op\":\"compare\"") >= 0 {
local rc3 = NyVmOpCompare.handle(obj, st)
if rc3 < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc3
}
} else if obj.indexOf("\"op\":\"branch\"") >= 0 {
local nb = NyVmOpBranch.handle(obj, st)
if nb < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return nb
}
next_bb = nb
break
} else if obj.indexOf("\"op\":\"jump\"") >= 0 {
local nb2 = NyVmOpJump.handle(obj)
if nb2 < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return nb2
}
next_bb = nb2
break
} else if obj.indexOf("\"op\":\"typeop\"") >= 0 {
local rc_t = NyVmOpTypeOp.handle(obj, st)
if rc_t < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_t
}
} else if obj.indexOf("\"op\":\"load\"") >= 0 {
local rc_l = NyVmOpLoad.handle(obj, st, mem)
if rc_l < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_l
}
} else if obj.indexOf("\"op\":\"store\"") >= 0 {
local rc_s = NyVmOpStore.handle(obj, st, mem)
if rc_s < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_s
}
} else if obj.indexOf("\"op\":\"mir_call\"") >= 0 {
local rc_m = NyVmOpMirCall.handle(obj, st)
if rc_m < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rc_m
}
} else if obj.indexOf("\"op\":\"ret\"") >= 0 {
local rrc = NyVmOpRet.handle(obj, st)
if rrc < 0 {
local tag = st.get("last_error_tag")
if tag != null && tag != "" { return tag }
return rrc
}
return rrc
} else {
print("[core] unsupported op: " + obj)
return -1
}
if nextp == null { break }
pos = nextp
}
if next_bb == null { return 0 }
predecessor = current_bb
current_bb = next_bb
}
print("[core] max iterations reached")
return -1
}
}
static box NyVmDispatcherMain { main(args){ return 0 } }

View File

@ -0,0 +1,14 @@
// extern_iface.hako — NyVmExternIface (skeleton)
// Minimal stub for extern calls registration/dispatch (Phase1: no-op)
static box NyVmExternIface {
// Register extern handler (placeholder)
register(name, handler_box) { return 0 }
// Call extern; return error (FailFast) until implemented
call(name, args) {
print("[core/extern] unsupported extern: " + name)
return -1
}
}
static box NyVmExternIfaceMain { main(args){ return 0 } }

View File

@ -0,0 +1,141 @@
// json_v0_reader.hako — NyVmJsonV0Reader (skeleton)
// Escape-aware minimal scanners to locate the first function's first block instructions.
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box NyVmJsonV0Reader {
_skip_ws(s, i) {
local n = s.size()
loop(i < n) {
local ch = s.substring(i,i+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
break
}
return i
}
// Return substring for the first function object
first_function(json) {
local p = JsonCursorBox.find_key_dual(json, "\"functions\":[", r#"\"functions\":\["#, 0)
if p < 0 { return "" }
local lb = json.indexOf("[", p)
if lb < 0 { return "" }
local i = me._skip_ws(json, lb+1)
if json.substring(i,i+1) != "{" { return "" }
local end = JsonCursorBox.seek_obj_end(json, i)
if end < 0 { return "" }
return json.substring(i, end+1)
}
// Return substring for the first block object
first_block(func_json) {
local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", r#"\"blocks\":\["#, 0)
if p < 0 { return "" }
local lb = func_json.indexOf("[", p)
if lb < 0 { return "" }
local i = me._skip_ws(func_json, lb+1)
if func_json.substring(i,i+1) != "{" { return "" }
local end = JsonCursorBox.seek_obj_end(func_json, i)
if end < 0 { return "" }
return func_json.substring(i, end+1)
}
// Return substring for the instructions array content (without the surrounding brackets)
block_instructions_json(block_json) {
local p = JsonCursorBox.find_key_dual(block_json, "\"instructions\":[", r#"\"instructions\":\["#, 0)
if p < 0 { return "" }
local lb = block_json.indexOf("[", p)
if lb < 0 { return "" }
local rb = JsonCursorBox.seek_array_end(block_json, lb)
if rb < 0 { return "" }
return block_json.substring(lb+1, rb)
}
// Read function entry id if present; returns -1 when missing
read_entry_id(func_json) {
local p = JsonCursorBox.find_key_dual(func_json, "\"entry\":", r#"\"entry\":"#, 0)
if p < 0 { return -1 }
p = func_json.indexOf(":", p)
if p < 0 { return -1 }
p = p + 1
// skip ws
loop(true) { local ch = func_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(func_json, p)
if digits == "" { return -1 }
return StringHelpers.to_i64(digits)
}
// Parse block id from a block JSON object; returns -1 on failure
read_block_id(block_json) {
local p = JsonCursorBox.find_key_dual(block_json, "\"id\":", r#"\"id\":"#, 0)
if p < 0 { return -1 }
p = block_json.indexOf(":", p)
if p < 0 { return -1 }
p = p + 1
// skip ws
loop(true) { local ch = block_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(block_json, p)
if digits == "" { return -1 }
return StringHelpers.to_i64(digits)
}
// Build a map: key stringified id → block_json
build_block_map(func_json) {
local out = new MapBox()
// locate blocks array
local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", r#"\"blocks\":\["#, 0)
if p < 0 { return out }
local lb = func_json.indexOf("[", p)
if lb < 0 { return out }
local rb = JsonCursorBox.seek_array_end(func_json, lb)
if rb < 0 { return out }
local arr = func_json.substring(lb+1, rb)
// iterate objects
local pos = 0
local n = arr.size()
loop (pos < n) {
// skip ws/commas
loop (pos < n) {
local ch = arr.substring(pos,pos+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { pos = pos + 1 continue }
break
}
if pos >= n { break }
if arr.substring(pos,pos+1) != "{" { break }
local end = JsonCursorBox.seek_obj_end(arr, pos)
if end < 0 { break }
local blk = arr.substring(pos, end+1)
local id = me.read_block_id(blk)
if id >= 0 {
out.set("" + id, blk)
}
pos = end + 1
}
return out
}
// Iterate instructions: return map { obj, next } or Err string when malformed; when finished, returns { obj:null, next:len }
next_instruction(insts_json, pos) {
local n = insts_json.size()
local i = me._skip_ws(insts_json, pos)
if i >= n { return new MapBox() } // obj=null, next=len (empty map)
// Skip trailing commas or ws
loop (i < n) {
local ch = insts_json.substring(i,i+1)
if ch == "," || ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
break
}
if i >= n { return new MapBox() }
if insts_json.substring(i,i+1) != "{" { return { "error": "instruction_not_object" } }
local end = JsonCursorBox.seek_obj_end(insts_json, i)
if end < 0 { return { "error": "instruction_obj_unclosed" } }
local obj = insts_json.substring(i, end+1)
local out = new MapBox()
out.set("obj", obj)
out.set("next", end+1)
return out
}
}
static box NyVmJsonV0ReaderMain { main(args){ return 0 } }

View File

@ -0,0 +1,69 @@
// ops/binop.hako — NyVmOpBinOp (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpBinOp {
_read_int_field(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) {
local ch = inst_json.substring(p,p+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue }
break
}
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
_read_str_field(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return "" }
p = inst_json.indexOf(":", p)
if p < 0 { return "" }
p = p + 1
loop(true) {
local ch = inst_json.substring(p,p+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue }
break
}
if inst_json.substring(p,p+1) != "\"" { return "" }
local end = JsonCursorBox.scan_string_end(inst_json, p)
if end < 0 { return "" }
local val = inst_json.substring(p+1, end)
return val
}
handle(inst_json, state) {
// Expect: {op:"binop", operation:"+|-|*|/|%|&|...", lhs:VID, rhs:VID, dst:VID}
local dst = me._read_int_field(inst_json, "dst")
local lhs = me._read_int_field(inst_json, "lhs")
local rhs = me._read_int_field(inst_json, "rhs")
local op = me._read_str_field(inst_json, "operation")
if dst == null || lhs == null || rhs == null || op == "" { print("[core/binop] malformed binop") return -1 }
local a = NyVmState.get_reg(state, lhs)
local b = NyVmState.get_reg(state, rhs)
local out = 0
// Support both symbol format ("+") and type format ("Add") for compatibility
if op == "+" || op == "Add" { out = a + b }
else if op == "-" || op == "Sub" { out = a - b }
else if op == "*" || op == "Mul" { out = a * b }
else if op == "/" || op == "Div" { if b == 0 { print("[core/binop] div by zero") return -1 } out = a / b }
else if op == "%" || op == "Mod" { if b == 0 { print("[core/binop] mod by zero") return -1 } out = a % b }
else if op == "&" || op == "BitAnd" || op == "And" { out = a & b }
else if op == "|" || op == "BitOr" || op == "Or" { out = a | b }
else if op == "^" || op == "BitXor" { out = a ^ b }
else if op == "<<" || op == "Shl" { out = a << b }
else if op == ">>" || op == "Shr" { out = a >> b }
else { print("[core/binop] unsupported op: " + op) return -1 }
NyVmState.set_reg(state, dst, out)
return 0
}
}
static box NyVmOpBinOpMain { main(args){ return 0 } }

View File

@ -0,0 +1,32 @@
// ops/branch.hako — NyVmOpBranch (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpBranch {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
// Returns next_bb id (i64)
handle(inst_json, state) {
// {op:"branch", cond:VID, then:BBID, else:BBID}
local condv = me._read_int(inst_json, "cond")
local then_id = me._read_int(inst_json, "then")
local else_id = me._read_int(inst_json, "else")
if condv == null || then_id == null || else_id == null { print("[core/branch] malformed") return -1 }
local cv = NyVmState.get_reg(state, condv)
if cv != 0 { return then_id } else { return else_id }
}
}
static box NyVmOpBranchMain { main(args){ return 0 } }

View File

@ -0,0 +1,55 @@
// ops/compare.hako — NyVmOpCompare (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpCompare {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
_read_str(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return "" }
p = inst_json.indexOf(":", p)
if p < 0 { return "" }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
if inst_json.substring(p,p+1) != "\"" { return "" }
local end = JsonCursorBox.scan_string_end(inst_json, p)
if end < 0 { return "" }
return inst_json.substring(p+1, end)
}
handle(inst_json, state) {
// {op:"compare", operation:"==|!=|<|<=|>|>=", lhs:VID, rhs:VID, dst:VID}
local dst = me._read_int(inst_json, "dst")
local lhs = me._read_int(inst_json, "lhs")
local rhs = me._read_int(inst_json, "rhs")
local op = me._read_str(inst_json, "operation")
if dst == null || lhs == null || rhs == null || op == "" { print("[core/compare] malformed") return -1 }
local a = NyVmState.get_reg(state, lhs)
local b = NyVmState.get_reg(state, rhs)
local r = 0
// Support both symbol format ("==") and type format ("Eq") for compatibility
if op == "==" || op == "Eq" { if a == b { r = 1 } else { r = 0 } }
else if op == "!=" || op == "Ne" { if a != b { r = 1 } else { r = 0 } }
else if op == "<" || op == "Lt" { if a < b { r = 1 } else { r = 0 } }
else if op == "<=" || op == "Le" { if a <= b { r = 1 } else { r = 0 } }
else if op == ">" || op == "Gt" { if a > b { r = 1 } else { r = 0 } }
else if op == ">=" || op == "Ge" { if a >= b { r = 1 } else { r = 0 } }
else { print("[core/compare] unsupported op: " + op) return -1 }
NyVmState.set_reg(state, dst, r)
return 0
}
}
static box NyVmOpCompareMain { main(args){ return 0 } }

View File

@ -0,0 +1,56 @@
// ops/const.hako — NyVmOpConst (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpConst {
_read_int_field(inst_json, key) {
// Find key (supports minimal whitespace)
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
// Skip ws
loop(true) {
local ch = inst_json.substring(p,p+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue }
break
}
// If nested object (value: {type:.., value:N}), read inner value
if inst_json.substring(p,p+1) == "{" {
local vkey = JsonCursorBox.find_key_dual(inst_json, "\"value\":", r#"\"value\":"#, p)
if vkey < 0 { return null }
local vp = inst_json.indexOf(":", vkey)
if vp < 0 { return null }
vp = vp + 1
// skip ws
loop(true) {
local ch2 = inst_json.substring(vp,vp+1)
if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { vp = vp + 1 continue }
break
}
// digits
local digits = JsonCursorBox.digits_from(inst_json, vp)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
// Plain number
local digits2 = JsonCursorBox.digits_from(inst_json, p)
if digits2 == "" { return null }
return StringHelpers.to_i64(digits2)
}
handle(inst_json, state) {
// Expect: {"op":"const","dst":VID, "value": {...} | NUM }
local dst = me._read_int_field(inst_json, "dst")
if dst == null { print("[core/const] missing dst") return -1 }
local v = me._read_int_field(inst_json, "value")
if v == null { print("[core/const] missing value") return -1 }
NyVmState.set_reg(state, dst, v)
return 0
}
}
static box NyVmOpConstMain { main(args){ return 0 } }

View File

@ -0,0 +1,30 @@
// ops/copy.hako — NyVmOpCopy
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpCopy {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
handle(inst_json, state) {
local dst = me._read_int(inst_json, "dst")
local src = me._read_int(inst_json, "src")
if dst == null || src == null { print("[core/copy] missing dst or src") return -1 }
local v = NyVmState.get_reg(state, src)
NyVmState.set_reg(state, dst, v)
return 0
}
}
static box NyVmOpCopyMain { main(args){ return 0 } }

View File

@ -0,0 +1,28 @@
// ops/jump.hako — NyVmOpJump (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box NyVmOpJump {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
// Returns next_bb id (i64)
handle(inst_json) {
// {op:"jump", target:BBID}
local tid = me._read_int(inst_json, "target")
if tid == null { print("[core/jump] malformed") return -1 }
return tid
}
}
static box NyVmOpJumpMain { main(args){ return 0 } }

View File

@ -0,0 +1,33 @@
// ops/load.hako — NyVmOpLoad
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpLoad {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
handle(inst_json, state, mem) {
// {"op":"load","dst":D,"ptr":P}
local dst = me._read_int(inst_json, "dst")
local ptr = me._read_int(inst_json, "ptr")
if dst == null || ptr == null { print("[core/load] missing dst or ptr") return -1 }
local key = StringHelpers.int_to_str(ptr)
local v = mem.get(key)
if v == null { print("[core/load] memory unset at " + key) return -1 }
NyVmState.set_reg(state, dst, v)
return 0
}
}
static box NyVmOpLoadMain { main(args){ return 0 } }

View File

@ -0,0 +1,779 @@
// ops/mir_call.hako — NyVmOpMirCall (Core dispatcher)
// Phase-2 scope:
// - ArrayBox metadata (size) + basic methods (size/push/pop/get/set)
// - MapBox metadata (len) + methods len()/iterator()
// - Stable diagnostics for unsupported module functions / methods / closures
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpMirCall {
_fail(state, tag) {
// Record a stable error tag so NyVmDispatcher can surface it as return value
if state != null { state.set("last_error_tag", tag) }
print(tag)
return -1
}
_arr_key(recv_id) { return "arrsize:r" + ("" + recv_id) }
_arr_val_key(recv_id, idx) { return "arrval:r" + ("" + recv_id) + ":" + ("" + idx) }
_map_key(recv_id) { return "maplen:r" + ("" + recv_id) }
_map_entry_slot(recv_id, key) { return "mapentry:r" + ("" + recv_id) + ":" + key }
_extract_mir_call_obj(inst_json) {
local key = "\"mir_call\":"
local p = inst_json.indexOf(key)
if p < 0 { return "" }
p = p + key.size()
local end = JsonCursorBox.seek_obj_end(inst_json, p)
if end < 0 { return "" }
return inst_json.substring(p, end+1)
}
_read_str(s, key) {
local p = JsonCursorBox.find_key_dual(s, "\""+key+"\":", r#"\""+key+"\":"#, 0)
if p < 0 { return "" }
p = s.indexOf(":", p)
if p < 0 { return "" }
p = p + 1
loop(true) {
local ch = s.substring(p,p+1)
// Parser workaround: avoid increment inside multi-way OR
local is_ws = 0
if ch == " " { is_ws = 1 }
if ch == "\n" { is_ws = 1 }
if ch == "\r" { is_ws = 1 }
if ch == "\t" { is_ws = 1 }
if is_ws == 1 { p = p + 1 continue }
break
}
if s.substring(p,p+1) != "\"" { return "" }
local end = JsonCursorBox.scan_string_end(s, p)
if end < 0 { return "" }
return s.substring(p+1, end)
}
_read_vid_field(json, key, missing_tag, bad_tag) {
local pos = JsonCursorBox.find_key_dual(json, "\""+key+"\":", r#"\""+key+"\":"#, 0)
if pos < 0 { print(missing_tag) return null }
local colon = json.indexOf(":", pos)
if colon < 0 { print(bad_tag) return null }
local digits = JsonCursorBox.digits_from(json, colon+1)
if digits == "" { print(bad_tag) return null }
return StringHelpers.to_i64(digits)
}
_read_optional_vid_field(json, key) {
local pos = JsonCursorBox.find_key_dual(json, "\""+key+"\":", r#"\""+key+"\":"#, 0)
if pos < 0 { return null }
local colon = json.indexOf(":", pos)
if colon < 0 { return null }
local digits = JsonCursorBox.digits_from(json, colon+1)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
// Read mir_call.flags.optionality: "default" | "optional" | "bang"
_read_optionality(mir_call_json) {
local fk = JsonCursorBox.find_key_dual(mir_call_json, "\"flags\":", r#"\"flags\":"#, 0)
if fk < 0 { return "default" }
local colon = mir_call_json.indexOf(":", fk)
if colon < 0 { return "default" }
local idx = colon + 1
loop(true) {
if idx >= mir_call_json.size() { return "default" }
local ch = mir_call_json.substring(idx, idx+1)
local is_ws = 0
if ch == " " { is_ws = 1 }
if ch == "\n" { is_ws = 1 }
if ch == "\r" { is_ws = 1 }
if ch == "\t" { is_ws = 1 }
if is_ws == 1 { idx = idx + 1 continue }
break
}
if idx >= mir_call_json.size() { return "default" }
if mir_call_json.substring(idx, idx+1) != "{" { return "default" }
local rb = JsonCursorBox.seek_obj_end(mir_call_json, idx)
if rb < 0 { return "default" }
local flags = mir_call_json.substring(idx, rb+1)
local opt = me._read_str(flags, "optionality")
if opt == "optional" { return "optional" }
if opt == "bang" { return "bang" }
if mir_call_json.indexOf("\"optionality\":\"bang\"") >= 0 { return "bang" }
if mir_call_json.indexOf("\"optionality\":\"optional\"") >= 0 { return "optional" }
return "default"
}
_read_dst(inst_json, context) {
local missing = "[core/mir_call] " + context + " missing dst"
local bad = "[core/mir_call] " + context + " bad dst"
return me._read_vid_field(inst_json, "dst", missing, bad)
}
_read_receiver(callee, context) {
local missing = "[core/mir_call] " + context + " missing receiver"
local bad = "[core/mir_call] " + context + " bad receiver"
return me._read_vid_field(callee, "receiver", missing, bad)
}
_scan_next_arg(m, i_ref) {
loop(true) {
local idx = i_ref.get(0)
local ch = m.substring(idx, idx+1)
// Parser workaround: avoid array-element assignment inside multi-way OR
local is_whitespace = 0
if ch == " " { is_whitespace = 1 }
if ch == "\n" { is_whitespace = 1 }
if ch == "\r" { is_whitespace = 1 }
if ch == "\t" { is_whitespace = 1 }
if is_whitespace == 1 { i_ref.set(0, idx + 1) continue }
break
}
local idx = i_ref.get(0)
if m.substring(idx, idx+1) == "]" { return "" }
local digits = JsonCursorBox.digits_from(m, idx)
if digits == "" { return "" }
i_ref.set(0, idx + digits.size())
loop(true) {
local idx2 = i_ref.get(0)
if idx2 >= m.size() { break }
local ch2 = m.substring(idx2, idx2+1)
// Apply workaround here too
local is_ws = 0
if ch2 == " " { is_ws = 1 }
if ch2 == "\n" { is_ws = 1 }
if ch2 == "\r" { is_ws = 1 }
if ch2 == "\t" { is_ws = 1 }
if is_ws == 1 { i_ref.set(0, idx2 + 1) continue }
break
}
local idx3 = i_ref.get(0)
if idx3 < m.size() && m.substring(idx3, idx3+1) == "," { i_ref.set(0, idx3 + 1) }
return digits
}
_read_arg_vid(m, index, missing_tag, bad_tag) {
local args_pos = JsonCursorBox.find_key_dual(m, "\"args\":[", r#"\"args\":\["#, 0)
if args_pos < 0 { print(missing_tag) return null }
local start = m.indexOf("[", args_pos)
if start < 0 { print(bad_tag) return null }
local i = new ArrayBox()
i.push(start + 1)
local current = 0
loop(true) {
local idx = i.get(0)
if idx >= m.size() { break }
loop(true) {
local idx2 = i.get(0)
if idx2 >= m.size() { break }
local ch = m.substring(idx2, idx2+1)
// Parser workaround: avoid array-element assignment inside multi-way OR
local is_ws = 0
if ch == " " { is_ws = 1 }
if ch == "\n" { is_ws = 1 }
if ch == "\r" { is_ws = 1 }
if ch == "\t" { is_ws = 1 }
if is_ws == 1 { i.set(0, idx2 + 1) continue }
break
}
local idx3 = i.get(0)
if idx3 >= m.size() { break }
if m.substring(idx3, idx3+1) == "]" { break }
local digits = me._scan_next_arg(m, i)
if digits == "" { print(bad_tag) return null }
if current == index { return StringHelpers.to_i64(digits) }
current = current + 1
}
print(missing_tag)
return null
}
_read_arg_vid_optional(m, index) {
local args_pos = JsonCursorBox.find_key_dual(m, "\"args\":[", r#"\"args\":\["#, 0)
if args_pos < 0 { return null }
local start = m.indexOf("[", args_pos)
if start < 0 { return null }
local i = new ArrayBox()
i.push(start + 1)
local current = 0
loop(true) {
local idx = i.get(0)
if idx >= m.size() { break }
if m.substring(idx, idx+1) == "]" { break }
local digits = me._scan_next_arg(m, i)
if digits == "" { return null }
if current == index { return StringHelpers.to_i64(digits) }
current = current + 1
}
return null
}
_ensure_map_meta(state, recv_id) {
local mem = state.get("mem")
local len_key = me._map_key(recv_id)
if mem.get(len_key) == null { mem.set(len_key, 0) }
}
_map_len_get(state, recv_id) {
local mem = state.get("mem")
local len = mem.get(me._map_key(recv_id))
if len == null { return 0 }
return len
}
_map_len_set(state, recv_id, len) {
local mem = state.get("mem")
mem.set(me._map_key(recv_id), len)
}
_handle_console(callee, m, state) {
local name = me._read_str(callee, "name")
if name == "" { return me._fail(state, "[core/mir_call] console missing name") }
if !(name == "env.console.log" || name == "nyash.console.log" || name == "env.console.warn" || name == "nyash.console.warn" || name == "env.console.error" || name == "nyash.console.error") {
return me._fail(state, "[core/mir_call] unsupported global: " + name)
}
local arg_vid = me._read_arg_vid(m, 0, "[core/mir_call] console missing arg", "[core/mir_call] console bad arg")
if arg_vid == null { return -1 }
local val = NyVmState.get_reg(state, arg_vid)
print("" + val)
return 0
}
_handle_constructor(inst_json, callee, state) {
local dst = me._read_dst(inst_json, "constructor")
if dst == null { return -1 }
local box_type = me._read_str(callee, "box_type")
if box_type == "ArrayBox" {
local mem = state.get("mem")
mem.set(me._arr_key(dst), 0)
NyVmState.set_reg(state, dst, dst)
return 0
}
if box_type == "MapBox" {
me._ensure_map_meta(state, dst)
NyVmState.set_reg(state, dst, dst)
return 0
}
if box_type == "StringBox" {
return me._fail(state, "[core/mir_call/constructor_unsupported]")
}
return me._fail(state, "[core/mir_call/constructor_unsupported]")
}
_array_metadata_or_fail(state, recv_id, context) {
local key = me._arr_key(recv_id)
local mem = state.get("mem")
local sz = mem.get(key)
if sz == null { print("[core/mir_call] " + context + " missing array metadata") return null }
// Return ArrayBox instead of array literal
local result = new ArrayBox()
result.push(mem)
result.push(key)
result.push(sz)
return result
}
_handle_array_method(method, inst_json, m, state, recv_id) {
if method == "size" {
local meta = me._array_metadata_or_fail(state, recv_id, "method(size)")
if meta == null { return -1 }
local dst = me._read_dst(inst_json, "method(size)")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, meta.get(2))
return 0
}
if method == "push" {
local meta = me._array_metadata_or_fail(state, recv_id, "method(push)")
if meta == null { return -1 }
local mem = meta.get(0)
local sz = meta.get(2)
local val_vid = me._read_arg_vid_optional(m, 0)
local value = 0
if val_vid != null { value = NyVmState.get_reg(state, val_vid) }
mem.set(me._arr_val_key(recv_id, sz), value)
sz = sz + 1
mem.set(meta.get(1), sz)
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, sz) }
return 0
}
if method == "pop" {
local meta = me._array_metadata_or_fail(state, recv_id, "method(pop)")
if meta == null { return -1 }
local mem = meta.get(0)
local sz = meta.get(2)
if sz <= 0 {
// Empty → return null (no-op)
local dst_empty = me._read_optional_vid_field(inst_json, "dst")
if dst_empty != null { NyVmState.set_reg(state, dst_empty, null) }
return 0
}
sz = sz - 1
local val = mem.get(me._arr_val_key(recv_id, sz))
mem.set(me._arr_val_key(recv_id, sz), null)
mem.set(meta.get(1), sz)
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, val) }
return 0
}
if method == "clear" {
// clear(): reset size metadata to 0; values are logically cleared (TTL: metadata-only)
local meta = me._array_metadata_or_fail(state, recv_id, "method(clear)")
if meta == null { return -1 }
local mem = meta.get(0)
mem.set(meta.get(1), 0)
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, void) }
return 0
}
if method == "get" {
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(get) missing index", "[core/mir_call] method(get) bad index")
if idx_vid == null { return -1 }
local meta = me._array_metadata_or_fail(state, recv_id, "method(get)")
if meta == null { return -1 }
local mem = meta.get(0)
local idx = NyVmState.get_reg(state, idx_vid)
if idx < 0 || idx >= meta.get(2) {
// OOB → null
local dst_oob = me._read_dst(inst_json, "method(get)")
if dst_oob == null { return -1 }
NyVmState.set_reg(state, dst_oob, null)
return 0
}
local val = mem.get(me._arr_val_key(recv_id, idx))
local dst = me._read_dst(inst_json, "method(get)")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, val)
return 0
}
if method == "set" {
local meta = me._array_metadata_or_fail(state, recv_id, "method(set)")
if meta == null { return -1 }
local mem = meta.get(0)
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(set) missing index", "[core/mir_call] method(set) bad index")
if idx_vid == null { return -1 }
local val_vid = me._read_arg_vid(m, 1, "[core/mir_call] method(set) missing value", "[core/mir_call] method(set) bad value")
if val_vid == null { return -1 }
local idx = NyVmState.get_reg(state, idx_vid)
if idx < 0 { return me._fail(state, "[core/array/oob_set]") }
local value = NyVmState.get_reg(state, val_vid)
// i > len → OOB fail; i == len → append; 0<=i<len → replace
if idx > meta.get(2) { return me._fail(state, "[core/array/oob_set]") }
if idx == meta.get(2) {
// append
mem.set(me._arr_val_key(recv_id, idx), value)
mem.set(meta.get(1), idx + 1)
} else {
mem.set(me._arr_val_key(recv_id, idx), value)
}
// Return void
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, void) }
return 0
}
return me._fail(state, "[core/mir_call/method_unsupported]")
}
_handle_map_method(method, inst_json, m, state, recv_id) {
me._ensure_map_meta(state, recv_id)
local len = me._map_len_get(state, recv_id)
local mem = state.get("mem")
if method == "size" || method == "len" {
local dst = me._read_dst(inst_json, "map " + method)
if dst == null { return -1 }
NyVmState.set_reg(state, dst, len)
return 0
}
if method == "has" {
local dst = me._read_dst(inst_json, "map has")
if dst == null { return -1 }
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map has missing key", "[core/mir_call] map has bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
local slot = me._map_entry_slot(recv_id, key)
local has_key = mem.get(slot) != null
local result = 0
if has_key { result = 1 }
NyVmState.set_reg(state, dst, result)
return 0
}
if method == "keys" {
local dst = me._read_dst(inst_json, "map keys")
if dst == null { return -1 }
// Return array of keys (simplified: return empty array for now)
local keys_arr = new ArrayBox()
NyVmState.set_reg(state, dst, keys_arr)
return 0
}
if method == "values" {
local dst = me._read_dst(inst_json, "map values")
if dst == null { return -1 }
// Return array of values (simplified: return empty array for now)
local values_arr = new ArrayBox()
NyVmState.set_reg(state, dst, values_arr)
return 0
}
if method == "iterator" {
local dst = me._read_dst(inst_json, "map iterator")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, len)
return 0
}
if method == "delete" {
// delete(key): if present, remove and decrement len; dst (optional) := receiver
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map delete missing key", "[core/mir_call] map delete bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
local slot = me._map_entry_slot(recv_id, key)
local deleted = mem.get(slot)
if deleted != null {
mem.set(slot, null)
len = (len - 1)
if len < 0 { len = 0 }
me._map_len_set(state, recv_id, len)
}
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, deleted) }
return 0
}
if method == "clear" {
// clear(): set length to 0; entries are logically clearedTTL: metadataonly
me._map_len_set(state, recv_id, 0)
local dst_opt2 = me._read_optional_vid_field(inst_json, "dst")
if dst_opt2 != null { NyVmState.set_reg(state, dst_opt2, void) }
return 0
}
if method == "set" {
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map set missing key", "[core/mir_call] map set bad key")
if key_vid == null { return -1 }
local val_vid = me._read_arg_vid(m, 1, "[core/mir_call] map set missing value", "[core/mir_call] map set bad value")
if val_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
// Validate key: null/void are invalid暗黙変換なし
if key_val == null || key_val == void { return me._fail(state, "[core/map/key_type]") }
local key = "" + key_val
local slot = me._map_entry_slot(recv_id, key)
local had = mem.get(slot) != null
// Validate value: void は不正。null は許可(値として保存)。
local v = NyVmState.get_reg(state, val_vid)
if v == void { return me._fail(state, "[core/map/value_type]") }
mem.set(slot, v)
if !had {
len = len + 1
me._map_len_set(state, recv_id, len)
}
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, void) }
return 0
}
if method == "get" {
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map get missing key", "[core/mir_call] map get bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
local slot = me._map_entry_slot(recv_id, key)
local value = mem.get(slot)
local opt = me._read_optionality(m)
if value == null {
// default: missing → null
local dst0 = me._read_dst(inst_json, "map get")
if dst0 == null { return -1 }
NyVmState.set_reg(state, dst0, null)
return 0
}
local dst = me._read_dst(inst_json, "map get")
if dst == null { return -1 }
if opt == "optional" {
local outm2 = new MapBox()
outm2.set("present", 1)
outm2.set("value", value)
NyVmState.set_reg(state, dst, outm2)
} else {
NyVmState.set_reg(state, dst, value)
}
return 0
}
return me._fail(state, "[core/mir_call/method_unsupported]")
}
_normalize_method(method) {
// Alias normalization: size|length|len -> size
if method == "length" || method == "len" { return "size" }
// substring|substr -> substring
if method == "substr" { return "substring" }
// charAt|at -> charAt
if method == "at" { return "charAt" }
return method
}
_handle_method(inst_json, callee, m, state) {
local raw_method = me._read_str(callee, "method")
if raw_method == "" { return me._fail(state, "[core/mir_call] method missing name") }
local method = me._normalize_method(raw_method)
local recv_id = me._read_receiver(callee, "method")
if recv_id == null { return -1 }
local mem = state.get("mem")
local arr_key = me._arr_key(recv_id)
local map_key = me._map_key(recv_id)
local has_arr = mem.get(arr_key) != null
local has_map = mem.get(map_key) != null
// Map-specific delegation (support size/len/iterator/set/has/keys/values/delete/get/clear)
if has_map && !has_arr && (method == "size" || method == "len" || method == "iterator" || method == "set" || method == "has" || method == "keys" || method == "values" || method == "delete" || method == "get" || method == "clear") {
return me._handle_map_method(method, inst_json, m, state, recv_id)
}
if method == "size" || method == "len" || method == "iterator" || method == "has" || method == "keys" || method == "values" {
// Map-specific methods without metadata should still succeed
if !has_arr && !has_map {
return me._handle_map_method(method, inst_json, m, state, recv_id)
}
}
// String.size/0 — when receiver is not an Array/Map pseudo-id, treat receiver value as String
if method == "size" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(size)")
if dst == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
NyVmState.set_reg(state, dst, s.size())
return 0
}
// String.indexOf/1 — return first index or -1
if method == "indexOf" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(indexOf)")
if dst == null { return -1 }
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(indexOf) missing needle", "[core/mir_call] method(indexOf) bad needle")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
local needle_val = NyVmState.get_reg(state, idx_vid)
local needle = "" + needle_val
local pos = s.indexOf(needle)
if pos >= 0 {
NyVmState.set_reg(state, dst, pos)
} else {
local opt = me._read_optionality(m)
if opt == "optional" {
NyVmState.set_reg(state, dst, null)
} else if opt == "bang" {
// Record stable error tag for dispatcher to surface as return value
state.set("last_error_tag", "[core/mir_call] string indexOf not found")
return -1
} else {
NyVmState.set_reg(state, dst, -1)
}
}
return 0
}
// String.lastIndexOf/1 — return last index or -1
if method == "lastIndexOf" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(lastIndexOf)")
if dst == null { return -1 }
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(lastIndexOf) missing needle", "[core/mir_call] method(lastIndexOf) bad needle")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
local needle_val = NyVmState.get_reg(state, idx_vid)
local needle = "" + needle_val
local pos = s.lastIndexOf(needle)
NyVmState.set_reg(state, dst, pos)
return 0
}
// String.substring/2 — return substring [start,end) with bounds check
if method == "substring" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(substring)")
if dst == null { return -1 }
local start_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(substring) missing start", "[core/mir_call] method(substring) bad start")
if start_vid == null { return -1 }
local end_vid = me._read_arg_vid(m, 1, "[core/mir_call] method(substring) missing end", "[core/mir_call] method(substring) bad end")
if end_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
local start = NyVmState.get_reg(state, start_vid)
local end = NyVmState.get_reg(state, end_vid)
// Bounds check
if start < 0 || end < 0 || start > s.size() || end > s.size() || start > end {
return me._fail(state, "[core/string/bounds]")
}
local out = s.substring(start, end)
NyVmState.set_reg(state, dst, out)
return 0
}
// String.charAt/1 — return character at index with bounds check
if method == "charAt" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(charAt)")
if dst == null { return -1 }
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(charAt) missing index", "[core/mir_call] method(charAt) bad index")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
local idx = NyVmState.get_reg(state, idx_vid)
// Bounds check
if idx < 0 || idx >= s.size() {
return me._fail(state, "[core/string/bounds]")
}
local ch = s.substring(idx, idx+1)
NyVmState.set_reg(state, dst, ch)
return 0
}
// String.replace/2 — replace first occurrence of pattern with replacement
if method == "replace" && !has_arr && !has_map {
local dst = me._read_dst(inst_json, "method(replace)")
if dst == null { return -1 }
local pattern_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(replace) missing pattern", "[core/mir_call] method(replace) bad pattern")
if pattern_vid == null { return -1 }
local replacement_vid = me._read_arg_vid(m, 1, "[core/mir_call] method(replace) missing replacement", "[core/mir_call] method(replace) bad replacement")
if replacement_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
local pattern_val = NyVmState.get_reg(state, pattern_vid)
local pattern = "" + pattern_val
local replacement_val = NyVmState.get_reg(state, replacement_vid)
local replacement = "" + replacement_val
// Simple replace: find first occurrence and replace
local pos = s.indexOf(pattern)
local result = s
if pos >= 0 {
local before = s.substring(0, pos)
local after = s.substring(pos + pattern.size(), s.size())
result = before + replacement + after
}
NyVmState.set_reg(state, dst, result)
return 0
}
return me._handle_array_method(method, inst_json, m, state, recv_id)
}
_handle_modulefn(inst_json, callee, m, state) {
local name = me._read_str(callee, "name")
if name == "" { return me._fail(state, "[core/mir_call] modulefn missing name") }
// Accept legacy names without "/arity" suffix for a few supported helpers
if name == "StringHelpers.int_to_str" { name = "StringHelpers.int_to_str/1" }
if name == "StringHelpers.to_i64" { name = "StringHelpers.to_i64/1" }
if name == "ArrayBox.len/0" {
local arg_vid = me._read_arg_vid(m, 0, "[core/mir_call] modulefn len/0 missing arg", "[core/mir_call] modulefn len/0 bad arg")
if arg_vid == null { return -1 }
local mem = state.get("mem")
local sz = mem.get(me._arr_key(arg_vid))
if sz == null { sz = 0 }
local dst = me._read_dst(inst_json, "modulefn len/0")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, sz)
return 0
}
if name == "ArrayBox.clear/0" {
// Alias support: treat as receiver.method("clear")
local arg_vid = me._read_first_arg(m)
if arg_vid == null { return me._fail(state, "[core/mir_call] array clear missing arg") }
// Reset metadata size to 0 (values are logically cleared; TTL: metadata-only)
local mem = state.get("mem")
mem.set(me._arr_key(arg_vid), 0)
// Optional dst := void
local dst_opt = me._read_optional_vid_field(inst_json, "dst")
if dst_opt != null { NyVmState.set_reg(state, dst_opt, void) }
return 0
}
if name == "MapBox.len/0" {
local arg_vid = me._read_first_arg(m)
if arg_vid == null { return me._fail(state, "[core/mir_call] map len missing arg") }
me._ensure_map_meta(state, arg_vid)
local dst = me._read_dst(inst_json, "modulefn MapBox.len/0")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, me._map_len_get(state, arg_vid))
return 0
}
if name == "StringHelpers.int_to_str/1" {
// One-arg function: stringify the argument value
local arg_vid = me._read_first_arg(m)
if arg_vid == null { return me._fail(state, "[core/mir_call] int_to_str missing arg") }
local v = NyVmState.get_reg(state, arg_vid)
local s = "" + v
local dst = me._read_dst(inst_json, "modulefn StringHelpers.int_to_str/1")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, s)
return 0
}
if name == "StringHelpers.to_i64/1" {
local arg_vid = me._read_first_arg(m)
if arg_vid == null { return me._fail(state, "[core/mir_call] string to_i64 missing arg") }
local v = NyVmState.get_reg(state, arg_vid)
// Accept already-integer values; else convert numeric strings only
local outv = 0
if ("" + v) == ("" + StringHelpers.to_i64(v)) {
// naive guard: to_i64 roundtrip textual equality — accept v
outv = StringHelpers.to_i64(v)
} else {
// Fallback strict check: only digit strings with optional sign
local s = "" + v
local i = 0
if s.size() > 0 && (s.substring(0,1) == "-" || s.substring(0,1) == "+") { i = 1 }
local ok = (s.size() > i)
loop(i < s.size()) {
local ch = s.substring(i,i+1)
if !(ch >= "0" && ch <= "9") { ok = false break }
i = i + 1
}
if !ok { return me._fail(state, "[core/mir_call] string to_i64 bad arg") }
outv = StringHelpers.to_i64(s)
}
local dst = me._read_dst(inst_json, "modulefn StringHelpers.to_i64/1")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, outv)
return 0
}
if name == "MapBox.iterator/0" {
return me._fail(state, "[core/mir_call] map iterator unsupported")
}
return me._fail(state, "[core/mir_call] modulefn unsupported: " + name)
}
_read_first_arg(mcall_json) {
local k = JsonCursorBox.find_key_dual(mcall_json, "\"args\":[", r#"\"args\":\["#, 0)
if k < 0 { return null }
local lb = mcall_json.indexOf("[", k)
if lb < 0 { return null }
local i = lb + 1
loop(true) {
local ch = mcall_json.substring(i,i+1)
// Parser workaround: avoid increment inside multi-way OR
local is_ws = 0
if ch == " " { is_ws = 1 }
if ch == "\n" { is_ws = 1 }
if ch == "\r" { is_ws = 1 }
if ch == "\t" { is_ws = 1 }
if is_ws == 1 { i = i + 1 continue }
break
}
local ds = JsonCursorBox.digits_from(mcall_json, i)
if ds == "" { return null }
return StringHelpers.to_i64(ds)
}
handle(inst_json, state) {
local m = me._extract_mir_call_obj(inst_json)
if m == "" { return me._fail(state, "[core/mir_call] missing mir_call object") }
local ck = JsonCursorBox.find_key_dual(m, "\"callee\":{", r#"\"callee\":\{"#, 0)
if ck < 0 { return me._fail(state, "[core/mir_call] missing callee") }
local brace = m.indexOf("{", ck)
if brace < 0 { return me._fail(state, "[core/mir_call] bad callee") }
local cb = JsonCursorBox.seek_obj_end(m, brace)
if cb < 0 { return me._fail(state, "[core/mir_call] bad callee") }
local callee = m.substring(brace, cb+1)
local typ = me._read_str(callee, "type")
if typ == "" { return me._fail(state, "[core/mir_call] callee missing type") }
if typ == "Global" || typ == "Extern" {
return me._handle_console(callee, m, state)
} else if typ == "Constructor" {
return me._handle_constructor(inst_json, callee, state)
} else if typ == "Method" {
return me._handle_method(inst_json, callee, m, state)
} else if typ == "ModuleFunction" {
return me._handle_modulefn(inst_json, callee, m, state)
} else if typ == "Closure" {
return me._fail(state, "[core/mir_call] unsupported callee type: Closure")
}
return me._fail(state, "[core/mir_call] unsupported callee type: " + typ)
}
}
static box NyVmOpMirCallMain { main(args){ return 0 } }

View File

@ -0,0 +1,78 @@
// ops/phi.hako — NyVmOpPhi (skeleton)
// Minimal implementation: select input matching predecessor; if none, pick first.
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpPhi {
_read_dst(inst_json) {
local p = JsonCursorBox.find_key_dual(inst_json, "\"dst\":", r#"\"dst\":"#, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p) + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
// Parse inputs=[[bbid,vid], ...], return pair (bbid, vid) list as MapArray
_read_inputs(inst_json) {
local out = new ArrayBox()
local p = JsonCursorBox.find_key_dual(inst_json, "\"inputs\":[", r#"\"inputs\":\["#, 0)
if p < 0 { return out }
local lb = inst_json.indexOf("[", p)
if lb < 0 { return out }
local rb = JsonCursorBox.seek_array_end(inst_json, lb)
if rb < 0 { return out }
local arr = inst_json.substring(lb+1, rb)
local pos = 0
local n = arr.size()
loop (pos < n) {
// skip ws/commas
loop(pos < n) { local ch = arr.substring(pos,pos+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { pos = pos + 1 continue } break }
if pos >= n { break }
if arr.substring(pos,pos+1) != "[" { break }
local rb2 = JsonCursorBox.seek_array_end(arr, pos)
if rb2 < 0 { break }
local pair = arr.substring(pos+1, rb2)
// read two ints
local i = 0
// first
loop(true) { local ch = pair.substring(i,i+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue } break }
local d1s = JsonCursorBox.digits_from(pair, i)
local d1 = d1s == "" ? -1 : StringHelpers.to_i64(d1s)
// move to next
local comma = pair.indexOf(",", i)
i = comma >= 0 ? comma + 1 : i
loop(true) { local ch2 = pair.substring(i,i+1) if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { i = i + 1 continue } break }
local d2s = JsonCursorBox.digits_from(pair, i)
local d2 = d2s == "" ? -1 : StringHelpers.to_i64(d2s)
local m = new MapBox(); m.set("bb", d1); m.set("vid", d2)
out.push(m)
pos = rb2 + 1
}
return out
}
// Apply PHI: dst = value(from predecessor bb)
handle(inst_json, state, predecessor) {
local dst = me._read_dst(inst_json)
if dst == null { print("[core/phi] missing dst") return -1 }
local ins = me._read_inputs(inst_json)
if ins.size() == 0 { print("[core/phi] empty inputs") return -1 }
local pick = ins.get(0)
// try match predecessor
local i = 0
loop(i < ins.size()) {
local m = ins.get(i)
if m.get("bb") == predecessor { pick = m break }
i = i + 1
}
local vid = pick.get("vid")
if vid == null { print("[core/phi] invalid input") return -1 }
local val = NyVmState.get_reg(state, vid)
NyVmState.set_reg(state, dst, val)
return 0
}
}
static box NyVmOpPhiMain { main(args){ return 0 } }

View File

@ -0,0 +1,33 @@
// ops/ret.hako — NyVmOpRet (skeleton)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpRet {
_read_int_field(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) {
local ch = inst_json.substring(p,p+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue }
break
}
if inst_json.substring(p,p+4) == "null" { return null }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
handle(inst_json, state) {
// Expect: {op:"ret", value:VID|null}
local vid = me._read_int_field(inst_json, "value")
if vid == null { return 0 }
return NyVmState.get_reg(state, vid)
}
}
static box NyVmOpRetMain { main(args){ return 0 } }

View File

@ -0,0 +1,32 @@
// ops/store.hako — NyVmOpStore
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpStore {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
handle(inst_json, state, mem) {
// {"op":"store","value":V,"ptr":P}
local val_id = me._read_int(inst_json, "value")
local ptr = me._read_int(inst_json, "ptr")
if val_id == null || ptr == null { print("[core/store] missing value or ptr") return -1 }
local v = NyVmState.get_reg(state, val_id)
local key = StringHelpers.int_to_str(ptr)
mem.set(key, v)
return 0
}
}
static box NyVmOpStoreMain { main(args){ return 0 } }

View File

@ -0,0 +1,53 @@
// ops/typeop.hako — NyVmOpTypeOp
// v1 shape: {"op":"typeop","operation":"check|cast","src":VID,"dst":VID,"target_type":STRING}
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpTypeOp {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
_read_str(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return "" }
p = inst_json.indexOf(":", p)
if p < 0 { return "" }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
if inst_json.substring(p,p+1) != "\"" { return "" }
local end = JsonCursorBox.scan_string_end(inst_json, p)
if end < 0 { return "" }
return inst_json.substring(p+1, end)
}
handle(inst_json, state) {
local dst = me._read_int(inst_json, "dst")
local src = me._read_int(inst_json, "src")
local op = me._read_str(inst_json, "operation")
if dst == null || src == null || op == "" { print("[core/typeop] malformed") return -1 }
if op == "check" {
NyVmState.set_reg(state, dst, 1)
return 0
}
if op == "cast" {
local v = NyVmState.get_reg(state, src)
NyVmState.set_reg(state, dst, v)
return 0
}
print("[core/typeop] unsupported operation: " + op)
return -1
}
}
static box NyVmOpTypeOpMain { main(args){ return 0 } }

View File

@ -0,0 +1,59 @@
// ops/unary.hako — NyVmOpUnary
// Accepts v1 shape: {"op":"unop","kind":"neg|not|bitnot","src":VID,"dst":VID}
// Also tolerates legacy: {"op":"unaryop","op_kind":"Neg|Not|BitNot","operand":VID,"dst":VID}
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
static box NyVmOpUnary {
_read_int(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return null }
p = inst_json.indexOf(":", p)
if p < 0 { return null }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = JsonCursorBox.digits_from(inst_json, p)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
_read_str(inst_json, key) {
local pat = "\"" + key + "\":"
local p = JsonCursorBox.find_key_dual(inst_json, pat, pat, 0)
if p < 0 { return "" }
p = inst_json.indexOf(":", p)
if p < 0 { return "" }
p = p + 1
loop(true) { local ch = inst_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
if inst_json.substring(p,p+1) != "\"" { return "" }
local end = JsonCursorBox.scan_string_end(inst_json, p)
if end < 0 { return "" }
return inst_json.substring(p+1, end)
}
handle(inst_json, state) {
local dst = me._read_int(inst_json, "dst")
if dst == null { print("[core/unop] missing dst") return -1 }
// prefer v1 fields
local src = me._read_int(inst_json, "src")
local kind = me._read_str(inst_json, "kind")
if src == null {
src = me._read_int(inst_json, "operand")
if kind == "" { kind = me._read_str(inst_json, "op_kind") }
// normalize legacy to lower-case
if kind == "Neg" { kind = "neg" } else if kind == "Not" { kind = "not" } else if kind == "BitNot" { kind = "bitnot" }
}
if src == null || kind == "" { print("[core/unop] malformed") return -1 }
local v = NyVmState.get_reg(state, src)
local out = 0
if kind == "neg" { out = 0 - v }
else if kind == "not" { out = (v == 0) ? 1 : 0 }
else if kind == "bitnot" { out = 0 - v - 1 }
else { print("[core/unop] unsupported kind: " + kind) return -1 }
NyVmState.set_reg(state, dst, out)
return 0
}
}
static box NyVmOpUnaryMain { main(args){ return 0 } }

View File

@ -0,0 +1,26 @@
// state.hako — NyVmState (skeleton)
// Holds registers and temporary memory. Numeric-only for Phase1.
static box NyVmState {
make() {
local s = new MapBox()
s.set("regs", new MapBox())
s.set("mem", new MapBox())
return s
}
_reg_key(id) { return "r" + ("" + id) }
get_reg(s, id) {
local key = me._reg_key(id)
local regs = s.get("regs")
local v = regs.get(key)
if v == null { return 0 }
return v
}
set_reg(s, id, value) {
local key = me._reg_key(id)
local regs = s.get("regs")
regs.set(key, value)
return 0
}
}
static box NyVmStateMain { main(args){ return 0 } }

View File

@ -0,0 +1,10 @@
// value.hako — NyVmValue (skeleton)
// Minimal numeric-only representation for Phase1 (const/binop/ret)
static box NyVmValue {
is_i64(v) { return 1 } // skeleton assumes integers only
to_i64(v) { return v }
from_i64(n) { return n }
}
static box NyVmValueMain { main(args){ return 0 } }

View File

@ -0,0 +1,13 @@
// engines/hakorune/engine.hako — Hakorune VM Engine wrapper (skeleton)
// Thin shim delegating to Core dispatcher during core extraction.
using "lang/src/vm/core/dispatcher.hako" as NyVmDispatcher
static box HakoruneNyVmEngine {
run(json) {
return NyVmDispatcher.run(json)
}
}
static box HakoruneNyVmEngineMain { main(args){ return 0 } }

View File

@ -0,0 +1,13 @@
// engines/mini/engine.hako — Mini NyVM Engine wrapper (skeleton)
// For now, delegate to Core dispatcher to keep a single nucleus.
using "lang/src/vm/core/dispatcher.hako" as NyVmDispatcher
static box MiniNyVmEngine {
run(json) {
return NyVmDispatcher.run(json)
}
}
static box MiniNyVmEngineMain { main(args){ return 0 } }

View File

@ -0,0 +1,52 @@
// flow_runner.hako — Selfhost VM runner thin boxexec allowed under selfhost/vm/
using "lang/src/compiler/pipeline_v2/flow_entry.hako" as FlowEntryBox
using hakorune.vm.mir_min as MirVmMin
using "lang/src/shared/common/string_ops.hako" as StringOps
static box FlowRunner {
_read_digits(text, pos) { local out = "" local i = pos loop(true) { local ch = text.substring(i, i+1) if ch == "" { break } if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } } return out }
_parse_return_int(ast_json) {
if ast_json == null { return null }
local rt = StringOps.index_of_from(ast_json, "\"type\":\"Return\"", 0)
if rt < 0 { return null }
local it = StringOps.index_of_from(ast_json, "\"type\":\"Int\"", rt)
if it < 0 { return null }
local vp = StringOps.index_of_from(ast_json, "\"value\":", it)
if vp < 0 { return null }
local ds = me._read_digits(ast_json, vp + 8)
if ds == "" { return null }
local acc = 0
local i = 0
loop(i < ds.size()) { acc = acc * 10 + ("0123456789".indexOf(ds.substring(i,i+1))) i = i + 1 }
return acc
}
// Execute on MiniVM from Stage1 JSON. compat=1 で v1→v0 経路。
run_vm_min_from_ast(ast_json, prefer_cfg, compat) {
// Fast-path Return(Int v)
local fast = me._parse_return_int(ast_json)
if fast != null { return fast }
local j = null
if compat == 1 { j = FlowEntryBox.emit_v1_compat_from_ast(ast_json, prefer_cfg) }
else { j = FlowEntryBox.emit_v0_from_ast(ast_json, prefer_cfg) }
// DEV marker injection is delegated to the CLI (Rust) bridge.
// 将来の切替用トグル: CLI が __cli_dev__ を埋めた場合のみ __dev__ に正規化現状は未使用・既定OFF
j = me._maybe_inject_dev_marker(j, ast_json)
return MirVmMin.run(j)
}
_maybe_inject_dev_marker(j, ast_json) {
if j == null { return j }
if j.indexOf("\"__dev__\":1") >= 0 { return j }
if ast_json != null && ast_json.indexOf("\"__cli_dev__\":1") >= 0 {
if j.substring(0,1) == "{" {
local payload = j.substring(1, j.size())
return "{\"__dev__\":1," + payload
}
}
return j
}
main(args) { return 0 }
}

16
lang/src/vm/gc/README.md Normal file
View File

@ -0,0 +1,16 @@
GC v0 — Mark & Sweep (Skeleton)
===============================
Responsibility
- Provide a minimal stoptheworld Mark & Sweep collector for Hakorune VM.
- Deterministic, observable, and FailFast by default.
Status
- Skeleton only. Not wired to VM yet. Safe to keep in repo without sideeffects.
Principles
- No generational/incremental logic in v0.
- Safepoints: call boundaries / loop backedges / before long I/O waits.
- Triggers: live_bytes growth (>80% since last sweep) or +4MB.
- Observability: `HAKO_GC_TRACE=1` for timings and survivor counts.

View File

@ -0,0 +1,18 @@
// gc_box.hako — GC v0 (skeleton; not wired)
using "lang/src/vm/gc/gc_metrics_box.hako" as GcMetrics
static box GcBox {
metrics: GcMetrics.GcMetricsBox
birth() {
from Box.birth()
me.metrics = new GcMetrics.GcMetricsBox()
}
// Placeholder hooks
register_object(_obj) { }
should_collect() { return false }
collect() { me.metrics.increment_collections() }
}

View File

@ -0,0 +1,7 @@
// gc_hooks.hako — GC safepoint hooks (no-op skeleton)
static box GcHooks {
// Safepoint at well-defined boundaries (no-op in v0)
safepoint() { }
}

View File

@ -0,0 +1,27 @@
// gc_metrics_box.hako — GC metrics (skeleton; not wired)
static box GcMetricsBox {
total_allocations: IntegerBox
total_collections: IntegerBox
total_freed: IntegerBox
birth() {
from Box.birth()
me.total_allocations = 0
me.total_collections = 0
me.total_freed = 0
}
increment_allocations() {
me.total_allocations = me.total_allocations + 1
}
increment_collections() {
me.total_collections = me.total_collections + 1
}
record_collection(_mark_ms: IntegerBox, _sweep_ms: IntegerBox, _survivors: IntegerBox) {
// tracing is gated by HAKO_GC_TRACE; printing is deferred to VM hook
}
}

View File

@ -0,0 +1,10 @@
// gc_policy_box.hako — GC Policy Box (v0)
// Responsibility: centralize policy toggles for collection decision.
// Default: OFF (returns 0). Threshold/ENV gating will be added later.
static box GcPolicyBox {
should_collect() { return 0 }
}
static box GcPolicyMain { main(args) { return 0 } }

View File

@ -0,0 +1,32 @@
// gc_runtime.hako — minimal GC runtime facade (v0; not wired)
using "lang/src/vm/gc/gc_box.hako" as Gc
using "lang/src/vm/gc/gc_policy_box.hako" as GcPolicy
static box GcRuntime {
gc: Gc.GcBox
birth() {
from Box.birth()
me.gc = new Gc.GcBox()
}
ensure() {
if me.gc == null { me.gc = new Gc.GcBox() }
return me.gc
}
allocate(_hint) {
// v0: metrics only
me.ensure().metrics.increment_allocations()
// Opportunistically check if a collection is needed (default OFF)
me.collect_if_needed()
}
// v0 policy: default OFF (delegates to policy box; thresholds/env gating TBD)
should_collect() { return GcPolicy.GcPolicyBox.should_collect() }
collect_if_needed() {
if me.should_collect() == 1 { me.ensure().collect() }
}
}

View File

@ -0,0 +1,14 @@
[module]
name = "selfhost.vm"
version = "1.0.0"
[exports]
entry = "boxes/mini_vm_entry.hako"
mir_min = "boxes/mir_vm_min.hako"
core = "boxes/mini_vm_core.hako"
[private]
# helpers = "internal/helpers.hako"
[dependencies]
# "selfhost.common.json" = "^1.0.0"

View File

@ -0,0 +1,17 @@
# Hakorune VM (nyvm) — Engine Guard
Responsibility
- Engine orchestration and instruction dispatch for nyvm.
- Controlflow handlers (branch/jump/phi) and value routing.
Allowed Imports
- `lang/src/vm/boxes/*` (helpers)
- `lang/src/shared/*` (string ops, JSON scan, etc.)
Forbidden
- Parser/Resolver/Emitter pipelines
- Direct plugin/ABI wiring (use kernel/extern facades)
Migration Note
- Final layout will be `lang/src/vm/engines/hakorune/` with module aliases.

View File

@ -0,0 +1,7 @@
This directory stores legacy implementations preserved during the Core re-export migration.
Policy
- Read-only snapshots of pre-migration handlers; kept for reference and potential rollback.
- Do not import from production code. New code should call Core via core_bridge_ops.hako.
- Remove after Core parity stabilizes and negative smokes are green across profiles.

View File

@ -0,0 +1,48 @@
// Snapshot of legacy binop_handler.hako (pre Core delegation)
// DO NOT import from production. See archive/README.md.
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/reg_guard.hako" as RegGuardBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
static box BinOpHandlerBoxLegacy {
handle(inst_json, regs) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("binop: dst field not found") }
local lhs_id = JsonFieldExtractor.extract_int(inst_json, "lhs")
if lhs_id == null { return Result.Err("binop: lhs field not found") }
local rhs_id = JsonFieldExtractor.extract_int(inst_json, "rhs")
if rhs_id == null { return Result.Err("binop: rhs field not found") }
local kind = JsonFieldExtractor.extract_string(inst_json, "operation")
if kind != null {
if kind == "+" { kind = "Add" }
else if kind == "-" { kind = "Sub" }
else if kind == "*" { kind = "Mul" }
else if kind == "/" { kind = "Div" }
else if kind == "%" { kind = "Mod" }
else { return Result.Err("binop: unsupported operation: " + kind) }
} else {
kind = JsonFieldExtractor.extract_string(inst_json, "op_kind")
if kind == null { return Result.Err("binop: operation/op_kind not found") }
}
local g_lhs = RegGuardBox.require_set(regs, lhs_id, "binop: lhs")
if g_lhs.is_Err() { return g_lhs }
local lhs_val = g_lhs.as_Ok()
local g_rhs = RegGuardBox.require_set(regs, rhs_id, "binop: rhs")
if g_rhs.is_Err() { return g_rhs }
local rhs_val = g_rhs.as_Ok()
local result_val = 0
if kind == "Add" { result_val = lhs_val + rhs_val }
else if kind == "Sub" { result_val = lhs_val - rhs_val }
else if kind == "Mul" { result_val = lhs_val * rhs_val }
else if kind == "Div" { if rhs_val == 0 { return Result.Err("binop: division by zero") } result_val = lhs_val / rhs_val }
else if kind == "Mod" { if rhs_val == 0 { return Result.Err("binop: modulo by zero") } result_val = lhs_val % rhs_val }
else { return Result.Err("binop: unsupported op_kind: " + kind) }
ValueManagerBox.set(regs, dst, result_val)
return Result.Ok(0)
}
}

View File

@ -0,0 +1,36 @@
// Snapshot of legacy compare_handler.hako (pre Core delegation)
// DO NOT import from production. See archive/README.md.
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/reg_guard.hako" as RegGuardBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
static box CompareHandlerBoxLegacy {
handle(inst_json, regs) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("compare: dst field not found") }
local lhs_id = JsonFieldExtractor.extract_int(inst_json, "lhs")
if lhs_id == null { return Result.Err("compare: lhs field not found") }
local rhs_id = JsonFieldExtractor.extract_int(inst_json, "rhs")
if rhs_id == null { return Result.Err("compare: rhs field not found") }
local kind = JsonFieldExtractor.extract_string(inst_json, "kind")
if kind == null { return Result.Err("compare: kind field not found") }
local lhs_val = ValueManagerBox.get(regs, lhs_id)
if lhs_val == null { return Result.Err("compare: lhs v%" + StringHelpers.int_to_str(lhs_id) + " is unset") }
local rhs_val = ValueManagerBox.get(regs, rhs_id)
if rhs_val == null { return Result.Err("compare: rhs v%" + StringHelpers.int_to_str(rhs_id) + " is unset") }
local result_val = 0
if kind == "Eq" { if lhs_val == rhs_val { result_val = 1 } }
else if kind == "Ne" { if lhs_val != rhs_val { result_val = 1 } }
else if kind == "Lt" { if lhs_val < rhs_val { result_val = 1 } }
else if kind == "Le" { if lhs_val <= rhs_val { result_val = 1 } }
else if kind == "Gt" { if lhs_val > rhs_val { result_val = 1 } }
else if kind == "Ge" { if lhs_val >= rhs_val { result_val = 1 } }
else { return Result.Err("compare: unsupported kind: " + kind) }
ValueManagerBox.set(regs, dst, result_val)
return Result.Ok(0)
}
}

View File

@ -0,0 +1,59 @@
// Snapshot of legacy const_handler.hako (pre Core delegation)
// DO NOT import from production. See archive/README.md.
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
static box ConstHandlerBoxLegacy {
handle(inst_json, regs) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("const: dst field not found") }
local key_i64 = "\"value\":{\"type\":\"i64\",\"value\":"
local val_i64_start = inst_json.indexOf(key_i64)
if val_i64_start >= 0 {
val_i64_start = val_i64_start + key_i64.size()
local digits = StringHelpers.read_digits(inst_json, val_i64_start)
if digits == "" { return Result.Err("const: invalid i64 value") }
local value = StringHelpers.to_i64(digits)
ValueManagerBox.set(regs, dst, value)
return Result.Ok(0)
}
local key_int = "\"value\":{\"Integer\":"
local val_int_start = inst_json.indexOf(key_int)
if val_int_start >= 0 {
val_int_start = val_int_start + key_int.size()
local digits = StringHelpers.read_digits(inst_json, val_int_start)
if digits == "" { return Result.Err("const: invalid Integer value") }
local value = StringHelpers.to_i64(digits)
ValueManagerBox.set(regs, dst, value)
return Result.Ok(0)
}
local key_str = "\"value\":{\"String\":\""
local val_str_start = inst_json.indexOf(key_str)
if val_str_start >= 0 {
val_str_start = val_str_start + key_str.size()
local val_str_end = StringOps.index_of_from(inst_json, "\"}", val_str_start)
if val_str_end < 0 { return Result.Err("const: invalid String value") }
local str_value = inst_json.substring(val_str_start, val_str_end)
ValueManagerBox.set(regs, dst, str_value)
return Result.Ok(0)
}
{
local key_s2 = "\"value\":{\"type\":\"string\",\"value\":\""
local p2 = inst_json.indexOf(key_s2)
if p2 >= 0 {
p2 = p2 + key_s2.size()
local end2 = StringOps.index_of_from(inst_json, "\"}", p2)
if end2 < 0 { return Result.Err("const: invalid string (type string)") }
local s2 = inst_json.substring(p2, end2)
ValueManagerBox.set(regs, dst, s2)
return Result.Ok(0)
}
}
return Result.Err("const: unsupported value type")
}
}

View File

@ -0,0 +1,158 @@
// ArgsExtractorBox - Extract and load arguments from MirCall JSON
// Single Responsibility: Parse args array, load values from registers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
static box ArgsExtractorBox {
// Extract args array from mir_call object and load values from registers
// mir_call_json: JSON string like '{"args":[1,2,3],...}'
// regs: register MapBox
// Returns: ArrayBox containing argument values (in order)
extract_and_load(mir_call_json, regs) {
// Create result array
local args_array = new ArrayBox()
// Find "args" field
local args_key = "\"args\":["
local args_start = mir_call_json.indexOf(args_key)
if args_start < 0 {
// No args field, return empty array
return args_array
}
args_start = args_start + args_key.size()
// Find array end
local args_end = StringOps.index_of_from(mir_call_json, "]", args_start)
if args_end < 0 {
// Malformed args array, return empty
return args_array
}
// Extract args array content (between [ and ])
local args_content = mir_call_json.substring(args_start, args_end)
// If empty array, return immediately
if args_content.size() == 0 {
return args_array
}
// Parse comma-separated ValueIds
local pos = 0
local content_len = args_content.size()
loop(pos < content_len) {
// Skip whitespace
loop(pos < content_len) {
local ch = args_content.substring(pos, pos + 1)
if ch != " " && ch != "\t" && ch != "\n" && ch != "\r" {
break
}
pos = pos + 1
}
if pos >= content_len {
break
}
// Find next comma or end
local next_comma = StringOps.index_of_from(args_content, ",", pos)
local value_end = 0
if next_comma < 0 {
value_end = content_len
} else {
value_end = next_comma
}
// Extract ValueId string
local value_str = args_content.substring(pos, value_end)
// Convert to integer (ValueId)
local value_id = StringHelpers.to_i64(value_str)
// Load value from register
local arg_value = ValueManagerBox.get(regs, value_id)
// Add to result array
args_array.push(arg_value)
// Move to next argument
if next_comma < 0 {
break
}
pos = next_comma + 1
}
return args_array
}
// Get argument count without loading values
// mir_call_json: JSON string like '{"args":[1,2,3],...}'
// Returns: number of arguments
count_args(mir_call_json) {
// Find "args" field
local args_key = "\"args\":["
local args_start = mir_call_json.indexOf(args_key)
if args_start < 0 {
return 0
}
args_start = args_start + args_key.size()
// Find array end
local args_end = StringOps.index_of_from(mir_call_json, "]", args_start)
if args_end < 0 {
return 0
}
// Extract args array content
local args_content = mir_call_json.substring(args_start, args_end)
if args_content.size() == 0 {
return 0
}
// Count commas + 1
local count = 1
local pos = 0
loop(pos < args_content.size()) {
local ch = args_content.substring(pos, pos + 1)
if ch == "," {
count = count + 1
}
pos = pos + 1
}
return count
}
// Extract argument ValueIds without loading register values
extract_ids(mir_call_json) {
local ids = new ArrayBox()
local args_key = "\"args\":["
local args_start = mir_call_json.indexOf(args_key)
if args_start < 0 { return ids }
args_start = args_start + args_key.size()
local args_end = StringOps.index_of_from(mir_call_json, "]", args_start)
if args_end < 0 { return ids }
local content = mir_call_json.substring(args_start, args_end)
if content.size() == 0 { return ids }
local pos = 0
loop(pos < content.size()) {
loop(pos < content.size()) {
local ch = content.substring(pos, pos+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { pos = pos + 1 continue }
break
}
if pos >= content.size() { break }
local comma = StringOps.index_of_from(content, ",", pos)
local end = comma < 0 ? content.size() : comma
local token = content.substring(pos, end)
if token.size() > 0 {
ids.push(StringHelpers.to_i64(token))
}
if comma < 0 { break }
pos = comma + 1
}
return ids
}
}

View File

@ -0,0 +1,21 @@
// args_guard.hako — ArgsGuardBox
// Responsibility: validate argument arrays for boxcall/method (FailFast)
using "lang/src/vm/boxes/result_box.hako" as Result
static box ArgsGuardBox {
// Ensure no nulls in args_array
// Returns Ok(args_array) or Err with first offending index
ensure_no_nulls(args_array, method_sig) {
if args_array == null { return Result.Ok(new ArrayBox()) }
local n = args_array.size()
local i = 0
loop(i < n) {
if args_array.get(i) == null {
return Result.Err("args: argument[" + i + "] is unset (method=" + method_sig + ")")
}
i = i + 1
}
return Result.Ok(args_array)
}
}

View File

@ -0,0 +1,43 @@
// backward_object_scanner.hako — BackwardObjectScannerBox
// Responsibility: extract last JSON object in a segment by scanning backward
using "lang/src/vm/boxes/result_box.hako" as Result
static box BackwardObjectScannerBox {
// Returns Ok(obj_string) or Err(label)
scan_last_object(seg, budget) {
local n = seg.size()
local steps = 0
// trim trailing spaces/commas
local e = n - 1
loop(e >= 0) {
if steps >= budget { return Result.Err("scan budget exceeded (trim)") }
steps = steps + 1
local ch = seg.substring(e, e+1)
if ch == " " || ch == "
" || ch == "
" || ch == " " || ch == "," { e = e - 1 continue }
break
}
// find last '}'
loop(e >= 0) {
if steps >= budget { return Result.Err("scan budget exceeded (find end)") }
steps = steps + 1
if seg.substring(e, e+1) == "}" { break }
e = e - 1
}
if e < 0 { return Result.Err("no closing brace") }
// match '{'
local depth = 1
local s = e - 1
loop(s >= 0) {
if steps >= budget { return Result.Err("scan budget exceeded (match start)") }
steps = steps + 1
local ch2 = seg.substring(s, s+1)
if ch2 == "}" { depth = depth + 1 } else { if ch2 == "{" { depth = depth - 1 if depth == 0 { break } } }
s = s - 1
}
if s < 0 { return Result.Err("no opening brace") }
return Result.Ok(seg.substring(s, e+1))
}
}

View File

@ -0,0 +1,38 @@
// BarrierHandlerBox - Memory barrier instruction handler
// Handles: barrier (memory fence for ordering guarantees)
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
static box BarrierHandlerBox {
// Handle barrier instruction
// inst_json: {"op":"barrier","op_kind":"Read|Write","ptr":X}
// regs: register MapBox
// mem: memory MapBox
// Returns: Result.Ok(0) or Result.Err(message)
// Phase 1: No-op (just validate fields, actual memory barrier not implemented yet)
handle(inst_json, regs, mem) {
// Extract op_kind
local kind = JsonFieldExtractor.extract_string(inst_json, "op_kind")
if kind == null {
return Result.Err("barrier: op_kind not found")
}
// Validate op_kind (must be "Read" or "Write")
if kind != "Read" && kind != "Write" {
return Result.Err("barrier: invalid op_kind: " + kind)
}
// Extract ptr (memory pointer)
local ptr = JsonFieldExtractor.extract_int(inst_json, "ptr")
if ptr == null {
return Result.Err("barrier: ptr not found")
}
// Phase 1: No-op (just validate fields)
// Future: Implement actual memory barrier semantics for ordering guarantees
// - Read barrier: Ensure all preceding loads complete before subsequent loads
// - Write barrier: Ensure all preceding stores complete before subsequent stores
return Result.Ok(0)
}
}

View File

@ -0,0 +1,17 @@
// BinOpHandlerBox - BinOp instruction handler
// Handles: %dst = %lhs op_kind %rhs (Add/Sub/Mul/Div/Mod)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/reg_guard.hako" as RegGuardBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/core_bridge_ops.hako" as CoreBridgeOps
static box BinOpHandlerBox {
// Handle binop instruction
// inst_json: {"op":"binop","dst":3,"op_kind":"Add","lhs":1,"rhs":2}
// Returns: Result.Ok(0) or Result.Err(message)
handle(inst_json, regs) { return CoreBridgeOps.apply_binop(inst_json, regs) }
}

View File

@ -0,0 +1,30 @@
// block_iterator.hako — BlockIteratorBox
// Responsibility: iterate block objects within blocks content string
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
static box BlockIteratorBox {
// Returns Ok({ obj: <string>, next_pos: <int> }) or Err when malformed
next(blocks_content, pos) {
local n = blocks_content.size()
local i = pos
loop(i < n) {
local ch = blocks_content.substring(i, i+1)
if ch == " " || ch == "
" || ch == "
" || ch == " " || ch == "," { i = i + 1 continue }
if ch == "{" {
local endi = JsonScanGuardBox.seek_obj_end(blocks_content, i, 200000)
if endi < 0 { return Result.Err("block object end not found") }
local obj = blocks_content.substring(i, endi+1)
local meta = new MapBox()
meta.set("obj", obj)
meta.set("next_pos", endi+1)
return Result.Ok(meta)
}
return Result.Err("block iterator: unexpected char")
}
return Result.Err("block iterator: end")
}

View File

@ -0,0 +1,152 @@
// block_mapper.hako — Phase 1 Day 3: MIR Block Map Builder
// Strategy: 箱化モジュール化 - ブロック解析を分離FunctionLocatorBox + BlocksLocatorBox 優先、手書きはフォールバック)
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/function_locator.hako" as FunctionLocatorBox
using "lang/src/vm/hakorune-vm/blocks_locator.hako" as BlocksLocatorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
static box BlockMapperBox {
// Build a map of block_id -> block_json
// Returns: Result.Ok(MapBox) or Result.Err(message)
build_map(mir_json) {
// Preferred path: locator boxesで堅牢に抽出
local loc_func = FunctionLocatorBox.locate(mir_json)
if loc_func.is_Err() { return me._fallback_build(mir_json) }
local func_meta = loc_func.as_Ok()
local func_json = func_meta.get("content")
local loc_blocks = BlocksLocatorBox.locate(func_json)
if loc_blocks.is_Err() { return me._fallback_build(mir_json) }
local blocks_meta = loc_blocks.as_Ok()
// content は配列の内側(先頭 '[' と末尾 ']' を除いた部分)
local blocks_content = blocks_meta.get("content")
local block_map = new MapBox()
// blocks_content から連続するオブジェクト { ... } を走査
local pos = 0
local n = blocks_content.size()
loop(pos < n) {
// 空白/カンマをスキップ
local ch = blocks_content.substring(pos, pos+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," { pos = pos + 1 continue }
if ch != "{" { pos = pos + 1 continue }
local end = JsonScanGuardBox.seek_obj_end(blocks_content, pos, 500000)
if end < 0 { return Result.Err("block object end not found") }
local block_json = blocks_content.substring(pos, end+1)
// id を抽出
local key_id = "\"id\":"
local id_start = block_json.indexOf(key_id)
if id_start < 0 { return Result.Err("block id not found") }
id_start = id_start + key_id.size()
// 空白スキップ
loop(id_start < block_json.size()) {
local c2 = block_json.substring(id_start, id_start+1)
if c2 == " " || c2 == "\n" || c2 == "\r" || c2 == "\t" { id_start = id_start + 1 continue }
break
}
local id_digits = StringHelpers.read_digits(block_json, id_start)
if id_digits == "" { return Result.Err("invalid block id") }
local block_id = StringHelpers.to_i64(id_digits)
block_map.set(StringHelpers.int_to_str(block_id), block_json)
pos = end + 1
}
return Result.Ok(block_map)
}
// Legacy fallback path: 手書きスキャンを維持(異常時の観測用)
_fallback_build(mir_json) {
local block_map = new MapBox()
// 関数オブジェクト検出
local obj_start = mir_json.indexOf("{")
if obj_start < 0 { return Result.Err("function object not found") }
local obj_end = JsonCursorBox.seek_obj_end(mir_json, obj_start)
if obj_end < 0 { return Result.Err("function object end not found") }
local func_json = mir_json.substring(obj_start, obj_end + 1)
// blocks 配列位置へ
local key_pos = JsonCursorBox.find_key_dual(func_json, "\"blocks\"", "\\\"blocks\\\"", 0)
if key_pos < 0 { return Result.Err("blocks key not found") }
local i = key_pos + "\"blocks\"".size()
loop(i < func_json.size()) {
local ch = func_json.substring(i, i+1)
if ch == ":" { i = i + 1 break }
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
return Result.Err("invalid blocks key format")
}
loop(i < func_json.size()) {
local ch = func_json.substring(i, i+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
if ch == "[" { break }
return Result.Err("blocks array missing after key")
}
local lbr = i
local blocks_end = JsonCursorBox.seek_array_end(func_json, lbr)
if blocks_end < 0 { return Result.Err("blocks array end not found") }
local blocks_json = func_json.substring(lbr, blocks_end)
// 各ブロックを解析
local pos = 0
local len = blocks_json.size()
loop(pos < len) {
// 空白とカンマをスキップ
local ch = blocks_json.substring(pos, pos + 1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" || ch == "," {
pos = pos + 1
continue
}
// ブロックオブジェクトを抽出
if ch == "{" {
local block_end = JsonCursorBox.seek_obj_end(blocks_json, pos)
if block_end < 0 {
return Result.Err("block object end not found")
}
local block_json = blocks_json.substring(pos, block_end + 1)
// ブロックID
local key_id = "\"id\":"
local id_start = block_json.indexOf(key_id)
if id_start < 0 {
return Result.Err("block id not found")
}
id_start = id_start + key_id.size()
// 数字直前の空白スキップ
loop(id_start < block_json.size()) {
local ch2 = block_json.substring(id_start, id_start+1)
if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { id_start = id_start + 1 continue }
break
}
local id_digits = StringHelpers.read_digits(block_json, id_start)
if id_digits == "" {
return Result.Err("invalid block id")
}
local block_id = StringHelpers.to_i64(id_digits)
// Map に保存
block_map.set(StringHelpers.int_to_str(block_id), block_json)
pos = block_end + 1
continue
}
// その他はスキップ
pos = pos + 1
}
return Result.Ok(block_map)
}
}

View File

@ -0,0 +1,38 @@
// blocks_locator.hako — BlocksLocatorBox
// Responsibility: locate blocks[] array in function JSON
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
static box BlocksLocatorBox {
locate(func_json) {
// Find key "blocks"
local kpos = JsonCursorBox.index_of_from(func_json, r#""blocks""#, 0)
if kpos < 0 { return Result.Err("blocks key not found") }
// find ':' then '['
local i = kpos + r#""blocks""#.size()
loop(i < func_json.size()) {
local ch = func_json.substring(i, i+1)
if ch == ":" { i = i + 1 break }
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
return Result.Err("blocks key format invalid")
}
loop(i < func_json.size()) {
local ch = func_json.substring(i, i+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 continue }
if ch == "[" { break }
return Result.Err("blocks array not found")
}
local lbr = i
local arr_end = JsonScanGuardBox.seek_array_end(func_json, lbr, 200000)
if arr_end < 0 { return Result.Err("blocks array end not found") }
// exclude brackets
local content = func_json.substring(lbr+1, arr_end)
local meta = new MapBox()
meta.set("start", lbr+1)
meta.set("end", arr_end)
meta.set("content", content)
return Result.Ok(meta)
}
}

View File

@ -0,0 +1,34 @@
// BoxcallBuilderBox — Build minimal boxcall JSON strings and manage temp regs
// Single Responsibility: Provide small helpers to synthesize boxcall JSON safely
// Usage: allocate temp register for args, then build JSON for BoxCallHandler
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box BoxcallBuilderBox {
// Allocate a temporary register id that does not collide with existing keys
alloc_tmp_reg(regs) {
local tmp_id = 900001
local try_key = StringHelpers.int_to_str(tmp_id)
loop(regs.has(try_key)) { tmp_id = tmp_id + 1 try_key = StringHelpers.int_to_str(tmp_id) }
return tmp_id
}
// Build a boxcall JSON for: { op: "boxcall", box: <vid>, method: "call", args: [tmp_id], dst: <dst> }
build_call(dst_reg, vid, tmp_id) {
return "{\"op\":\"boxcall\",\"dst\":" + StringHelpers.int_to_str(dst_reg) +
",\"box\":" + StringHelpers.int_to_str(vid) +
",\"method\":\"call\",\"args\":[" + StringHelpers.int_to_str(tmp_id) + "]}"
}
}
static box BoxcallBuilderBox_Methods {
// Generic builder: method name + args (array of reg ids as CSV string)
build_method(dst_reg, vid, method_name, arg_ids_csv) {
return "{\"op\":\"boxcall\",\"dst\":"
+ StringHelpers.int_to_str(dst_reg)
+ ",\"box\":" + StringHelpers.int_to_str(vid)
+ ",\"method\":\"" + method_name + "\""
+ ",\"args\":[" + arg_ids_csv + "]}"
}
}

View File

@ -0,0 +1,191 @@
// BoxCallHandlerBox - Handle boxcall instruction (Box method calls)
// Single Responsibility: Dispatch dynamic Box method calls
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
using "lang/src/vm/hakorune-vm/args_guard.hako" as ArgsGuardBox
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/receiver_guard.hako" as ReceiverGuardBox
static box BoxCallHandlerBox {
// Handle boxcall instruction
// inst_json: {"op":"boxcall","box":X,"method":"name","args":[...],"dst":Y}
// regs: register MapBox
// Returns: Result.Ok(0) or Result.Err(message)
handle(inst_json, regs) {
// Extract box (receiver) ValueId
local box_id = JsonFieldExtractor.extract_int(inst_json, "box")
if box_id == null {
return Result.Err("boxcall: box field not found")
}
// Extract method name
local method_name = JsonFieldExtractor.extract_string(inst_json, "method")
if method_name == null {
return Result.Err("boxcall: method field not found")
}
// Extract arguments (construct fake mir_call JSON for ArgsExtractorBox)
// We need to extract args array from inst_json
local args_array = me._extract_args(inst_json, regs)
// Special-case: methodRef — tolerate missing/empty arity by defaulting to 0 and return early
if method_name == "methodRef" {
local name = args_array.get(0)
local arity = args_array.get(1)
if arity == null { arity = 0 }
local dst_reg = JsonFieldExtractor.extract_int(inst_json, "dst")
// Load receiver early for direct dispatch
local receiver = ValueManagerBox.get(regs, box_id)
if receiver == null {
return Result.Err("boxcall: receiver v%" + StringHelpers.int_to_str(box_id) + " is unset (method=methodRef)")
}
local result_val = receiver.methodRef(name, arity)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, result_val) }
return Result.Ok(0)
}
// Guard arguments (no nulls)
local _g = ArgsGuardBox.ensure_no_nulls(args_array, method_name + "/" + args_array.size())
if _g.is_Err() { return _g }
// Extract destination register
local dst_reg = JsonFieldExtractor.extract_int(inst_json, "dst")
// Load receiver object
local receiver = ValueManagerBox.get(regs, box_id)
if receiver == null {
return Result.Err("boxcall: receiver v%" + StringHelpers.int_to_str(box_id) + " is unset (method=" + method_name + ")")
}
// Prepare method signature for dispatch
local arg_count = args_array.size()
local method_sig = method_name + "/" + arg_count
// Known methods dispatch table
local result_val = null
// StringBox methods (using Rust VM available methods)
if method_sig == "upper/0" {
result_val = receiver.to_upper()
} else if method_sig == "lower/0" {
result_val = receiver.to_lower()
} else if method_sig == "size/0" {
result_val = receiver.size()
} else if method_sig == "length/0" {
result_val = receiver.size()
} else if method_sig == "isEmpty/0" {
result_val = receiver.isEmpty()
} 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))
}
// ArrayBox methods
else if method_sig == "push/1" {
result_val = receiver.push(args_array.get(0))
} else if method_sig == "get/1" {
result_val = receiver.get(args_array.get(0))
} else if method_sig == "set/2" {
result_val = receiver.set(args_array.get(0), args_array.get(1))
} else if method_sig == "length/0" {
result_val = receiver.size()
} else if method_sig == "size/0" {
result_val = receiver.size()
} else if method_sig == "isEmpty/0" {
result_val = receiver.isEmpty()
}
// MapBox methods
else if method_sig == "get/1" {
result_val = receiver.get(args_array.get(0))
} else if method_sig == "set/2" {
result_val = receiver.set(args_array.get(0), args_array.get(1))
} else if method_sig == "has/1" {
result_val = receiver.has(args_array.get(0))
} else if method_sig == "size/0" {
result_val = receiver.size()
} else if method_sig == "isEmpty/0" {
result_val = receiver.isEmpty()
} else if method_sig == "delete/1" {
result_val = receiver.delete(args_array.get(0))
} else if method_sig == "keys/0" {
result_val = receiver.keys()
} else if method_sig == "values/0" {
result_val = receiver.values()
} else if method_sig == "methodRef/2" {
// slot 113: Array.methodRef(method_name, arity) -> CallableBox
result_val = receiver.methodRef(args_array.get(0), args_array.get(1))
} else if method_sig == "call/1" {
// slot 501: CallableBox.call(args_array) -> any
// Flatten: pass args_array.get(0) (the actual args ArrayBox) to CallableBox.call
// CallableBox.call will flatten the ArrayBox into individual arguments
result_val = receiver.call(args_array.get(0))
} else if method_sig == "arity/0" {
// slot 500: CallableBox.arity() -> Integer
result_val = receiver.arity()
} else if method_sig == "call/2" {
// slot 210: Map.call(key, args_array) -> any
result_val = receiver.call(args_array.get(0), args_array.get(1))
} else if method_sig == "callAsync/2" {
// slot 211: Map.callAsync(key, args_array) -> FutureBox
result_val = receiver.callAsync(args_array.get(0), args_array.get(1))
} else if method_sig == "callAsync/1" {
// slot 502: CallableBox.callAsync(args_array) -> FutureBox
// Flatten: pass args_array.get(0) (the actual args ArrayBox) to CallableBox.callAsync
// CallableBox.callAsync will flatten the ArrayBox into individual arguments
result_val = receiver.callAsync(args_array.get(0))
}
// Fallback: unknown method
// Note: Method resolution depends on Rust VM's plugin switching mechanism
// (NYASH_VM_DISABLE_STRING_HANDLERS, NYASH_USE_PLUGIN_BUILTINS, etc.)
// Future: Add Selfhost VM's own plugin resolution here
else {
return Result.Err("boxcall: unknown method: " + method_sig)
}
// Store result in destination register
if dst_reg != null {
ValueManagerBox.set(regs, dst_reg, result_val)
}
return Result.Ok(0)
}
// Extract args array from boxcall inst_json
// inst_json: {"op":"boxcall","args":[...],...}
// Returns: ArrayBox of argument values
_extract_args(inst_json, regs) {
// Find "args" field
local args_key = "\"args\":["
local args_start = inst_json.indexOf(args_key)
if args_start < 0 {
// No args field, return empty array
return new ArrayBox()
}
args_start = args_start + args_key.size()
// Find array end
local args_end = StringOps.index_of_from(inst_json, "]", args_start)
if args_end < 0 {
return new ArrayBox()
}
// Extract args array content
local args_content = inst_json.substring(args_start, args_end)
// Create fake mir_call JSON for ArgsExtractorBox
local fake_mir_call = "{\"args\":[" + args_content + "]}"
// Use ArgsExtractorBox to parse and load args
return ArgsExtractorBox.extract_and_load(fake_mir_call, regs)
}
}

View File

@ -0,0 +1,157 @@
// CalleeParserBox - Extract callee information from MirCall JSON
// Single Responsibility: Parse callee field and extract type/name
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
static box CalleeParserBox {
// Extract callee type from mir_call object
// mir_call_json: JSON string like '{"callee":{"type":"Global","name":"print"},...}'
// Returns: "Global" / "Extern" / "Method" / "ModuleFunction" / null on error
extract_type(mir_call_json) {
// Find "callee" field
local callee_key = "\"callee\":"
local callee_start = mir_call_json.indexOf(callee_key)
if callee_start < 0 {
return null
}
callee_start = callee_start + callee_key.size()
// Find callee object end (simplified: find next "type" field)
local type_key = "\"type\":\""
local type_start = StringOps.index_of_from(mir_call_json, type_key, callee_start)
if type_start < 0 {
return null
}
type_start = type_start + type_key.size()
// Extract type value
local type_end = StringOps.index_of_from(mir_call_json, "\"", type_start)
if type_end < 0 {
return null
}
return mir_call_json.substring(type_start, type_end)
}
// Extract callee name from mir_call object
// mir_call_json: JSON string like '{"callee":{"type":"Global","name":"print"},...}'
// Returns: function name string / null on error
extract_name(mir_call_json) {
// Find "name" field (within callee object)
local name_key = "\"name\":\""
local name_start = mir_call_json.indexOf(name_key)
if name_start < 0 {
return null
}
name_start = name_start + name_key.size()
// Extract name value
local name_end = StringOps.index_of_from(mir_call_json, "\"", name_start)
if name_end < 0 {
return null
}
return mir_call_json.substring(name_start, name_end)
}
// Extract full callee object as JSON string (for complex cases)
// mir_call_json: JSON string like '{"callee":{...},"args":[...],...}'
// Returns: callee JSON substring
extract_callee_object(mir_call_json) {
// Find "callee" field
local callee_key = "\"callee\":"
local callee_start = mir_call_json.indexOf(callee_key)
if callee_start < 0 {
return null
}
local obj_start = callee_start + callee_key.size()
// Find object end (simple: find matching })
// Note: This is simplified and assumes no nested objects in callee
local obj_end = StringOps.index_of_from(mir_call_json, "}", obj_start)
if obj_end < 0 {
return null
}
return mir_call_json.substring(obj_start, obj_end + 1)
}
// Extract receiver ValueId from Method callee
// mir_call_json: JSON string like '{"callee":{"type":"Method","receiver":1,"method":"size"},...}'
// Returns: receiver ValueId (integer) / null on error
extract_receiver(mir_call_json) {
// Find "receiver" field
local receiver_key = "\"receiver\":"
local receiver_start = mir_call_json.indexOf(receiver_key)
if receiver_start < 0 {
return null
}
receiver_start = receiver_start + receiver_key.size()
// Extract receiver value (integer)
local digits = StringHelpers.read_digits(mir_call_json, receiver_start)
if digits == "" {
return null
}
return StringHelpers.to_i64(digits)
}
// Extract method name from Method callee
// mir_call_json: JSON string like '{"callee":{"type":"Method","receiver":1,"method":"size"},...}'
// Returns: method name string / null on error
extract_method(mir_call_json) {
// Find "method" field
local method_key = "\"method\":\""
local method_start = mir_call_json.indexOf(method_key)
if method_start < 0 {
return null
}
method_start = method_start + method_key.size()
// Extract method value
local method_end = StringOps.index_of_from(mir_call_json, "\"", method_start)
if method_end < 0 {
return null
}
return mir_call_json.substring(method_start, method_end)
}
// Extract box_type from Constructor callee
// mir_call_json: JSON string like '{"callee":{"type":"Constructor","box_type":"ArrayBox"},...}'
// Returns: box_type string / null on error
extract_box_type(mir_call_json) {
// Find "box_type" field
local box_type_key = "\"box_type\":\""
local box_type_start = mir_call_json.indexOf(box_type_key)
if box_type_start < 0 {
return null
}
box_type_start = box_type_start + box_type_key.size()
// Extract box_type value
local box_type_end = StringOps.index_of_from(mir_call_json, "\"", box_type_start)
if box_type_end < 0 {
return null
}
return mir_call_json.substring(box_type_start, box_type_end)
}
// Extract value id from Value callee
// mir_call_json: '{"callee":{"type":"Value","value":5}, ...}'
// Returns: value register id (integer) / null on error
extract_value_id(mir_call_json) {
local val_key = "\"value\":"
local val_start = mir_call_json.indexOf(val_key)
if val_start < 0 { return null }
val_start = val_start + val_key.size()
// Read digits
local digits = StringHelpers.read_digits(mir_call_json, val_start)
if digits == "" { return null }
return StringHelpers.to_i64(digits)
}
}

View File

@ -0,0 +1,315 @@
// ClosureCallHandlerBox - Closure creation handler (Phase 2 MVP)
// Single Responsibility: Create closure objects with captured variables
// Note: Full closure calling via Callee::Value is Phase 4 Day 16
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
static box ClosureCallHandlerBox {
// Handle Closure creation (Callee::Closure)
// mir_call_json: {"callee":{"type":"Closure","params":["x"],"captures":[["v",5]],"me_capture":null},...}
// dst_reg: destination register (not null for constructors)
// regs: register MapBox
// Returns: Result.Ok(0) or Result.Err(message)
handle(mir_call_json, dst_reg, regs) {
if dst_reg == null {
return Result.Err("Closure: dst_reg must not be null (constructor)")
}
// Extract callee object
local callee_start = mir_call_json.indexOf("\"callee\":")
if callee_start < 0 {
return Result.Err("Closure: callee field not found")
}
callee_start = callee_start + 9 // strlen("\"callee\":")
// Find callee object end
local callee_end = JsonCursorBox.seek_obj_end(mir_call_json, callee_start)
if callee_end < 0 {
return Result.Err("Closure: callee object end not found")
}
local callee_json = mir_call_json.substring(callee_start, callee_end + 1)
// Extract params array
local params_array = me.extract_string_array(callee_json, "params")
if params_array == null {
return Result.Err("Closure: failed to extract params")
}
// Extract captures array
local captures_map = me.extract_captures(callee_json, regs)
if captures_map == null {
return Result.Err("Closure: failed to extract captures")
}
// Extract me_capture (optional)
local me_capture = me.extract_me_capture(callee_json, regs)
// me_capture can be null (valid for closures without me)
// Create closure object (MapBox)
local closure_obj = new MapBox()
closure_obj.set("type", "Closure")
closure_obj.set("params", params_array)
closure_obj.set("captures", captures_map)
if me_capture != null {
closure_obj.set("me_capture", me_capture)
}
// Store closure object in dst register
regs.set(dst_reg, closure_obj)
return Result.Ok(0)
}
// Extract params array from JSON
// Returns ArrayBox of StringBox parameters
extract_string_array(json, field_name) {
local field_key = "\"" + field_name + "\":"
local start = json.indexOf(field_key)
if start < 0 {
return null
}
start = start + field_key.size()
// Skip whitespace
loop(start < json.size()) {
local ch = json.charAt(start)
if ch == " " {
start = start + 1
}
if ch == "\t" {
start = start + 1
}
if ch == "\n" {
start = start + 1
}
if ch == "[" {
break
}
start = start + 1
}
if start >= json.size() {
return null
}
// Find array end
local end_pos = JsonCursorBox.seek_array_end(json, start)
if end_pos < 0 {
return null
}
local array_json = json.substring(start, end_pos + 1)
// Parse array (simple implementation for string arrays)
local result = new ArrayBox()
local i = 1 // skip '['
loop(i < array_json.size()) {
local ch = array_json.charAt(i)
if ch == "]" {
break
}
if ch == "\"" {
// Find string end
local str_start = i + 1
local str_end = str_start
loop(str_end < array_json.size()) {
local ch2 = array_json.charAt(str_end)
if ch2 == "\"" {
break
}
str_end = str_end + 1
}
local param = array_json.substring(str_start, str_end)
result.push(param)
i = str_end + 1
}
i = i + 1
}
return result
}
// Extract captures from JSON and load values from registers
// Returns MapBox: name → value
extract_captures(json, regs) {
local field_key = "\"captures\":"
local start = json.indexOf(field_key)
if start < 0 {
// No captures field = empty captures (valid)
return new MapBox()
}
start = start + field_key.size()
// Skip whitespace to find '['
loop(start < json.size()) {
local ch = json.charAt(start)
if ch == " " {
start = start + 1
}
if ch == "\t" {
start = start + 1
}
if ch == "\n" {
start = start + 1
}
if ch == "[" {
break
}
start = start + 1
}
if start >= json.size() {
return null
}
// Find array end
local end_pos = JsonCursorBox.seek_array_end(json, start)
if end_pos < 0 {
return null
}
local array_json = json.substring(start, end_pos + 1)
// Parse captures array: [["name1", 5], ["name2", 6]]
local result = new MapBox()
local i = 1 // skip '['
loop(i < array_json.size()) {
local ch = array_json.charAt(i)
if ch == "]" {
break
}
if ch == "[" {
// Parse [name, vid] tuple
local tuple_start = i
local tuple_end = tuple_start + 1
loop(tuple_end < array_json.size()) {
local ch2 = array_json.charAt(tuple_end)
if ch2 == "]" {
break
}
tuple_end = tuple_end + 1
}
local tuple_json = array_json.substring(tuple_start, tuple_end + 1)
// Extract name (first element)
local name_start = tuple_json.indexOf("\"")
if name_start >= 0 {
name_start = name_start + 1
local name_end = name_start
loop(name_end < tuple_json.size()) {
local ch3 = tuple_json.charAt(name_end)
if ch3 == "\"" {
break
}
name_end = name_end + 1
}
local name = tuple_json.substring(name_start, name_end)
// Extract vid (second element, after comma)
local comma_pos = tuple_json.indexOf(",")
if comma_pos >= 0 {
local vid_start = comma_pos + 1
// Skip whitespace
loop(vid_start < tuple_json.size()) {
local ch4 = tuple_json.charAt(vid_start)
if ch4 != " " {
break
}
vid_start = vid_start + 1
}
local vid_end = vid_start
loop(vid_end < tuple_json.size()) {
local ch5 = tuple_json.charAt(vid_end)
if ch5 == "]" {
break
}
if ch5 == "," {
break
}
vid_end = vid_end + 1
}
local vid_str = tuple_json.substring(vid_start, vid_end)
local digits = StringHelpers.read_digits(vid_str, 0)
local vid = StringHelpers.to_i64(digits)
// Load value from register
local value = regs.get(vid)
if value == null {
// Register not found, store null (may cause error later)
result.set(name, null)
}
result.set(name, value)
}
}
i = tuple_end + 1
}
i = i + 1
}
return result
}
// Extract me_capture from JSON and load from registers
// Returns value or null
extract_me_capture(json, regs) {
local field_key = "\"me_capture\":"
local start = json.indexOf(field_key)
if start < 0 {
return null
}
start = start + field_key.size()
// Skip whitespace
loop(start < json.size()) {
local ch = json.charAt(start)
if ch == " " {
start = start + 1
}
if ch == "\t" {
start = start + 1
}
if ch == "\n" {
start = start + 1
}
break
}
if start >= json.size() {
return null
}
// Check if null
local substr = json.substring(start, start + 4)
if substr == "null" {
return null
}
// Extract integer vid
local vid_end = start
loop(vid_end < json.size()) {
local ch2 = json.charAt(vid_end)
if ch2 >= "0" {
if ch2 <= "9" {
vid_end = vid_end + 1
}
}
break
}
local vid_str = json.substring(start, vid_end)
local digits = StringHelpers.read_digits(vid_str, 0)
local vid = StringHelpers.to_i64(digits)
// Load value from register
local value = regs.get(vid)
return value // may be null if register not found
}
}

View File

@ -0,0 +1,17 @@
// CompareHandlerBox - Compare instruction handler
// Handles: %dst = %lhs kind %rhs (Eq/Ne/Lt/Le/Gt/Ge)
// Returns: 1 (true) or 0 (false)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/reg_guard.hako" as RegGuardBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/core_bridge_ops.hako" as CoreBridgeOps
static box CompareHandlerBox {
// Handle compare instruction
// inst_json: {"op":"compare","dst":3,"kind":"Eq","lhs":1,"rhs":2}
// Returns: Result.Ok(0) or Result.Err(message)
handle(inst_json, regs) { return CoreBridgeOps.apply_compare(inst_json, regs) }
}

View File

@ -0,0 +1,17 @@
// ConstHandlerBox - Const instruction handler
// Handles: %dst = const value
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/core_bridge_ops.hako" as CoreBridgeOps
static box ConstHandlerBox {
// Handle const instruction
// inst_json: {"op":"const","dst":1,"value":{"type":"i64","value":42}}
// or: {"op":"const","dst":2,"value":{"String":"hello"}}
// Returns: Result.Ok(0) or Result.Err(message)
handle(inst_json, regs) { return CoreBridgeOps.apply_const(inst_json, regs) }
}

View File

@ -0,0 +1,97 @@
// ConstructorCallHandlerBox - Handle Constructor calls (Box instantiation)
// Single Responsibility: Create Box instances with birth() initialization
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/gc/gc_runtime.hako" as GcRuntime
static box ConstructorCallHandlerBox {
// Handle Constructor call
// box_type: "ArrayBox", "MapBox", etc.
// args_array: ArrayBox containing birth() arguments
// dst_reg: destination register (required for Constructor)
// regs: register MapBox
// Returns: Result.Ok(0) or Result.Err(message)
handle(box_type, args_array, dst_reg, regs) {
// Constructor must have destination register
if dst_reg == null {
return Result.Err("Constructor call without destination")
}
// Create Box instance based on box_type
local instance = me._create_box_instance(box_type)
if instance == null {
return Result.Err("Unknown Box type: " + box_type)
}
local argc = args_array.size()
// Store instance before birth so `me` is available during initialization
ValueManagerBox.set(regs, dst_reg, instance)
// GC v0: metrics onlyBoxBirthに近い位置で1カウント
GcRuntime.allocate(box_type)
local birth_result = me._call_birth(instance, argc, args_array, box_type)
if birth_result != null {
// Revert destination on failure to mimic Rust VM behaviour (value unavailable)
ValueManagerBox.set(regs, dst_reg, null)
return birth_result
}
return Result.Ok(0)
}
// Create Box instance by box_type
// Returns: Box instance or null if unsupported
_create_box_instance(box_type) {
// Phase 2 MVP: Support common Box types
if box_type == "ArrayBox" {
return new ArrayBox()
}
else {
if box_type == "MapBox" {
return new MapBox()
}
else {
if box_type == "StringBox" {
return new StringBox()
}
else {
// Unsupported box_type
return null
}
}
}
}
// Call birth() method with arguments
// Returns: Result.Err(message) on failure, null on success
_call_birth(instance, argc, args_array, box_type) {
// Phase 2 MVP: Support 0-2 arguments
// Note: call() primitive requires string literal, so explicit dispatch
if argc == 0 {
instance.birth()
} else {
if argc == 1 {
instance.birth(args_array.get(0))
}
else {
if argc == 2 {
instance.birth(args_array.get(0), args_array.get(1))
}
else {
if argc == 3 {
instance.birth(args_array.get(0), args_array.get(1), args_array.get(2))
}
else {
// Unsupported argument count
return Result.Err("NewBox " + box_type + " failed: birth() with " + StringHelpers.int_to_str(argc) + " arguments not supported (max 3)")
}
}
}
}
// Success
return null
}
}

View File

@ -0,0 +1,16 @@
// CopyHandlerBox - Copy instruction handler
// Handles: %dst = copy %src
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/reg_guard.hako" as RegGuardBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/core_bridge_ops.hako" as CoreBridgeOps
static box CopyHandlerBox {
// Handle copy instruction
// inst_json: {"op":"copy","dst":2,"src":1}
// Returns: Result.Ok(0) or Result.Err(message)
handle(inst_json, regs) { return CoreBridgeOps.apply_copy(inst_json, regs) }
}

View File

@ -0,0 +1,12 @@
// core_bridge.hako — Transitional bridge to Core dispatcher (opt-in)
// Not wired by default; allows phased migration without touching runner.
include "lang/src/vm/core/dispatcher.hako"
static box HakoruneVmCoreBridge {
run(json) {
return NyVmDispatcher.run(json)
}
}
static box HakoruneVmCoreBridgeMain { main(args){ return 0 } }

View File

@ -0,0 +1,484 @@
// core_bridge_ops.hako — Thin wrappers to delegate hakorune-vm ops to Core
// Policy: keep public signatures/return contracts, convert JSON shape and
// register state as needed, and call Core ops.
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/core/state.hako" as NyVmState
using "lang/src/vm/core/ops/const.hako" as NyVmOpConst
using "lang/src/vm/core/ops/binop.hako" as NyVmOpBinOp
using "lang/src/vm/core/ops/ret.hako" as NyVmOpRet
using "lang/src/vm/core/ops/compare.hako" as NyVmOpCompare
using "lang/src/vm/core/ops/branch.hako" as NyVmOpBranch
using "lang/src/vm/core/ops/jump.hako" as NyVmOpJump
using "lang/src/vm/core/ops/phi.hako" as NyVmOpPhi
using "lang/src/vm/core/ops/copy.hako" as NyVmOpCopy
using "lang/src/vm/core/ops/unary.hako" as NyVmOpUnary
using "lang/src/vm/core/ops/typeop.hako" as NyVmOpTypeOp
using "lang/src/vm/core/ops/load.hako" as NyVmOpLoad
using "lang/src/vm/core/ops/store.hako" as NyVmOpStore
using "lang/src/vm/core/ops/mir_call.hako" as NyVmOpMirCall
static box CoreBridgeOps {
// TTL: Delegation map (Phase 20.17)
// - Collections: prefer Core mir_call to centralize semantics and tags.
// * Array: size/push/pop/get/set → delegatedthis file
// * Map : len/iterator(set as len)/set/get → delegatedthis file
// - Strings: remain on HostBridge/ModuleFunctionCallHandlerBox; add Core support later.
// Avoid shadow semantics here until Core gains a string box.
// Apply const via Core
apply_const(inst_json, regs) {
// Prepare Core state and execute
local st = NyVmState.birth()
local rc = NyVmOpConst.handle(inst_json, st)
if rc < 0 { return Result.Err("const: core failure") }
// Reflect dst into hakorune-vm regs
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("const: dst not found") }
local v = NyVmState.get_reg(st, dst)
ValueManagerBox.set(regs, dst, v)
return Result.Ok(0)
}
// Apply binop via Core
apply_binop(inst_json, regs) {
// Extract required fields
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("binop: dst field not found") }
local lhs = JsonFieldExtractor.extract_int(inst_json, "lhs")
if lhs == null { return Result.Err("binop: lhs field not found") }
local rhs = JsonFieldExtractor.extract_int(inst_json, "rhs")
if rhs == null { return Result.Err("binop: rhs field not found") }
// Normalize operation: prefer symbolic 'operation', else map from 'op_kind'
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
if op == null {
local kind = JsonFieldExtractor.extract_string(inst_json, "op_kind")
if kind == null { return Result.Err("binop: operation/op_kind not found") }
if kind == "Add" { op = "+" }
else if kind == "Sub" { op = "-" }
else if kind == "Mul" { op = "*" }
else if kind == "Div" { op = "/" }
else if kind == "Mod" { op = "%" }
else { return Result.Err("binop: unsupported op_kind: " + kind) }
}
// Guard: required src regs must be set
local lhs_val = ValueManagerBox.get(regs, lhs)
if lhs_val == null { return Result.Err("binop: lhs v%" + lhs + " is unset") }
local rhs_val = ValueManagerBox.get(regs, rhs)
if rhs_val == null { return Result.Err("binop: rhs v%" + rhs + " is unset") }
// Rebuild minimal JSON acceptable to Core
local j = "{\"op\":\"binop\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
// Execute via Core with a temporary state containing sources
local st = NyVmState.birth()
NyVmState.set_reg(st, lhs, lhs_val)
NyVmState.set_reg(st, rhs, rhs_val)
local rc = NyVmOpBinOp.handle(j, st)
if rc < 0 { return Result.Err("binop: core failure") }
local out = NyVmState.get_reg(st, dst)
ValueManagerBox.set(regs, dst, out)
return Result.Ok(0)
}
// Load ret value via Core (instruction form)
load_ret_from_instruction(inst_json, regs) {
local vid = JsonFieldExtractor.extract_int(inst_json, "value")
if vid == null { return Result.Err("ret: value field not found") }
local v = ValueManagerBox.get(regs, vid)
if v == null { return Result.Err("ret: register v%" + vid + " is unset") }
local st = NyVmState.birth()
NyVmState.set_reg(st, vid, v)
local out = NyVmOpRet.handle(inst_json, st)
return Result.Ok(out)
}
// Apply compare via Core
apply_compare(inst_json, regs) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("compare: dst field not found") }
local lhs = JsonFieldExtractor.extract_int(inst_json, "lhs")
if lhs == null { return Result.Err("compare: lhs field not found") }
local rhs = JsonFieldExtractor.extract_int(inst_json, "rhs")
if rhs == null { return Result.Err("compare: rhs field not found") }
// Normalize kind -> operation
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
if op == null {
local kind = JsonFieldExtractor.extract_string(inst_json, "kind")
if kind == null { return Result.Err("compare: kind/operation not found") }
if kind == "Eq" { op = "==" }
else if kind == "Ne" { op = "!=" }
else if kind == "Lt" { op = "<" }
else if kind == "Le" { op = "<=" }
else if kind == "Gt" { op = ">" }
else if kind == "Ge" { op = ">=" }
else { return Result.Err("compare: unsupported kind: " + kind) }
}
// Guards
local lv = ValueManagerBox.get(regs, lhs)
if lv == null { return Result.Err("compare: lhs v%" + lhs + " is unset") }
local rv = ValueManagerBox.get(regs, rhs)
if rv == null { return Result.Err("compare: rhs v%" + rhs + " is unset") }
// Build minimal JSON
local j = "{\"op\":\"compare\",\"dst\":" + dst + ",\"operation\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
local st = NyVmState.birth()
NyVmState.set_reg(st, lhs, lv)
NyVmState.set_reg(st, rhs, rv)
local rc = NyVmOpCompare.handle(j, st)
if rc < 0 { return Result.Err("compare: core failure") }
ValueManagerBox.set(regs, dst, NyVmState.get_reg(st, dst))
return Result.Ok(0)
}
// Branch terminator via Core; returns Ok(next_bb)
select_branch_next_from_terminator(term_json, regs) {
// Extract fields from terminator shape
local cond = JsonFieldExtractor.extract_int(term_json, "condition")
if cond == null { return Result.Err("branch: condition not found") }
local then_bb = JsonFieldExtractor.extract_int(term_json, "then_bb")
if then_bb == null { return Result.Err("branch: then_bb not found") }
local else_bb = JsonFieldExtractor.extract_int(term_json, "else_bb")
if else_bb == null { return Result.Err("branch: else_bb not found") }
// Guard and prepare state
local cv = ValueManagerBox.get(regs, cond)
if cv == null { cv = 0 }
local st = NyVmState.birth()
NyVmState.set_reg(st, cond, cv)
// Minimal JSON for Core
local j = "{\"op\":\"branch\",\"cond\":" + cond + ",\"then\":" + then_bb + ",\"else\":" + else_bb + "}"
local nb = NyVmOpBranch.handle(j, st)
if nb < 0 { return Result.Err("branch: core failure") }
return Result.Ok(nb)
}
// Jump terminator via Core; returns Ok(next_bb)
select_jump_next_from_terminator(term_json) {
local target = JsonFieldExtractor.extract_int(term_json, "target")
if target == null { return Result.Err("jump: target not found") }
local j = "{\"op\":\"jump\",\"target\":" + target + "}"
local nb = NyVmOpJump.handle(j)
if nb < 0 { return Result.Err("jump: core failure") }
return Result.Ok(nb)
}
// Apply single PHI via Core
apply_phi(inst_json, regs, predecessor) {
// Collect candidate source vids from inputs and populate state
local st = NyVmState.birth()
// Minimal scan: find inputs array and iterate pairs [bb,vid]
local p = inst_json.indexOf("\"inputs\":[")
if p >= 0 {
local lb = inst_json.indexOf("[", p)
if lb >= 0 {
local rb = JsonFieldExtractor.seek_array_end != null ? JsonFieldExtractor.seek_array_end(inst_json, lb) : -1
// Fallback: if JsonFieldExtractor provides no seek helper, approximate by using core reader later
local arr = rb > lb ? inst_json.substring(lb+1, rb) : inst_json.substring(lb+1, inst_json.size())
// Walk for numbers in pairs
local pos = 0
loop(pos < arr.size()) {
// look for '[' of a pair
local c = arr.substring(pos,pos+1)
if c != "[" { pos = pos + 1 continue }
// bb
pos = pos + 1
// skip spaces
loop(pos < arr.size()) { local ch = arr.substring(pos,pos+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { pos = pos + 1 continue } break }
// read first int (bb) — ignored here
// advance to comma
local comma = arr.indexOf(",", pos)
if comma < 0 { break }
pos = comma + 1
loop(pos < arr.size()) { local ch2 = arr.substring(pos,pos+1) if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { pos = pos + 1 continue } break }
// read vid digits
local i = pos
local ds = ""
loop(i < arr.size()) { local ch3 = arr.substring(i,i+1) if ch3 >= "0" && ch3 <= "9" { ds = ds + ch3 i = i + 1 continue } break }
if ds != "" {
local vid = ds + 0 // to i64 via implicit
// populate candidate with current regs value or 0
local vv = ValueManagerBox.get(regs, vid)
if vv == null { vv = 0 }
NyVmState.set_reg(st, vid, vv)
}
// seek to closing ']' of pair
local close = arr.indexOf("]", i)
pos = close >= 0 ? close + 1 : i
}
}
}
// Delegate to Core
local rc = NyVmOpPhi.handle(inst_json, st, predecessor)
if rc < 0 { return Result.Err("phi: core failure") }
// Reflect dst
// Extract dst
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null { return Result.Err("phi: dst not found") }
local v = NyVmState.get_reg(st, dst)
ValueManagerBox.set(regs, dst, v)
return Result.Ok(0)
}
// Copy via Core
apply_copy(inst_json, regs) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
local src = JsonFieldExtractor.extract_int(inst_json, "src")
if dst == null || src == null { return Result.Err("copy: dst or src field not found") }
local sv = ValueManagerBox.get(regs, src)
if sv == null { return Result.Err("copy: src v%" + src + " is unset") }
local st = NyVmState.birth()
NyVmState.set_reg(st, src, sv)
local rc = NyVmOpCopy.handle(inst_json, st)
if rc < 0 { return Result.Err("copy: core failure") }
ValueManagerBox.set(regs, dst, NyVmState.get_reg(st, dst))
return Result.Ok(0)
}
// Unary via Core
apply_unary(inst_json, regs) {
// Determine operand
local opnd = JsonFieldExtractor.extract_int(inst_json, "src")
if opnd == null { opnd = JsonFieldExtractor.extract_int(inst_json, "operand") }
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
if dst == null || opnd == null { return Result.Err("unaryop: malformed") }
local vv = ValueManagerBox.get(regs, opnd)
if vv == null { return Result.Err("unaryop: operand v%" + opnd + " is unset") }
local st = NyVmState.birth(); NyVmState.set_reg(st, opnd, vv)
local rc = NyVmOpUnary.handle(inst_json, st)
if rc < 0 { return Result.Err("unaryop: core failure") }
ValueManagerBox.set(regs, dst, NyVmState.get_reg(st, dst))
return Result.Ok(0)
}
// Typeop via Core
apply_typeop(inst_json, regs, mem) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
local val = JsonFieldExtractor.extract_int(inst_json, "value")
if dst == null || val == null { return Result.Err("typeop: dst/value not found") }
local op = JsonFieldExtractor.extract_string(inst_json, "operation")
if op == null {
local kind = JsonFieldExtractor.extract_string(inst_json, "op_kind")
if kind == null { return Result.Err("typeop: op_kind/operation not found") }
if kind == "Check" { op = "check" }
else if kind == "Cast" { op = "cast" }
else { return Result.Err("typeop: invalid op_kind: " + kind) }
}
local vv = ValueManagerBox.get(regs, val)
if vv == null { return Result.Err("typeop: value v%" + val + " is unset") }
// Build minimal Core JSON: {op:"typeop", operation:"check|cast", src, dst}
local j = "{\"op\":\"typeop\",\"operation\":\"" + op + "\",\"src\":" + val + ",\"dst\":" + dst + "}"
local st = NyVmState.birth(); NyVmState.set_reg(st, val, vv)
local rc = NyVmOpTypeOp.handle(j, st)
if rc < 0 { return Result.Err("typeop: core failure") }
ValueManagerBox.set(regs, dst, NyVmState.get_reg(st, dst))
return Result.Ok(0)
}
// Load via Core
apply_load(inst_json, regs, mem) {
local dst = JsonFieldExtractor.extract_int(inst_json, "dst")
local ptr = JsonFieldExtractor.extract_int(inst_json, "ptr")
if dst == null || ptr == null { return Result.Err("load: dst/ptr not found") }
local rc = NyVmOpLoad.handle(inst_json, NyVmState.birth(), mem)
if rc < 0 { return Result.Err("load: core failure") }
ValueManagerBox.set(regs, dst, mem.get("" + ptr))
return Result.Ok(0)
}
// Store via Core
apply_store(inst_json, regs, mem) {
local val = JsonFieldExtractor.extract_int(inst_json, "value")
local ptr = JsonFieldExtractor.extract_int(inst_json, "ptr")
if val == null || ptr == null { return Result.Err("store: value/ptr not found") }
local vv = ValueManagerBox.get(regs, val)
if vv == null { return Result.Err("store: value v%" + val + " is unset") }
local st = NyVmState.birth(); NyVmState.set_reg(st, val, vv)
local rc = NyVmOpStore.handle(inst_json, st, mem)
if rc < 0 { return Result.Err("store: core failure") }
return Result.Ok(0)
}
// Collection helpers (Array/Map metadata shortcuts). Returns null when not handled.
try_method_collection(method, mir_call_json, args_array, dst_reg, regs, receiver_id) {
if method == "size" && ValueManagerBox.has_array_meta(regs, receiver_id) {
local size = ValueManagerBox.get_array_size(regs, receiver_id)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, size) }
return Result.Ok(0)
}
if method == "push" && ValueManagerBox.has_array_meta(regs, receiver_id) {
local next = ValueManagerBox.inc_array_size(regs, receiver_id, 1)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, next) }
return Result.Ok(0)
}
if method == "pop" && ValueManagerBox.has_array_meta(regs, receiver_id) {
local next = ValueManagerBox.inc_array_size(regs, receiver_id, -1)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, next) }
return Result.Ok(0)
}
if method == "len" && ValueManagerBox.ensure_map_meta(regs, receiver_id) != null {
local len = ValueManagerBox.get_map_len(regs, receiver_id)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, len) }
return Result.Ok(0)
}
if method == "iterator" && ValueManagerBox.ensure_map_meta(regs, receiver_id) != null {
// Mirror Core: iterator() returns current len metadata in this MVP
local len = ValueManagerBox.get_map_len(regs, receiver_id)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, len) }
return Result.Ok(0)
}
if method == "set" && ValueManagerBox.ensure_map_meta(regs, receiver_id) != null {
if args_array.size() < 1 {
return Result.Err("method(set): missing key argument")
}
local key_val = args_array.get(0)
local existed = ValueManagerBox.mark_map_entry(regs, receiver_id, key_val)
if !existed { ValueManagerBox.inc_map_len(regs, receiver_id, 1) }
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, 0) }
return Result.Ok(0)
}
// Map.get — delegate to Core for value retrieval (metadata alone is insufficient)
if method == "get" && ValueManagerBox.ensure_map_meta(regs, receiver_id) != null {
if dst_reg == null { return Result.Err("method(get): missing destination register") }
// Synthesize full instruction JSON for Core: {op:mir_call, dst: D, mir_call: <json>}
local inst = "{\"op\":\"mir_call\",\"dst\":" + StringHelpers.int_to_str(dst_reg) + ",\"mir_call\":" + mir_call_json + "}"
// Prepare Core state with receiver and first argument loaded
local st = NyVmState.birth()
// Receiver value
local recv_val = ValueManagerBox.get(regs, receiver_id)
if recv_val != null { NyVmState.set_reg(st, receiver_id, recv_val) }
// First arg id (key)
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
if ids != null && ids.size() > 0 {
local key_id = ids.get(0)
local key_val = ValueManagerBox.get(regs, key_id)
if key_val != null { NyVmState.set_reg(st, key_id, key_val) }
}
// Execute via Core
local rc = NyVmOpMirCall.handle(inst, st)
if rc < 0 { return Result.Err("method(get): core failure") }
ValueManagerBox.set(regs, dst_reg, NyVmState.get_reg(st, dst_reg))
return Result.Ok(0)
}
// Array.get — delegate to Core for value retrieval
if method == "get" {
if dst_reg == null { return Result.Err("method(get): missing destination register") }
local inst = "{\"op\":\"mir_call\",\"dst\":" + StringHelpers.int_to_str(dst_reg) + ",\"mir_call\":" + mir_call_json + "}"
local st = NyVmState.birth()
// Receiver
local recv_val = ValueManagerBox.get(regs, receiver_id)
if recv_val != null { NyVmState.set_reg(st, receiver_id, recv_val) }
// Arg[0]
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
if ids != null && ids.size() > 0 {
local key_id = ids.get(0)
local key_val = ValueManagerBox.get(regs, key_id)
if key_val != null { NyVmState.set_reg(st, key_id, key_val) }
}
local rc = NyVmOpMirCall.handle(inst, st)
if rc < 0 { return Result.Err("method(get): core failure") }
ValueManagerBox.set(regs, dst_reg, NyVmState.get_reg(st, dst_reg))
return Result.Ok(0)
}
// Array.set — delegate to Core for bounds/auto-extend semantics
if method == "set" {
// dst may be null (void), Core handles optional dst
local dst_id = -1
if dst_reg != null { dst_id = dst_reg }
local inst = "{\"op\":\"mir_call\",\"dst\":" + StringHelpers.int_to_str(dst_id) + ",\"mir_call\":" + mir_call_json + "}"
local st = NyVmState.birth()
// Receiver
local recv_val2 = ValueManagerBox.get(regs, receiver_id)
if recv_val2 != null { NyVmState.set_reg(st, receiver_id, recv_val2) }
// Args[0], Args[1]
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
local ids2 = ArgsExtractorBox.extract_ids(mir_call_json)
if ids2 != null {
local i = 0
loop(i < ids2.size()) {
local id = ids2.get(i)
local val = ValueManagerBox.get(regs, id)
if val != null { NyVmState.set_reg(st, id, val) }
i = i + 1
}
}
local rc2 = NyVmOpMirCall.handle(inst, st)
if rc2 < 0 { return Result.Err("method(set): core failure") }
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, NyVmState.get_reg(st, dst_reg)) }
return Result.Ok(0)
}
return null
}
try_modulefn_collection(name, mir_call_json, args_array, dst_reg, regs) {
local want = me._canonical_modulefn_name(name, args_array.size())
if want == "ArrayBox.len/0" {
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
local recv_id = null
if ids != null && ids.size() > 0 { recv_id = ids.get(0) }
local len = 0
if recv_id != null { len = ValueManagerBox.get_array_size(regs, recv_id) }
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, len) }
return Result.Ok(0)
}
if want == "MapBox.len/0" {
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
local recv_id = null
if ids != null && ids.size() > 0 { recv_id = ids.get(0) }
if recv_id != null { ValueManagerBox.ensure_map_meta(regs, recv_id) }
local len = 0
if recv_id != null { len = ValueManagerBox.get_map_len(regs, recv_id) }
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, len) }
return Result.Ok(0)
}
if want == "StringHelpers.int_to_str/1" {
if dst_reg == null { return Result.Err("modulefn(int_to_str): missing destination register") }
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
if ids == null || ids.size() < 1 { return Result.Err("modulefn(int_to_str): missing arg") }
local id0 = ids.get(0)
local val0 = ValueManagerBox.get(regs, id0)
local s = "" + val0
ValueManagerBox.set(regs, dst_reg, s)
return Result.Ok(0)
}
if want == "StringHelpers.to_i64/1" {
if dst_reg == null { return Result.Err("modulefn(to_i64): missing destination register") }
using "lang/src/vm/hakorune-vm/args_extractor.hako" as ArgsExtractorBox
local ids = ArgsExtractorBox.extract_ids(mir_call_json)
if ids == null || ids.size() < 1 { return Result.Err("modulefn(to_i64): missing arg") }
local id0 = ids.get(0)
local val0 = ValueManagerBox.get(regs, id0)
local s = "" + val0
// Allow optional sign + digits only
local i = 0
if s.size() > 0 && (s.substring(0,1) == "+" || s.substring(0,1) == "-") { i = 1 }
local ok = (s.size() > i)
loop(i < s.size()) {
local ch = s.substring(i,i+1)
if !(ch >= "0" && ch <= "9") { ok = false break }
i = i + 1
}
if !ok { return Result.Err("[core/mir_call] string to_i64 bad arg") }
// Convert via helper (guards overflow semantics centrally)
local n = StringHelpers.to_i64(s)
ValueManagerBox.set(regs, dst_reg, n)
return Result.Ok(0)
}
return null
}
_canonical_modulefn_name(name, argc) {
if name == null { return "" }
if name.indexOf("/") >= 0 { return name }
return name + "/" + StringHelpers.int_to_str(argc)
}
}
static box CoreBridgeOpsMain { main(args){ return 0 } }

View File

@ -0,0 +1,14 @@
using "lang/src/vm/hakorune-vm/hakorune_vm_core.hako" as HakoruneVmCore
static box Main {
// Usage: hakorune-vm-exe <mir.json>
main(args) {
local path = null
if args { if args.size() > 0 { @p = args.get(0) if p { path = p } } }
if path == null {
print("usage: hakorune-vm-exe <mir.json>")
return -1
}
return HakoruneVmCore.run_from_file(path)
}
}

View File

@ -0,0 +1,7 @@
// error_builder.hako — ErrorBuilderBox
// Responsibility: build consistent error messages
static box ErrorBuilderBox {
unset_reg(label, id_str) { return label + " v%" + id_str + " is unset" }
missing_key(name) { return name + " not found" }
}

View File

@ -0,0 +1,45 @@
// ExternCallHandlerBox - Handle Extern function calls
// Single Responsibility: Execute external/runtime functions
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box ExternCallHandlerBox {
// Handle extern function call
// name: extern function name (e.g., "hostbridge.box_new")
// args_array: ArrayBox containing argument values
// dst_reg: destination register (null if no return value)
// regs: register MapBox
// mem: memory MapBox
// Returns: Result.Ok(0) or Result.Err(message)
_policy_allowlist_on() {
local p = env.get("HAKO_NYVM_EXTERN_POLICY")
if p == null { p = env.get("NYASH_NYVM_EXTERN_POLICY") }
if p == null { return 0 }
local s = "" + p
s = s.to_lower()
if s == "allow" || s == "allowlist" || s == "on" || s == "1" { return 1 }
return 0
}
_allow(name) {
// Minimal allowlist for Phase 20.12b
if name == "env.console.log" { return 1 }
if name == "env.console.warn" { return 1 }
if name == "env.console.error" { return 1 }
return 0
}
handle(name, args_array, dst_reg, regs, mem) {
// Policy: optional allowlist. When enabled, restrict externs to a minimal set.
if me._policy_allowlist_on() == 1 {
if me._allow(name) == 0 { return Result.Err("extern not allowed: " + name) }
}
// Delegation: forward externs to Rust via hostbridge trampoline.
if args_array == null { args_array = new ArrayBox() }
local ret
ret = hostbridge.extern_invoke(name, args_array)
if dst_reg != null { ValueManagerBox.set(regs, dst_reg, ret) }
return Result.Ok(0)
}
}

View File

@ -0,0 +1,22 @@
// function_locator.hako — FunctionLocatorBox
// Responsibility: locate first function object in MIR(JSON)
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
static box FunctionLocatorBox {
locate(mir_json) {
// naive: first '{'..balanced '}'
local s = JsonCursorBox.index_of_from(mir_json, "{", 0)
if s < 0 { return Result.Err("function object not found") }
local e = JsonScanGuardBox.seek_obj_end(mir_json, s, 200000)
if e < 0 { return Result.Err("function object end not found") }
local obj = mir_json.substring(s, e+1)
local meta = new MapBox()
meta.set("start", s)
meta.set("end", e)
meta.set("content", obj)
return Result.Ok(meta)
}
}

View File

@ -0,0 +1,13 @@
// gc_hooks.hako — HakoruneVM GC safepoint hooks (v0: metrics-only collection)
// Responsibility: provide a single safepoint entry the VM can call at well-defined
// boundaries (MirCall, loop back-edges, etc.). In v0, this only triggers the
// GC runtime's collect_if_needed() which is gated to default OFF.
using "lang/src/vm/gc/gc_runtime.hako" as GcRuntime
static box GcHooks {
safepoint() {
// v0: no-op unless GC gating enables collection; metrics only otherwise.
GcRuntime.collect_if_needed()
}
}

View File

@ -0,0 +1,50 @@
// GlobalCallHandlerBox - Handle Global function calls
// Single Responsibility: Execute global functions (print, etc.)
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
static box GlobalCallHandlerBox {
// Handle global function call
// name: function name (e.g., "print")
// args_array: ArrayBox containing argument values
// dst_reg: destination register (null if no return value)
// regs: register MapBox
// Returns: Result.Ok(0) or Result.Err(message)
handle(name, args_array, dst_reg, regs) {
if name == "print" {
return me._handle_print(args_array, dst_reg, regs)
}
// Unsupported global function
return Result.Err("global_call: unsupported function: " + name)
}
// Handle print() function
// Phase 1: Simple implementation - print integer value
// Future: Support multiple types, string formatting
_handle_print(args_array, dst_reg, regs) {
// Check argument count
local argc = args_array.size()
if argc != 1 {
return Result.Err("print: expected 1 argument, got " + StringHelpers.int_to_str(argc))
}
// Get first argument
local arg_value = args_array.get(0)
// Print value (convert to string first)
// Phase 1: Assume integer value
local output = StringHelpers.int_to_str(arg_value)
print(output)
// print() has no return value (dst_reg should be null)
// If dst_reg is provided, set it to 0
if dst_reg != null {
ValueManagerBox.set(regs, dst_reg, 0)
}
return Result.Ok(0)
}
}

View File

@ -0,0 +1,235 @@
// hakorune_vm_core.hako — Phase 1: 最小動作VMConst/BinOp/Ret
// Strategy: @match 最大限活用、Result @enum でエラーハンドリング
// Reference: INSTRUCTION_SET.md, LLVM Python, Rust VM
// Phase 1 Day 3: 制御フロー実装Branch/Jump/Phi- 箱化モジュール化
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
using "lang/src/vm/boxes/result_box.hako" as Result
// Phase 1 Day 3: 箱化モジュール化
using "lang/src/vm/hakorune-vm/block_mapper.hako" as BlockMapperBox
using "lang/src/vm/hakorune-vm/terminator_handler.hako" as TerminatorHandlerBox
using "lang/src/vm/hakorune-vm/phi_handler.hako" as PhiHandlerBox
using "lang/src/vm/hakorune-vm/function_locator.hako" as FunctionLocatorBox
using "lang/src/vm/hakorune-vm/blocks_locator.hako" as BlocksLocatorBox
using "lang/src/vm/hakorune-vm/block_iterator.hako" as BlockIteratorBox
// Phase 1 Day 3 リファクタリング: 命令ハンドラー箱化
using "lang/src/vm/hakorune-vm/instruction_dispatcher.hako" as InstructionDispatcherBox
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/instrs_locator.hako" as InstrsLocatorBox
using "lang/src/shared/mir/mir_io_box.hako" as MirIoBox
using "lang/src/vm/hakorune-vm/ret_value_loader.hako" as RetValueLoaderBox
static box HakoruneVmCore {
// Unchecked entry: skip MIR(JSON) schema validation (Gate C smoke/helper)
run_unchecked(mir_json) {
// Initialize registers and memory
local regs = new MapBox()
local mem = new MapBox()
// Execute
local result = me._execute_blocks(mir_json, regs, mem)
if result.is_Ok() { return result.as_Ok() }
else { print("[ERROR] Hakorune-VM: " + result.as_Err()) return -1 }
}
// Read MIR(JSON) from file and run
run_from_file(path) {
local fb = new FileBox()
local ok = fb.open(path, "r")
if ok == false {
print("[ERROR] cannot open MIR file: " + path)
return -1
}
local s = fb.read()
fb.close()
return me.run(s)
}
// Main entry point
run(mir_json) {
// Validate MIR JSON via MirIoBox (Phase A)
{
local v = MirIoBox.validate(mir_json)
if v.is_Err() { print("[ERROR] MIR validate: " + v.as_Err()) return -1 }
}
// Initialize registers and memory
local regs = new MapBox()
local mem = new MapBox()
// Execute from block 0 (Phase 1 Day 3: multiple blocks support)
local result = me._execute_blocks(mir_json, regs, mem)
// @match Result for error handling
if result.is_Ok() {
return result.as_Ok()
} else {
print("[ERROR] Hakorune-VM: " + result.as_Err())
return -1
}
}
// Phase 1 Day 3: Execute multiple blocks with control flow
_execute_blocks(mir_json, regs, mem) {
// Build block map
local block_map_result = BlockMapperBox.build_map(mir_json)
if block_map_result.is_Err() {
return block_map_result
}
local block_map = block_map_result.as_Ok()
// Determine entry block id (prefer explicit function.entry; fallback to first block id; final fallback 0)
local current_bb = 0
{
local loc_func = FunctionLocatorBox.locate(mir_json)
// try to read function.entry if present (tolerant)
if !loc_func.is_Err() {
local func_meta = loc_func.as_Ok()
local func_json = func_meta.get("content")
// read entry key if present
{
local k = r#""entry""#
local p = func_json.indexOf(k)
if p >= 0 {
p = p + k.size()
loop(p < func_json.size()) { local ch = func_json.substring(p,p+1) if ch == ":" { p = p + 1 break } if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
loop(p < func_json.size()) { local ch = func_json.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digits = StringHelpers.read_digits(func_json, p)
if digits != "" { current_bb = StringHelpers.to_i64(digits) }
}
}
local loc_blocks = BlocksLocatorBox.locate(func_json)
if !loc_blocks.is_Err() {
local blocks_meta = loc_blocks.as_Ok()
local content = blocks_meta.get("content")
local it = BlockIteratorBox.next(content, 0)
if !it.is_Err() {
local first = it.as_Ok().get("obj")
local key_id = "\"id\":"
local is = first.indexOf(key_id)
if is >= 0 {
is = is + key_id.size()
// skip ws
loop(is < first.size()) {
local ch = first.substring(is, is+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { is = is + 1 continue }
break
}
local digits = StringHelpers.read_digits(first, is)
if digits != "" { current_bb = StringHelpers.to_i64(digits) }
}
}
}
}
}
local predecessor = -1
local max_iterations = 1000
local iterations = 0
loop(current_bb >= 0 && iterations < max_iterations) {
iterations = iterations + 1
// Get current block
local block_json = block_map.get(StringHelpers.int_to_str(current_bb))
if block_json == null {
return Result.Err("block not found: " + StringHelpers.int_to_str(current_bb))
}
// Execute PHI instructions first
local phi_result = PhiHandlerBox.handle_phi_instructions(block_json, regs, predecessor)
if phi_result.is_Err() {
return phi_result
}
// Execute normal instructions (non-PHI)
local exec_result = me._execute_instructions_in_block(block_json, regs, mem)
if exec_result.is_Err() {
return exec_result
}
// If a ret was executed within instructions, finish here
if regs.has("__ret_seen__") || block_json.indexOf("\"op\":\"ret\"") >= 0 {
return Result.Ok(exec_result.as_Ok())
}
// Execute terminator
local term_result = TerminatorHandlerBox.handle_terminator(block_json, regs)
if term_result.is_Err() {
// Tolerate missing terminator as empty return (dev-only relaxation)
if term_result.as_Err() == "terminator not found" {
return Result.Ok(0)
}
return term_result
}
local term_map = term_result.as_Ok()
local term_type = term_map.get("type")
if term_type == "ret" {
// Load return value from register (via unified loader)
return RetValueLoaderBox.load_from_terminator(term_map, regs)
}
if term_type == "jump" || term_type == "branch" {
predecessor = current_bb
local next_id = term_map.get("next_bb")
if block_map.get(StringHelpers.int_to_str(next_id)) == null { return Result.Err("invalid next_bb: " + StringHelpers.int_to_str(next_id)) }
current_bb = next_id
continue
}
return Result.Err("unknown terminator type: " + term_type)
}
if iterations >= max_iterations {
return Result.Err("max iterations reached (infinite loop?)")
}
return Result.Ok(0)
}
// Execute instructions in a block (excluding PHI)
_execute_instructions_in_block(block_json, regs, mem) {
// Find instructions array
local key_insts = "\"instructions\":["
local insts_start = block_json.indexOf(key_insts)
if insts_start < 0 {
// No instructions (empty block)
return Result.Ok(0)
}
insts_start = insts_start + key_insts.size()
// Find instructions array end
local insts_end = JsonScanGuardBox.seek_array_end(block_json, insts_start - 1, 200000)
if insts_end < 0 {
return Result.Err("instructions array end not found")
}
local insts_json = block_json.substring(insts_start, insts_end)
// Execute instructions sequentially
return me._execute_instructions(insts_json, regs, mem)
}
// Execute instructions sequentially
_execute_instructions(insts_json, regs, mem) {
local pos = 0
local len = insts_json.size()
local last_ret_value = 0
loop(pos < len) {
// Skip whitespace
local ch = insts_json.substring(pos, pos + 1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" {
pos = pos + 1
continue
}
// Skip comma
if ch == "," {
pos = pos + 1
continue
}
// Find instruction object
if ch == "{" {
local inst_end = JsonCursorBox.seek_obj_end(insts_json, pos)
if inst_end < 0 {
return Result.Err("instruction object end not found")
}
local inst_json = insts_json.substring(pos, inst_end + 1)
// Dispatch instruction using InstructionDispatcherBox
local result = InstructionDispatcherBox.dispatch(inst_json, regs, mem)
// Check for errors or return value
if result.is_Err() {
return result
}
// Check if instruction is Ret (store return value)
if inst_json.indexOf("\"op\":\"ret\"") >= 0 {
last_ret_value = result.as_Ok()
regs.set("__ret_seen__", 1)
return Result.Ok(last_ret_value)
}
pos = inst_end + 1
continue
}
// Skip other characters
pos = pos + 1
}
return Result.Ok(last_ret_value)
}
}

View File

@ -0,0 +1,97 @@
// instrs_locator.hako — InstrsLocatorBox
// Responsibility: locate instructions[] in block JSON (empty allowed)
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
using "lang/src/vm/hakorune-vm/json_normalize_box.hako" as JsonNormalizeBox
static box InstrsLocatorBox {
locate(block_json) {
local key = r#""instructions""#
local k = JsonCursorBox.index_of_from(block_json, key, 0)
if k < 0 {
local meta0 = new MapBox()
meta0.set("found", 0)
meta0.set("single", 0)
meta0.set("content", "")
return Result.Ok(meta0)
}
local i = k + key.size()
// find ':' then '['
loop(i < block_json.size()) {
local ch = block_json.substring(i,i+1)
if ch == ":" { i = i + 1 break }
if ch == " " || ch == "
" || ch == "
" || ch == " " { i = i + 1 continue }
return Result.Err("instructions key format invalid")
}
loop(i < block_json.size()) {
local ch = block_json.substring(i,i+1)
if ch == " " || ch == "
" || ch == "
" || ch == " " { i = i + 1 continue }
if ch == "[" { break }
return Result.Err("instructions array not found")
}
local arr_end = JsonScanGuardBox.seek_array_end(block_json, i, 200000)
if arr_end < 0 { return Result.Err("instructions array end not found") }
// pointers inside block_json
local start = i + 1
local endi = arr_end
// empty fast path
local j = start
loop(j < endi) {
local ch = block_json.substring(j, j+1)
if ch == " " || ch == "
" || ch == "
" || ch == " " { j = j + 1 continue }
break
}
if j >= endi {
local meta1 = new MapBox()
meta1.set("found", 1)
meta1.set("single", 0)
meta1.set("content", "")
return Result.Ok(meta1)
}
// slice content
local content = block_json.substring(start, endi)
// normalize if large
if content.size() > 8192 { content = JsonNormalizeBox.normalize(content, 500000) }
// detect single-object fast path
local single_flag = 0
{
local cs = 0
local ce = content.size() - 1
loop(cs <= ce) {
local ch2 = content.substring(cs, cs+1)
if ch2 == " " || ch2 == "
" || ch2 == "
" || ch2 == " " { cs = cs + 1 continue }
break
}
loop(ce >= cs) {
local ch3 = content.substring(ce, ce+1)
if ch3 == " " || ch3 == "
" || ch3 == "
" || ch3 == " " { ce = ce - 1 continue }
break
}
if cs <= ce {
local first = content.substring(cs, cs+1)
local last = content.substring(ce, ce+1)
if first == "{" && last == "}" {
local sep = JsonCursorBox.index_of_from(content, "},", cs)
if sep < 0 { single_flag = 1 }
}
}
}
local meta = new MapBox()
meta.set("found", 1)
meta.set("single", single_flag)
meta.set("content", content)
return Result.Ok(meta)
}
}

View File

@ -0,0 +1,27 @@
// instruction_array_locator.hako — InstructionArrayLocatorBox
// Responsibility: robustly locate instructions[] segment in a block JSON
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/vm/hakorune-vm/json_scan_guard.hako" as JsonScanGuardBox
static box InstructionArrayLocatorBox {
// Locate instructions array in a block JSON
// Returns Ok({ start: <idx>, end: <idx>, content: <string> })
locate(block_json) {
local key = r#""instructions""#
local kpos = JsonCursorBox.index_of_from(block_json, key, 0)
if kpos < 0 { return Result.Err("instructions key not found") }
// find '[' after the key
local lbr = JsonCursorBox.index_of_from(block_json, r#"["#, kpos)
if lbr < 0 { return Result.Err("instructions array not found") }
local arr_end = JsonScanGuardBox.seek_array_end(block_json, lbr, 200000)
if arr_end < 0 { return Result.Err("instructions array end not found") }
local content = block_json.substring(lbr + 1, arr_end)
local meta = new MapBox()
meta.set("start", lbr + 1)
meta.set("end", arr_end)
meta.set("content", content)
return Result.Ok(meta)
}
}

View File

@ -0,0 +1,71 @@
// InstructionDispatcherBox - Dispatch instructions to specific handlers
// Single Responsibility: Extract op field and route to correct handler
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/hakorune-vm/json_field_extractor.hako" as JsonFieldExtractor
using "lang/src/vm/hakorune-vm/value_manager.hako" as ValueManagerBox
using "lang/src/vm/hakorune-vm/const_handler.hako" as ConstHandlerBox
using "lang/src/vm/hakorune-vm/binop_handler.hako" as BinOpHandlerBox
using "lang/src/vm/hakorune-vm/compare_handler.hako" as CompareHandlerBox
using "lang/src/vm/hakorune-vm/copy_handler.hako" as CopyHandlerBox
using "lang/src/vm/hakorune-vm/unaryop_handler.hako" as UnaryOpHandlerBox
using "lang/src/vm/hakorune-vm/load_handler.hako" as LoadHandlerBox
using "lang/src/vm/hakorune-vm/store_handler.hako" as StoreHandlerBox
using "lang/src/vm/hakorune-vm/nop_handler.hako" as NopHandlerBox
using "lang/src/vm/hakorune-vm/safepoint_handler.hako" as SafepointHandlerBox
using "lang/src/vm/hakorune-vm/barrier_handler.hako" as BarrierHandlerBox
using "lang/src/vm/hakorune-vm/typeop_handler.hako" as TypeOpHandlerBox
using "lang/src/vm/hakorune-vm/mircall_handler.hako" as MirCallHandlerBox
using "lang/src/vm/hakorune-vm/boxcall_handler.hako" as BoxCallHandlerBox
using "lang/src/vm/hakorune-vm/newbox_handler.hako" as NewBoxHandlerBox
using "lang/src/vm/hakorune-vm/ret_value_loader.hako" as RetValueLoaderBox
static box InstructionDispatcherBox {
// Dispatch instruction based on op field
// inst_json: MIR instruction JSON (e.g., '{"op":"const","dst":1,...}')
// regs: register MapBox
// mem: memory MapBox
// Returns: Result.Ok(0) or Result.Err(message)
dispatch(inst_json, regs, mem) {
// Extract op field
local key_op = "\"op\":\""
local op_start = inst_json.indexOf(key_op)
if op_start < 0 {
return Result.Err("op field not found")
}
op_start = op_start + key_op.size()
local op_end = StringOps.index_of_from(inst_json, "\"", op_start)
if op_end < 0 {
return Result.Err("op field end not found")
}
local op = inst_json.substring(op_start, op_end)
// Skip PHI (handled separately at block start)
if op == "phi" {
return Result.Ok(0)
}
// @match instruction dispatch to handler boxes
return match op {
"const" => ConstHandlerBox.handle(inst_json, regs)
"unaryop" => UnaryOpHandlerBox.handle(inst_json, regs)
"binop" => BinOpHandlerBox.handle(inst_json, regs)
"compare" => CompareHandlerBox.handle(inst_json, regs)
"load" => LoadHandlerBox.handle(inst_json, regs, mem)
"store" => StoreHandlerBox.handle(inst_json, regs, mem)
"ret" => RetValueLoaderBox.load_from_instruction(inst_json, regs)
"copy" => CopyHandlerBox.handle(inst_json, regs)
"nop" => NopHandlerBox.handle(inst_json, regs, mem)
"safepoint" => SafepointHandlerBox.handle(inst_json, regs, mem)
"barrier" => BarrierHandlerBox.handle(inst_json, regs, mem)
"typeop" => TypeOpHandlerBox.handle(inst_json, regs, mem)
"mir_call" => MirCallHandlerBox.handle(inst_json, regs, mem)
"boxcall" => BoxCallHandlerBox.handle(inst_json, regs)
"newbox" => NewBoxHandlerBox.handle(inst_json, regs)
_ => Result.Err("unsupported instruction: " + op)
}
}
}

Some files were not shown because too many files have changed in this diff Show More