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:
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 } }
|
||||
|
||||
Reference in New Issue
Block a user