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:
15
lang/src/vm/DEPRECATED.md
Normal file
15
lang/src/vm/DEPRECATED.md
Normal file
@ -0,0 +1,15 @@
|
||||
# DEPRECATED: selfhost/vm (Mini‑VM sandbox)
|
||||
|
||||
This directory hosts the original Mini‑VM used for early self‑hosting.
|
||||
|
||||
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.*`.
|
||||
- Mini‑VM specific smokes remain rc-only and opt-in.
|
||||
|
||||
Removal trigger
|
||||
- When Hakorune VM reaches feature parity and quick/integration remain green for one sprint, Mini‑VM will be retired.
|
||||
6
lang/src/vm/LAYER_GUARD.hako
Normal file
6
lang/src/vm/LAYER_GUARD.hako
Normal file
@ -0,0 +1,6 @@
|
||||
// LAYER_GUARD — VM Layer Guard (Phase 20.8)
|
||||
// Policy:
|
||||
// - lang/src 優先。lang/src 配下からの `using "selfhost/..."` 直参照は禁止。
|
||||
// - 参照は lang/src の等価箱(ミラー)へ統一し、段階撤退を進める。
|
||||
// - Mini‑VM の新規機能追加は慎重に(既定は凍結・スモークで仕様固定)。
|
||||
static box MiniVmLayerGuard { main(args) { return 0 } }
|
||||
105
lang/src/vm/README.md
Normal file
105
lang/src/vm/README.md
Normal 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.)
|
||||
- Mini‑VM minimal executor lives as boxes (e.g., `boxes/mir_vm_min.hako`)
|
||||
|
||||
Target (post‑20.12b, gradual)
|
||||
- `engines/hakorune/` — mainline nyvm engine
|
||||
- `engines/mini/` — Mini‑VM 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 engine‑agnostic execution primitives and should not import
|
||||
engine‑specific modules. During migration, temporary adapters may exist.
|
||||
|
||||
Bridge‑B (Ny/Core 直行)
|
||||
- Wrapper 経路では `include "lang/src/vm/core/dispatcher.hako"` で Core Dispatcher を取り込み、
|
||||
`NyVmDispatcher.run(json)` を直接呼び出す。`using` は名前解決のみで実体は登録されないため、
|
||||
Core を呼ぶ目的では `include` を用いること。
|
||||
Gate‑C(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`
|
||||
- Gate‑C(Core, json→Core 直行) canaries: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh`(既定OFF)
|
||||
- Gate‑C(Core) array sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.sh`(push→set→get をログで検証)
|
||||
- Gate‑C(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`
|
||||
- Gate‑C 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.
|
||||
- Gate‑C 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で終了(Fail‑Fast)。
|
||||
|
||||
Deprecations
|
||||
- `NYASH_GATE_C_DIRECT` は移行中の互換トグル(TTL)だよ。将来は Gate‑C(Core)
|
||||
直行(`HAKO_GATE_C_CORE=1`)に統一予定。新しい導線では Core の実行仕様(数値=rc,
|
||||
安定化した診断タグ)が適用されるよ。
|
||||
- 互換トグルを使うと起動時に警告が出るよ(`HAKO_GATE_C_DIRECT_SILENCE=1` で抑止可)。
|
||||
|
||||
Diagnostics (stable tags)
|
||||
- 本フェーズでは、Gate‑C(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`
|
||||
- Gate‑C Direct では、リーダー/検証レイヤの診断をそのまま用いる(例: `unsupported callee type (expected Extern): ModuleFunction`)。
|
||||
|
||||
Exit code differences
|
||||
- Core: 数値=rc(OS仕様により 0–255 に丸められる。例: 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`(メタのサイズを返す)— 他はタグ付き Fail‑Fast
|
||||
- Global/Extern: `env.console.{log|warn|error}`(数値引数のみ印字)
|
||||
- Others are Fail‑Fast(安定文言を出力)
|
||||
|
||||
See also: docs/development/architecture/collection_semantics.md(Array/Map のSSOT集約)
|
||||
|
||||
String helpers
|
||||
- Core route(Gate‑C/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)。
|
||||
- インデックスは UTF‑8 のバイト境界(コードポイント境界ではない)。
|
||||
- ModuleFunction:
|
||||
- `StringHelpers.to_i64/1` — interpreter に inline 実装あり(数値文字列のみを許容)。
|
||||
数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。
|
||||
|
||||
Core dispatcher canaries(直行ルート)
|
||||
- `profiles/quick/core/canary_core_dispatcher_*` は Gate‑C(Core) 直行へ移行済み。
|
||||
一部(大きな値や plugin‑enabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、
|
||||
rc はフォールバックとして扱う(TTL; 収束後に rc 検証に戻す)。
|
||||
|
||||
Aliases
|
||||
- Keep existing logical module names in `hako.toml` and introduce aliases to
|
||||
new paths when transitioning.
|
||||
16
lang/src/vm/boxes/README.md
Normal file
16
lang/src/vm/boxes/README.md
Normal 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
|
||||
- Mini‑VM's minimal executor currently lives here (`mir_vm_min.hako`). It may
|
||||
move under `engines/mini/` later; keep it box‑pure (no I/O) until then.
|
||||
|
||||
123
lang/src/vm/boxes/arithmetic.hako
Normal file
123
lang/src/vm/boxes/arithmetic.hako
Normal 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)) }
|
||||
}
|
||||
22
lang/src/vm/boxes/cfg_navigator.hako
Normal file
22
lang/src/vm/boxes/cfg_navigator.hako
Normal 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) }
|
||||
}
|
||||
28
lang/src/vm/boxes/compare_ops.hako
Normal file
28
lang/src/vm/boxes/compare_ops.hako
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
25
lang/src/vm/boxes/compare_scan_box.hako
Normal file
25
lang/src/vm/boxes/compare_scan_box.hako
Normal 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 } }
|
||||
98
lang/src/vm/boxes/flow_debugger.hako
Normal file
98
lang/src/vm/boxes/flow_debugger.hako
Normal file
@ -0,0 +1,98 @@
|
||||
// flow_debugger.hako — Mini‑VM 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 }
|
||||
}
|
||||
24
lang/src/vm/boxes/guard_box.hako
Normal file
24
lang/src/vm/boxes/guard_box.hako
Normal file
@ -0,0 +1,24 @@
|
||||
// guard_box.hako — GuardBox
|
||||
// 責務: 反復処理に上限を設け、無限ループをFail‑Fastに近い形で防止
|
||||
// 非責務: エラー出力のポリシー決定(呼び出し側で扱う)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
116
lang/src/vm/boxes/instruction_scanner.hako
Normal file
116
lang/src/vm/boxes/instruction_scanner.hako
Normal 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 })
|
||||
}
|
||||
|
||||
// Mini‑VM 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
|
||||
}
|
||||
}
|
||||
61
lang/src/vm/boxes/json_cur.hako
Normal file
61
lang/src/vm/boxes/json_cur.hako
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
131
lang/src/vm/boxes/mini_collections.hako
Normal file
131
lang/src/vm/boxes/mini_collections.hako
Normal 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
|
||||
}
|
||||
// Fail‑Fast 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: Fail‑Fast 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
|
||||
}
|
||||
}
|
||||
14
lang/src/vm/boxes/mini_vm_core.hako
Normal file
14
lang/src/vm/boxes/mini_vm_core.hako
Normal 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) }
|
||||
}
|
||||
18
lang/src/vm/boxes/mini_vm_entry.hako
Normal file
18
lang/src/vm/boxes/mini_vm_entry.hako
Normal 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) }
|
||||
}
|
||||
114
lang/src/vm/boxes/mini_vm_prints.hako
Normal file
114
lang/src/vm/boxes/mini_vm_prints.hako
Normal 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) }
|
||||
}
|
||||
52
lang/src/vm/boxes/minivm_probe.hako
Normal file
52
lang/src/vm/boxes/minivm_probe.hako
Normal file
@ -0,0 +1,52 @@
|
||||
// minivm_probe.hako — Mini‑VM 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 } }
|
||||
84
lang/src/vm/boxes/mir_vm_m2.hako
Normal file
84
lang/src/vm/boxes/mir_vm_m2.hako
Normal 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
|
||||
}
|
||||
}
|
||||
465
lang/src/vm/boxes/mir_vm_min.hako
Normal file
465
lang/src/vm/boxes/mir_vm_min.hako
Normal 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 Mini‑VM 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 Mini‑VM
|
||||
// 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). Fail‑Fast 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: Fail‑Fast (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 (mini‑VM専用マーカー; 環境依存を避ける)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
165
lang/src/vm/boxes/op_handlers.hako
Normal file
165
lang/src/vm/boxes/op_handlers.hako
Normal 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 Mini‑VM
|
||||
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 Mini‑VM 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) } }
|
||||
}
|
||||
}
|
||||
36
lang/src/vm/boxes/operator_box.hako
Normal file
36
lang/src/vm/boxes/operator_box.hako
Normal file
@ -0,0 +1,36 @@
|
||||
// operator_box.hako — OperatorBox (debug/parity for Mini‑VM)
|
||||
// Responsibility: Provide Compare/Arithmetic/Unary helpers behind a clean API
|
||||
// for self‑hosted (Ny) components. Intended for debugging and parity checks.
|
||||
// Non‑responsibility: Being called from the Rust VM runtime (non‑reentry 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 Mini‑VM 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) }
|
||||
}
|
||||
18
lang/src/vm/boxes/phi_apply_box.hako
Normal file
18
lang/src/vm/boxes/phi_apply_box.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
123
lang/src/vm/boxes/phi_decode_box.hako
Normal file
123
lang/src/vm/boxes/phi_decode_box.hako
Normal 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 (prelude‑safe)
|
||||
_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"))
|
||||
}
|
||||
}
|
||||
29
lang/src/vm/boxes/release_manager.hako
Normal file
29
lang/src/vm/boxes/release_manager.hako
Normal 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 (best‑effort)
|
||||
|
||||
box ReleaseManagerBox {
|
||||
release(obj) {
|
||||
// Call through runtime externs; no return value
|
||||
env.runtime.release(obj)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Release all objects in an array (best‑effort)
|
||||
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 } }
|
||||
|
||||
8
lang/src/vm/boxes/result_box.hako
Normal file
8
lang/src/vm/boxes/result_box.hako
Normal file
@ -0,0 +1,8 @@
|
||||
// result_box.hako — Result / ResultBox
|
||||
// 責務: 処理結果の統一表現(成功値 or エラーメッセージ)
|
||||
// 使い方: Result.Ok(val) / Result.Err(msg) → ResultBox
|
||||
|
||||
@enum Result {
|
||||
Ok(value)
|
||||
Err(error)
|
||||
}
|
||||
21
lang/src/vm/boxes/result_helpers.hako
Normal file
21
lang/src/vm/boxes/result_helpers.hako
Normal 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
|
||||
}
|
||||
}
|
||||
47
lang/src/vm/boxes/ret_resolve_simple.hako
Normal file
47
lang/src/vm/boxes/ret_resolve_simple.hako
Normal 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 } }
|
||||
44
lang/src/vm/boxes/rune_host.hako
Normal file
44
lang/src/vm/boxes/rune_host.hako
Normal file
@ -0,0 +1,44 @@
|
||||
// RuneHostBox — thin placeholder for future rune integration (selfhost)
|
||||
// Responsibility: provide a stable interface; default is disabled (Fail‑Fast)
|
||||
|
||||
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 side‑effect 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 side‑effect 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
|
||||
}
|
||||
}
|
||||
39
lang/src/vm/boxes/scanner_box.hako
Normal file
39
lang/src/vm/boxes/scanner_box.hako
Normal 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文字進めて、直前の文字を返す。末尾なら null(Fail‑Fastは呼び出し側で制御)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
170
lang/src/vm/boxes/seam_inspector.hako
Normal file
170
lang/src/vm/boxes/seam_inspector.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
67
lang/src/vm/boxes/step_runner.hako
Normal file
67
lang/src/vm/boxes/step_runner.hako
Normal file
@ -0,0 +1,67 @@
|
||||
// step_runner.hako — Mini‑VM 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 }
|
||||
}
|
||||
32
lang/src/vm/boxes/vm_kernel_box.hako
Normal file
32
lang/src/vm/boxes/vm_kernel_box.hako
Normal 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\":[]}"
|
||||
}
|
||||
}
|
||||
|
||||
60
lang/src/vm/collect_empty_args_smoke.hako
Normal file
60
lang/src/vm/collect_empty_args_smoke.hako
Normal 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)
|
||||
14
lang/src/vm/collect_empty_args_using_smoke.hako
Normal file
14
lang/src/vm/collect_empty_args_using_smoke.hako
Normal 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)
|
||||
23
lang/src/vm/collect_literal_eval.hako
Normal file
23
lang/src/vm/collect_literal_eval.hako
Normal 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)
|
||||
183
lang/src/vm/collect_mixed_smoke.hako
Normal file
183
lang/src/vm/collect_mixed_smoke.hako
Normal 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
|
||||
}
|
||||
}
|
||||
12
lang/src/vm/collect_mixed_using_smoke.hako
Normal file
12
lang/src/vm/collect_mixed_using_smoke.hako
Normal 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)
|
||||
19
lang/src/vm/collect_prints_loader_smoke.hako
Normal file
19
lang/src/vm/collect_prints_loader_smoke.hako
Normal 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)
|
||||
17
lang/src/vm/core/README.md
Normal file
17
lang/src/vm/core/README.md
Normal 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.
|
||||
|
||||
201
lang/src/vm/core/dispatcher.hako
Normal file
201
lang/src/vm/core/dispatcher.hako
Normal 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 } }
|
||||
14
lang/src/vm/core/extern_iface.hako
Normal file
14
lang/src/vm/core/extern_iface.hako
Normal file
@ -0,0 +1,14 @@
|
||||
// extern_iface.hako — NyVmExternIface (skeleton)
|
||||
// Minimal stub for extern calls registration/dispatch (Phase‑1: no-op)
|
||||
static box NyVmExternIface {
|
||||
// Register extern handler (placeholder)
|
||||
register(name, handler_box) { return 0 }
|
||||
// Call extern; return error (Fail‑Fast) until implemented
|
||||
call(name, args) {
|
||||
print("[core/extern] unsupported extern: " + name)
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
static box NyVmExternIfaceMain { main(args){ return 0 } }
|
||||
|
||||
141
lang/src/vm/core/json_v0_reader.hako
Normal file
141
lang/src/vm/core/json_v0_reader.hako
Normal 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 } }
|
||||
69
lang/src/vm/core/ops/binop.hako
Normal file
69
lang/src/vm/core/ops/binop.hako
Normal 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 } }
|
||||
|
||||
32
lang/src/vm/core/ops/branch.hako
Normal file
32
lang/src/vm/core/ops/branch.hako
Normal 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 } }
|
||||
|
||||
55
lang/src/vm/core/ops/compare.hako
Normal file
55
lang/src/vm/core/ops/compare.hako
Normal 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 } }
|
||||
56
lang/src/vm/core/ops/const.hako
Normal file
56
lang/src/vm/core/ops/const.hako
Normal 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 } }
|
||||
30
lang/src/vm/core/ops/copy.hako
Normal file
30
lang/src/vm/core/ops/copy.hako
Normal 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 } }
|
||||
|
||||
28
lang/src/vm/core/ops/jump.hako
Normal file
28
lang/src/vm/core/ops/jump.hako
Normal 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 } }
|
||||
|
||||
33
lang/src/vm/core/ops/load.hako
Normal file
33
lang/src/vm/core/ops/load.hako
Normal 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 } }
|
||||
|
||||
779
lang/src/vm/core/ops/mir_call.hako
Normal file
779
lang/src/vm/core/ops/mir_call.hako
Normal 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 cleared(TTL: metadata‑only)
|
||||
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 } }
|
||||
78
lang/src/vm/core/ops/phi.hako
Normal file
78
lang/src/vm/core/ops/phi.hako
Normal 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 } }
|
||||
|
||||
33
lang/src/vm/core/ops/ret.hako
Normal file
33
lang/src/vm/core/ops/ret.hako
Normal 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 } }
|
||||
|
||||
32
lang/src/vm/core/ops/store.hako
Normal file
32
lang/src/vm/core/ops/store.hako
Normal 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 } }
|
||||
|
||||
53
lang/src/vm/core/ops/typeop.hako
Normal file
53
lang/src/vm/core/ops/typeop.hako
Normal 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 } }
|
||||
|
||||
59
lang/src/vm/core/ops/unary.hako
Normal file
59
lang/src/vm/core/ops/unary.hako
Normal 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 } }
|
||||
|
||||
26
lang/src/vm/core/state.hako
Normal file
26
lang/src/vm/core/state.hako
Normal file
@ -0,0 +1,26 @@
|
||||
// state.hako — NyVmState (skeleton)
|
||||
// Holds registers and temporary memory. Numeric-only for Phase‑1.
|
||||
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 } }
|
||||
10
lang/src/vm/core/value.hako
Normal file
10
lang/src/vm/core/value.hako
Normal file
@ -0,0 +1,10 @@
|
||||
// value.hako — NyVmValue (skeleton)
|
||||
// Minimal numeric-only representation for Phase‑1 (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 } }
|
||||
|
||||
13
lang/src/vm/engines/hakorune/engine.hako
Normal file
13
lang/src/vm/engines/hakorune/engine.hako
Normal 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 } }
|
||||
|
||||
13
lang/src/vm/engines/mini/engine.hako
Normal file
13
lang/src/vm/engines/mini/engine.hako
Normal 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 } }
|
||||
|
||||
52
lang/src/vm/flow_runner.hako
Normal file
52
lang/src/vm/flow_runner.hako
Normal file
@ -0,0 +1,52 @@
|
||||
// flow_runner.hako — Selfhost VM runner thin box(exec 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 Mini‑VM from Stage‑1 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
16
lang/src/vm/gc/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
GC v0 — Mark & Sweep (Skeleton)
|
||||
===============================
|
||||
|
||||
Responsibility
|
||||
- Provide a minimal stop‑the‑world Mark & Sweep collector for Hakorune VM.
|
||||
- Deterministic, observable, and Fail‑Fast by default.
|
||||
|
||||
Status
|
||||
- Skeleton only. Not wired to VM yet. Safe to keep in repo without side‑effects.
|
||||
|
||||
Principles
|
||||
- No generational/incremental logic in v0.
|
||||
- Safepoints: call boundaries / loop back‑edges / 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.
|
||||
|
||||
18
lang/src/vm/gc/gc_box.hako
Normal file
18
lang/src/vm/gc/gc_box.hako
Normal 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() }
|
||||
}
|
||||
|
||||
7
lang/src/vm/gc/gc_hooks.hako
Normal file
7
lang/src/vm/gc/gc_hooks.hako
Normal 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() { }
|
||||
}
|
||||
|
||||
27
lang/src/vm/gc/gc_metrics_box.hako
Normal file
27
lang/src/vm/gc/gc_metrics_box.hako
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
10
lang/src/vm/gc/gc_policy_box.hako
Normal file
10
lang/src/vm/gc/gc_policy_box.hako
Normal 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 } }
|
||||
|
||||
32
lang/src/vm/gc/gc_runtime.hako
Normal file
32
lang/src/vm/gc/gc_runtime.hako
Normal 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() }
|
||||
}
|
||||
}
|
||||
14
lang/src/vm/hako_module.toml
Normal file
14
lang/src/vm/hako_module.toml
Normal 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"
|
||||
17
lang/src/vm/hakorune-vm/README.md
Normal file
17
lang/src/vm/hakorune-vm/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Hakorune VM (nyvm) — Engine Guard
|
||||
|
||||
Responsibility
|
||||
- Engine orchestration and instruction dispatch for nyvm.
|
||||
- Control‑flow 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.
|
||||
|
||||
7
lang/src/vm/hakorune-vm/archive/README.md
Normal file
7
lang/src/vm/hakorune-vm/archive/README.md
Normal 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.
|
||||
|
||||
48
lang/src/vm/hakorune-vm/archive/binop_handler.legacy.hako
Normal file
48
lang/src/vm/hakorune-vm/archive/binop_handler.legacy.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
36
lang/src/vm/hakorune-vm/archive/compare_handler.legacy.hako
Normal file
36
lang/src/vm/hakorune-vm/archive/compare_handler.legacy.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
59
lang/src/vm/hakorune-vm/archive/const_handler.legacy.hako
Normal file
59
lang/src/vm/hakorune-vm/archive/const_handler.legacy.hako
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
158
lang/src/vm/hakorune-vm/args_extractor.hako
Normal file
158
lang/src/vm/hakorune-vm/args_extractor.hako
Normal 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
|
||||
}
|
||||
}
|
||||
21
lang/src/vm/hakorune-vm/args_guard.hako
Normal file
21
lang/src/vm/hakorune-vm/args_guard.hako
Normal file
@ -0,0 +1,21 @@
|
||||
// args_guard.hako — ArgsGuardBox
|
||||
// Responsibility: validate argument arrays for boxcall/method (Fail‑Fast)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
43
lang/src/vm/hakorune-vm/backward_object_scanner.hako
Normal file
43
lang/src/vm/hakorune-vm/backward_object_scanner.hako
Normal 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))
|
||||
}
|
||||
}
|
||||
38
lang/src/vm/hakorune-vm/barrier_handler.hako
Normal file
38
lang/src/vm/hakorune-vm/barrier_handler.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
17
lang/src/vm/hakorune-vm/binop_handler.hako
Normal file
17
lang/src/vm/hakorune-vm/binop_handler.hako
Normal 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) }
|
||||
}
|
||||
30
lang/src/vm/hakorune-vm/block_iterator.hako
Normal file
30
lang/src/vm/hakorune-vm/block_iterator.hako
Normal 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")
|
||||
}
|
||||
152
lang/src/vm/hakorune-vm/block_mapper.hako
Normal file
152
lang/src/vm/hakorune-vm/block_mapper.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
38
lang/src/vm/hakorune-vm/blocks_locator.hako
Normal file
38
lang/src/vm/hakorune-vm/blocks_locator.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
34
lang/src/vm/hakorune-vm/boxcall_builder.hako
Normal file
34
lang/src/vm/hakorune-vm/boxcall_builder.hako
Normal 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 + "]}"
|
||||
}
|
||||
}
|
||||
191
lang/src/vm/hakorune-vm/boxcall_handler.hako
Normal file
191
lang/src/vm/hakorune-vm/boxcall_handler.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
157
lang/src/vm/hakorune-vm/callee_parser.hako
Normal file
157
lang/src/vm/hakorune-vm/callee_parser.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
315
lang/src/vm/hakorune-vm/closure_call_handler.hako
Normal file
315
lang/src/vm/hakorune-vm/closure_call_handler.hako
Normal 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
|
||||
}
|
||||
}
|
||||
17
lang/src/vm/hakorune-vm/compare_handler.hako
Normal file
17
lang/src/vm/hakorune-vm/compare_handler.hako
Normal 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) }
|
||||
}
|
||||
17
lang/src/vm/hakorune-vm/const_handler.hako
Normal file
17
lang/src/vm/hakorune-vm/const_handler.hako
Normal 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) }
|
||||
}
|
||||
97
lang/src/vm/hakorune-vm/constructor_call_handler.hako
Normal file
97
lang/src/vm/hakorune-vm/constructor_call_handler.hako
Normal 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 only(BoxBirthに近い位置で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
|
||||
}
|
||||
}
|
||||
16
lang/src/vm/hakorune-vm/copy_handler.hako
Normal file
16
lang/src/vm/hakorune-vm/copy_handler.hako
Normal 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) }
|
||||
}
|
||||
12
lang/src/vm/hakorune-vm/core_bridge.hako
Normal file
12
lang/src/vm/hakorune-vm/core_bridge.hako
Normal 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 } }
|
||||
484
lang/src/vm/hakorune-vm/core_bridge_ops.hako
Normal file
484
lang/src/vm/hakorune-vm/core_bridge_ops.hako
Normal 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 → delegated(this file)
|
||||
// * Map : len/iterator(set as len)/set/get → delegated(this 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 } }
|
||||
14
lang/src/vm/hakorune-vm/entry/main.hako
Normal file
14
lang/src/vm/hakorune-vm/entry/main.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
7
lang/src/vm/hakorune-vm/error_builder.hako
Normal file
7
lang/src/vm/hakorune-vm/error_builder.hako
Normal 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" }
|
||||
}
|
||||
45
lang/src/vm/hakorune-vm/extern_call_handler.hako
Normal file
45
lang/src/vm/hakorune-vm/extern_call_handler.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
22
lang/src/vm/hakorune-vm/function_locator.hako
Normal file
22
lang/src/vm/hakorune-vm/function_locator.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
13
lang/src/vm/hakorune-vm/gc_hooks.hako
Normal file
13
lang/src/vm/hakorune-vm/gc_hooks.hako
Normal file
@ -0,0 +1,13 @@
|
||||
// gc_hooks.hako — Hakorune‑VM 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()
|
||||
}
|
||||
}
|
||||
50
lang/src/vm/hakorune-vm/global_call_handler.hako
Normal file
50
lang/src/vm/hakorune-vm/global_call_handler.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
235
lang/src/vm/hakorune-vm/hakorune_vm_core.hako
Normal file
235
lang/src/vm/hakorune-vm/hakorune_vm_core.hako
Normal file
@ -0,0 +1,235 @@
|
||||
// hakorune_vm_core.hako — Phase 1: 最小動作VM(Const/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)
|
||||
}
|
||||
}
|
||||
97
lang/src/vm/hakorune-vm/instrs_locator.hako
Normal file
97
lang/src/vm/hakorune-vm/instrs_locator.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
27
lang/src/vm/hakorune-vm/instruction_array_locator.hako
Normal file
27
lang/src/vm/hakorune-vm/instruction_array_locator.hako
Normal 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)
|
||||
}
|
||||
}
|
||||
71
lang/src/vm/hakorune-vm/instruction_dispatcher.hako
Normal file
71
lang/src/vm/hakorune-vm/instruction_dispatcher.hako
Normal 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
Reference in New Issue
Block a user