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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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