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,8 @@
selfhost/shared — Shared boxes
Responsibilities
- Minimal MIR schema/builders and JSON helpers used by compiler and VM.
- No execution or parsing side effects.
Allowed imports
- selfhost/shared/* only; no compiler/vm imports.

View File

@ -0,0 +1,37 @@
// map_kv_string_to_array.hako — Adapter to convert keysS/valuesS string to ArrayBox
// Responsibility: Provide split helpers (String -> ArrayBox) for MapBox keys/values
static box MapKvStringToArrayAdapter {
// Split a newline-joined String into an ArrayBox of strings.
// Empty or null-like input yields empty ArrayBox.
split_lines(s) {
local out = new ArrayBox()
if s == null { return out }
// Simple scan: split by "\n"
local i = 0
local start = 0
local n = s.size()
loop(i < n) {
if s.substring(i, i+1) == "\n" {
out.push(s.substring(start, i))
start = i + 1
}
i = i + 1
}
// tail
if start <= n { out.push(s.substring(start, n)) }
return out
}
// Adapt Map.keysS() -> ArrayBox
adapt_keysS(map) {
local ks = map.keysS()
return me.split_lines(ks)
}
// Adapt Map.valuesS() -> ArrayBox
adapt_valuesS(map) {
local vs = map.valuesS()
return me.split_lines(vs)
}
}

View File

@ -0,0 +1,20 @@
// LlvmBackendBox — Hako ABI (stub)
// File-handoff only: compile MIR(JSON v0) to object, then link to EXE.
// This is a stub for call-site wiring; current implementation is provided by external tools (ny-llvmc).
static box LlvmBackendBox {
// Returns object path on success, or throws (Err) on failure.
compile_obj(json_path) {
// Stub only (dev): return a deterministic obj path to guide call sites.
// Real implementation should shell out to ny-llvmc / harness.
if json_path == null { throw "LlvmBackend.compile_obj: json_path is null" }
local stem = json_path + ".o" // naive; callers should pass preferred path later
return stem
}
// Links an exe. Returns true on success (stub always false to prevent accidental use).
link_exe(obj_path, out_path, libs) {
if obj_path == null || out_path == null { throw "LlvmBackend.link_exe: path is null" }
return false // stub
}
}

View File

@ -0,0 +1,121 @@
// box_helpers.hako — 共通Box操作ヘルパー (Phase 31.2 共通化)
// 目的: ArrayBox/MapBox の安全な操作を統一的に提供
// 削減: 7ファイル × 2パターン = 14重複 → 1箇所に集約
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box BoxHelpers {
// ArrayBox.size/1 の結果unwrap (MapBox-wrapped integer対応)
array_len(arr) {
if arr == null { return 0 }
local size_val = call("ArrayBox.size/1", arr)
local repr = "" + size_val
if repr.indexOf("MapBox(") == 0 {
local inner = call("MapBox.get/2", size_val, "value")
if inner != null { return inner }
}
return size_val
}
// ArrayBox.get/2 の安全呼び出し
array_get(arr, idx) {
if arr == null { return null }
return call("ArrayBox.get/2", arr, idx)
}
// MapBox.get/2 の安全呼び出し
map_get(obj, key) {
if obj == null { return null }
return call("MapBox.get/2", obj, key)
}
// MapBox.set/3 の安全呼び出し(形だけ統一)
map_set(obj, key, val) {
if obj == null { obj = new MapBox() }
call("MapBox.set/3", obj, key, val)
return obj
}
// MapBox-wrapped integer の unwrap (汎用版)
value_i64(val) {
if val == null { return 0 }
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local inner = call("MapBox.get/2", val, "value")
if inner != null { return inner }
}
return val
}
// MapBox型判定
is_map(val) {
if val == null { return 0 }
local repr = "" + val
if repr.indexOf("MapBox(") == 0 { return 1 }
return 0
}
// ArrayBox型判定
is_array(val) {
if val == null { return 0 }
local repr = "" + val
if repr.indexOf("ArrayBox(") == 0 { return 1 }
return 0
}
// Fail-fast helpers (Phase 31.3+)
expect_map(val, context) {
if val == null {
print("[BoxHelpers] expected MapBox for " + context + " but got null")
call("MapBox.get/2", val, "__box_helpers_expect_map_null")
return val
}
if me.is_map(val) == 1 { return val }
print("[BoxHelpers] dev assert failed: expected MapBox for " + context)
call("MapBox.get/2", val, "__box_helpers_expect_map")
return val
}
expect_array(val, context) {
if val == null {
print("[BoxHelpers] expected ArrayBox for " + context + " but got null")
call("ArrayBox.get/2", val, 0)
return val
}
if me.is_array(val) == 1 { return val }
print("[BoxHelpers] dev assert failed: expected ArrayBox for " + context)
call("ArrayBox.get/2", val, 0)
return val
}
expect_i64(val, context) {
if val == null {
print("[BoxHelpers] dev assert failed: expected i64 (non-null) for " + context)
call("MapBox.get/2", val, "__box_helpers_expect_i64_null")
return 0
}
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local ty = call("MapBox.get/2", val, "type")
if ty != null {
local ty_str = "" + ty
if ty_str != "i64" && ty_str != "int" && ty_str != "integer" {
print("[BoxHelpers] dev assert failed: unexpected type " + ty_str + " in " + context)
call("MapBox.get/2", val, "__box_helpers_expect_i64_type")
return 0
}
}
local inner = call("MapBox.get/2", val, "value")
if inner != null { return StringHelpers.to_i64(inner) }
print("[BoxHelpers] dev assert failed: missing value in " + context)
call("MapBox.get/2", val, "__box_helpers_expect_i64_value")
return 0
}
if StringHelpers.is_numeric_str("" + val) == 1 { return StringHelpers.to_i64(val) }
print("[BoxHelpers] dev assert failed: expected numeric value for " + context)
call("MapBox.get/2", val, "__box_helpers_expect_i64_direct")
return 0
}
}
static box BoxHelpersStub { main(args) { return 0 } }

View File

@ -0,0 +1,276 @@
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonCur
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
static box MiniVmBinOp {
// Minimal: Print(BinaryOp) with operator "+"; supports string+string and int+int
try_print_binop_at(json, end, print_pos) {
local scan = new MiniVmScan()
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = scan.index_of_from(json, k_bo, print_pos)
if bpos <= 0 || bpos >= end { return -1 }
// bound BinaryOp object (prefer expression object)
local k_expr = "\"expression\":{"
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
local obj_start = -1
if expr_pos > 0 && expr_pos < end {
obj_start = scan.index_of_from(json, "{", expr_pos)
} else {
obj_start = scan.index_of_from(json, "{", bpos)
}
local obj_end = scan.find_balanced_object_end(json, obj_start)
if obj_start <= 0 || obj_end <= 0 || obj_end > end { return -1 }
// operator must be '+'
local k_op = "\"operator\":\"+\""
local opos = scan.index_of_from(json, k_op, bpos)
if opos <= 0 || opos >= obj_end { return -1 }
// string + string fast-path
local cur = new MiniJson()
local k_left_lit = "\"left\":{\"kind\":\"Literal\""
local lhdr = scan.index_of_from(json, k_left_lit, opos)
if lhdr > 0 && lhdr < obj_end {
local k_sval = "\"value\":\""
local lvp = scan.index_of_from(json, k_sval, lhdr)
if lvp > 0 && lvp < obj_end {
local li = lvp + k_sval.size()
local lval = cur.read_quoted_from(json, li)
if lval {
local k_right_lit = "\"right\":{\"kind\":\"Literal\""
local rhdr = scan.index_of_from(json, k_right_lit, li + lval.size())
if rhdr > 0 && rhdr < obj_end {
local rvp = scan.index_of_from(json, k_sval, rhdr)
if rvp > 0 && rvp < obj_end {
local ri = rvp + k_sval.size()
local rval = cur.read_quoted_from(json, ri)
if rval { print(lval + rval) return ri + rval.size() + 1 }
}
}
}
}
}
// int + int typed pattern
local k_l = "\"left\":{\"kind\":\"Literal\""
local lpos = scan.index_of_from(json, k_l, opos)
if lpos <= 0 || lpos >= obj_end { return -1 }
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local li2 = scan.index_of_from(json, k_lint, opos)
if li2 <= 0 || li2 >= obj_end { return -1 }
local ldigits = scan.read_digits(json, li2 + k_lint.size())
if ldigits == "" { return -1 }
local k_r = "\"right\":{\"kind\":\"Literal\""
local rpos = scan.index_of_from(json, k_r, lpos)
if rpos <= 0 || rpos >= obj_end { return -1 }
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local ri2 = scan.index_of_from(json, k_rint, lpos)
if ri2 <= 0 || ri2 >= obj_end { return -1 }
local rdigits = scan.read_digits(json, ri2 + k_rint.size())
if rdigits == "" { return -1 }
local ai = scan._str_to_int(ldigits)
local bi = scan._str_to_int(rdigits)
print(scan._int_to_str(ai + bi))
return obj_end + 1
}
// Greedy disabled (kept for parity)
try_print_binop_int_greedy(json, end, print_pos) { return -1 }
// Fallback: within the current Print's expression BinaryOp object, scan for two numeric values and sum
try_print_binop_sum_any(json, end, print_pos) {
local scan = new MiniVmScan()
local k_expr = "\"expression\":{"
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
// If expression object cannot be bounded, fall back to typed-direct pattern within the current slice
if expr_pos <= 0 || expr_pos >= end {
// bound coarse slice to current Print by next Print marker
local k_print = "\"kind\":\"Print\""
local next_p = scan.index_of_from(json, k_print, print_pos + 1)
local slice_end = end
if next_p > 0 { slice_end = next_p }
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
// only if BinaryOp is present in this slice
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", print_pos) > 0 {
local lp = scan.index_of_from(json, k_lint, print_pos)
if lp > 0 { if lp < slice_end {
local ld = scan.read_digits(json, lp + k_lint.size())
if ld != "" {
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
if rp > 0 { if rp < slice_end {
local rd = scan.read_digits(json, rp + k_rint.size())
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
}}
}
}}
}
return -1
}
local obj_start = scan.index_of_from(json, "{", expr_pos)
if obj_start <= 0 || obj_start >= end { return -1 }
local obj_end = scan.find_balanced_object_end(json, obj_start)
if obj_end <= 0 || obj_end > end {
local k_print = "\"kind\":\"Print\""
local next_p = scan.index_of_from(json, k_print, print_pos + 1)
local obj_end2 = end
if next_p > 0 && next_p <= end { obj_end2 = next_p }
// typed-direct fallback in bounded region
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local lp = scan.index_of_from(json, k_lint, obj_start)
if lp > 0 { if lp < obj_end2 {
local ld = scan.read_digits(json, lp + k_lint.size())
if ld != "" {
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
if rp > 0 { if rp < obj_end2 {
local rd = scan.read_digits(json, rp + k_rint.size())
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
}}
}
}}
return -1
}
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = scan.index_of_from(json, k_bo, obj_start)
if bpos <= 0 || bpos >= obj_end {
// typed-direct fallback (within bounds window)
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local lp = scan.index_of_from(json, k_lint, obj_start)
if lp > 0 { if lp < obj_end {
local ld = scan.read_digits(json, lp + k_lint.size())
if ld != "" {
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
if rp > 0 { if rp < obj_end {
local rd = scan.read_digits(json, rp + k_rint.size())
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
}}
}
}}
return -1
}
local k_plus = "\"operator\":\"+\""
local opos = scan.index_of_from(json, k_plus, bpos)
if opos <= 0 || opos >= obj_end {
// typed-direct fallback
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
local lp = scan.index_of_from(json, k_lint, obj_start)
if lp > 0 { if lp < obj_end {
local ld = scan.read_digits(json, lp + k_lint.size())
if ld != "" {
local rp = scan.index_of_from(json, k_rint, lp + k_lint.size())
if rp > 0 { if rp < obj_end {
local rd = scan.read_digits(json, rp + k_rint.size())
if rd != "" { print(scan._int_to_str(scan._str_to_int(ld) + scan._str_to_int(rd))) return 1 }
}}
}
}}
return -1
}
local nums = []
local i = obj_start
loop (i < obj_end) {
if call("String.substring/2", json, i, i+1) == "\"" {
local j = scan.index_of_from(json, "\"", i+1)
if j < 0 || j >= obj_end { break }
i = j + 1
continue
}
local d = scan.read_digits(json, i)
if d { nums.push(d) i = i + d.size() continue }
i = i + 1
}
local nsz = nums.size()
if nsz < 2 { return -1 }
local a = scan._str_to_int(nums.get(nsz-2))
local b = scan._str_to_int(nums.get(nsz-1))
print(scan._int_to_str(a + b))
return obj_end + 1
}
// Deterministic: within Print.expression BinaryOp('+'), pick two successive 'value' fields and sum
try_print_binop_sum_expr_values(json, end, print_pos) {
local scan = new MiniVmScan()
local cur = new MiniJson()
local k_expr = "\"expression\":{"
local expr_pos = scan.index_of_from(json, k_expr, print_pos)
if expr_pos <= 0 || expr_pos >= end { return -1 }
local obj_start = scan.index_of_from(json, "{", expr_pos)
if obj_start <= 0 || obj_start >= end { return -1 }
local obj_end = scan.find_balanced_object_end(json, obj_start)
if obj_end <= 0 || obj_end > end { return -1 }
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = scan.index_of_from(json, k_bo, obj_start)
if bpos <= 0 || bpos >= obj_end { return -1 }
local k_plus = "\"operator\":\"+\""
local opos = scan.index_of_from(json, k_plus, bpos)
if opos <= 0 || opos >= obj_end { return -1 }
local k_v = "\"value\":"
local found = 0
local a = 0
local pos = scan.index_of_from(json, k_v, obj_start)
loop (pos > 0 && pos < obj_end) {
local di = cur.read_digits_from(json, pos + k_v.size())
if di != "" {
if found == 0 { a = scan._str_to_int(di) found = 1 } else {
local b = scan._str_to_int(di)
print(scan._int_to_str(a + b))
return obj_end + 1
}
}
pos = scan.index_of_from(json, k_v, pos + k_v.size())
if pos <= 0 || pos >= obj_end { break }
}
return -1
}
// Simpler: after operator '+', scan two successive 'value' fields and sum
try_print_binop_sum_after_bop(json) {
local scan = new MiniVmScan()
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = json.indexOf(k_bo)
if bpos < 0 { return -1 }
local k_plus = "\"operator\":\"+\""
local opos = scan.index_of_from(json, k_plus, bpos)
if opos < 0 { return -1 }
local k_v = "\"value\":"
local p = opos
p = scan.index_of_from(json, k_v, p)
if p < 0 { return -1 }
p = scan.index_of_from(json, k_v, p + k_v.size())
if p < 0 { return -1 }
local end1 = scan.index_of_from(json, "}", p)
if end1 < 0 { return -1 }
local d1 = scan.read_digits(json, p + k_v.size())
if d1 == "" { return -1 }
p = scan.index_of_from(json, k_v, end1)
if p < 0 { return -1 }
p = scan.index_of_from(json, k_v, p + k_v.size())
if p < 0 { return -1 }
local end2 = scan.index_of_from(json, "}", p)
if end2 < 0 { return -1 }
local d2 = scan.read_digits(json, p + k_v.size())
if d2 == "" { return -1 }
local ai = scan._str_to_int(d1)
local bi = scan._str_to_int(d2)
print(scan._int_to_str(ai + bi))
return 0
}
// Fallback: find first BinaryOp and return sum of two numeric values as string
parse_first_binop_sum(json) {
local scan = new MiniVmScan()
local k_bo = "\"kind\":\"BinaryOp\""
local bpos = json.indexOf(k_bo)
if bpos < 0 { return "" }
local k_typed = "\"type\":\"int\",\"value\":"
local p1 = scan.index_of_from(json, k_typed, bpos)
if p1 < 0 { return "" }
local d1 = scan.read_digits(json, p1 + k_typed.size())
if d1 == "" { return "" }
local p2 = scan.index_of_from(json, k_typed, p1 + k_typed.size())
if p2 < 0 { return "" }
local d2 = scan.read_digits(json, p2 + k_typed.size())
if d2 == "" { return "" }
return scan._int_to_str(scan._str_to_int(d1) + scan._str_to_int(d2))
}
}

View File

@ -0,0 +1,49 @@
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
static box MiniVmCompare {
// Compare(lhs int, rhs int) minimal: prints 0/1 and returns next pos or -1
try_print_compare_at(json, end, print_pos) {
local scan = new MiniVmScan()
local k_cp = "\"kind\":\"Compare\""
local cpos = scan.indexOf(k_cp)
if cpos < 0 { cpos = scan.index_of_from(json, k_cp, print_pos) }
if cpos <= 0 || cpos >= end { return -1 }
local k_op = "\"operation\":\""
local opos = scan.index_of_from(json, k_op, cpos)
if opos <= 0 || opos >= end { return -1 }
local oi = opos + k_op.size()
local oj = scan.index_of_from(json, "\"", oi)
if oj <= 0 || oj > end { return -1 }
local op = json.substring(oi, oj)
// lhs value
local k_lhs = "\"lhs\":{\"kind\":\"Literal\""
local hl = scan.index_of_from(json, k_lhs, oj)
if hl <= 0 || hl >= end { return -1 }
local k_v = "\"value\":"
local hv = scan.index_of_from(json, k_v, hl)
if hv <= 0 || hv >= end { return -1 }
local a = scan.read_digits(json, hv + k_v.size())
// rhs value
local k_rhs = "\"rhs\":{\"kind\":\"Literal\""
local hr = scan.index_of_from(json, k_rhs, hl)
if hr <= 0 || hr >= end { return -1 }
local rv = scan.index_of_from(json, k_v, hr)
if rv <= 0 || rv >= end { return -1 }
local b = scan.read_digits(json, rv + k_v.size())
if !a || !b { return -1 }
local ai = scan._str_to_int(a)
local bi = scan._str_to_int(b)
local res = match op {
"<" => { if ai < bi { 1 } else { 0 } }
"==" => { if ai == bi { 1 } else { 0 } }
"<=" => { if ai <= bi { 1 } else { 0 } }
">" => { if ai > bi { 1 } else { 0 } }
">=" => { if ai >= bi { 1 } else { 0 } }
"!=" => { if ai != bi { 1 } else { 0 } }
_ => 0
}
print(res)
// advance after rhs object (coarsely)
return rv + 1
}
}

View File

@ -0,0 +1,79 @@
// Mini-VM scanning and numeric helpers
// Delegation Policy: all scanning primitives route to JsonCursorBox to avoid divergence.
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
static box MiniVmScan {
// helper: find needle from position pos (escape-aware where needed)
index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
// helper: find balanced bracket range [ ... ] starting at idx (points to '[')
find_balanced_array_end(json, idx) { return JsonCursorBox.seek_array_end(json, idx) }
// helper: find balanced object range { ... } starting at idx (points to '{')
find_balanced_object_end(json, idx) { return JsonCursorBox.seek_obj_end(json, idx) }
_str_to_int(s) { return StringHelpers.to_i64(s) }
_int_to_str(n) { return StringHelpers.int_to_str(n) }
read_digits(json, pos) { return StringHelpers.read_digits(json, pos) }
// Linear pass: sum all numbers outside of quotes
sum_numbers_no_quotes(json) {
@i = 0
@n = json.size()
@total = 0
loop (i < n) {
@ch = call("String.substring/2", json, i, i+1)
if ch == "\"" {
@j = me.index_of_from(json, "\"", i+1)
if j < 0 { break }
i = j + 1
continue
}
@d = me.read_digits(json, i)
if d { total = total + me._str_to_int(d) i = i + d.size() continue }
i = i + 1
}
return me._int_to_str(total)
}
// Naive: sum all digit runs anywhere
sum_all_digits_naive(json) {
@i = 0
@n = json.size()
@total = 0
loop (i < n) {
@d = me.read_digits(json, i)
if d { total = total + me._str_to_int(d) i = i + d.size() continue }
i = i + 1
}
return me._int_to_str(total)
}
// Sum first two integers outside quotes; returns string or empty
sum_first_two_numbers(json) {
@i = 0
@n = json.size()
@total = 0
local found = 0
loop (i < n) {
@ch = call("String.substring/2", json, i, i+1)
if ch == "\"" {
@j = me.index_of_from(json, "\"", i+1)
if j < 0 { break }
i = j + 1
continue
}
@d = me.read_digits(json, i)
if d {
total = total + me._str_to_int(d)
found = found + 1
i = i + d.size()
if found >= 2 { return me._int_to_str(total) }
continue
}
i = i + 1
}
return ""
}
}

View File

@ -0,0 +1,39 @@
// modules_inspect_box.hako — ModulesInspectBox
// Responsibility: Helpers for Dir-as-NS module inspection from Nyash code.
// NonIO, formatting-only utilities. Caller provides inputs (e.g., lists or JSON).
static box ModulesInspectBox {
// Convert apps-relative path to namespace (Dir-as-NS v2.2 rules)
path_to_namespace(path) {
if path == null { return "" }
local s = "" + path
// Strip leading ./
if s.size() >= 2 && s.substring(0,2) == "./" { s = s.substring(2, s.size()) }
// Remove leading apps/
local pref = "apps/"
if s.size() >= pref.size() && s.substring(0, pref.size()) == pref { s = s.substring(pref.size(), s.size()) }
// Replace '-' with '.' in directory parts only
local out = ""
local i = 0
loop(i < s.size()) {
local ch = s.substring(i, i+1)
if ch == "/" { out = out + "." i = i + 1 continue }
if ch == "-" { out = out + "." i = i + 1 continue }
out = out + ch
i = i + 1
}
// Drop .hako and optional _box suffix
if out.size() >= 5 && out.substring(out.size()-5, out.size()) == ".hako" {
out = out.substring(0, out.size()-5)
}
if out.size() >= 4 && out.substring(out.size()-4, out.size()) == "_box" {
out = out.substring(0, out.size()-4)
}
return out
}
// Pretty single line
pretty_line(tag, ns, path) { return "[" + tag + "] " + ns + " → " + path }
}
static box ModulesInspectMain { main(args){ return 0 } }

View File

@ -0,0 +1,174 @@
// string_helpers.hako — StringHelpers (common, pure helpers)
// Responsibility: numeric/string conversions and JSON quoting for selfhost tools.
// Non-responsibility: JSON scanning beyond local character processing; use CfgNavigatorBox/StringScanBox for navigation.
static box StringHelpers {
// Convert a numeric or numeric-like string to its decimal representation.
int_to_str(n) {
local v = me.to_i64(n)
if v == 0 { return "0" }
if v < 0 { return "-" + me.int_to_str(0 - v) }
local out = ""
local digits = "0123456789"
loop (v > 0) {
local d = v % 10
local ch = digits.substring(d, d+1)
out = ch + out
v = v / 10
}
return out
}
// Parse integer from number or numeric-like string (leading '-' allowed; stops at first non-digit).
to_i64(x) {
local s = "" + x
local i = 0
local neg = 0
if s.substring(0,1) == "-" { neg = 1 i = 1 }
local n = s.size()
if i >= n { return 0 }
local acc = 0
loop (i < n) {
local ch = s.substring(i, i+1)
if ch < "0" || ch > "9" { break }
local ds = "0123456789"
local dpos = ds.indexOf(ch)
if dpos < 0 { break }
acc = acc * 10 + dpos
i = i + 1
}
if neg == 1 { return 0 - acc }
return acc
}
// Quote a string for JSON (escape backslash, quote, and control chars) and wrap with quotes
json_quote(s) {
if s == null { return "\"\"" }
local out = ""
local i = 0
local n = s.size()
loop (i < n) {
local ch = s.substring(i, i+1)
if ch == "\\" { out = out + "\\\\" }
else { if ch == "\"" { out = out + "\\\"" } else {
if ch == "\n" { out = out + "\\n" } else {
if ch == "\r" { out = out + "\\r" } else {
if ch == "\t" { out = out + "\\t" } else { out = out + ch }
}
}
}}
i = i + 1
}
return "\"" + out + "\""
}
// Check if string is numeric-like (optional leading '-', then digits).
is_numeric_str(s) {
if s == null { return 0 }
local n = s.size()
if n == 0 { return 0 }
local i = 0
if s.substring(0,1) == "-" { if n == 1 { return 0 } i = 1 }
loop(i < n) { local ch = s.substring(i, i+1) if ch < "0" || ch > "9" { return 0 } i = i + 1 }
return 1
}
// Read consecutive digits starting at pos (no sign handling here; keep semantics simple)
read_digits(text, pos) {
local out = ""
loop (true) {
local ch = text.substring(pos, pos+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch pos = pos + 1 } else { break }
}
return out
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🆕 Extended String Utilities (added for parser/compiler unification)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Character predicates
is_digit(ch) { return ch >= "0" && ch <= "9" }
is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
// Pattern matching
starts_with(src, i, pat) {
local n = src.size()
local m = pat.size()
if i + m > n { return 0 }
local k = 0
loop(k < m) {
if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 }
k = k + 1
}
return 1
}
// Keyword match with word boundary (next char not [A-Za-z0-9_])
starts_with_kw(src, i, kw) {
if me.starts_with(src, i, kw) == 0 { return 0 }
local n = src.size()
local j = i + kw.size()
if j >= n { return 1 }
local ch = src.substring(j, j+1)
if me.is_alpha(ch) || me.is_digit(ch) { return 0 }
return 1
}
// String search
index_of(src, i, pat) {
local n = src.size()
local m = pat.size()
if m == 0 { return i }
local j = i
loop(j + m <= n) {
if me.starts_with(src, j, pat) { return j }
j = j + 1
}
return -1
}
// Trim spaces and tabs (with optional semicolon at end)
trim(s) {
local i = 0
local n = s.size()
loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 }
local j = n
loop(j > i && (s.substring(j-1,j) == " " || s.substring(j-1,j) == "\t" || s.substring(j-1,j) == ";")) { j = j - 1 }
return s.substring(i, j)
}
// Skip whitespace from position i
skip_ws(src, i) {
if src == null { return i }
local n = src.size()
local cont = 1
local guard = 0
local max = 100000
loop(cont == 1) {
if guard > max { return i } else { guard = guard + 1 }
if i < n {
if me.is_space(src.substring(i, i+1)) { i = i + 1 } else { cont = 0 }
} else { cont = 0 }
}
return i
}
// Find last occurrence of pattern in string (backward search)
last_index_of(src, pat) {
if src == null { return -1 }
if pat == null { return -1 }
local n = src.size()
local m = pat.size()
if m == 0 { return n }
if m > n { return -1 }
local i = n - m
loop(i >= 0) {
if me.starts_with(src, i, pat) { return i }
i = i - 1
}
return -1
}
}

View File

@ -0,0 +1,23 @@
// string_ops.hako — Common string utility functions
// Responsibility: Basic string operations used across the codebase
// Non-goals: JSON parsing, complex text processing
static box StringOps {
// Find substring starting from position
// Returns: index of first occurrence at/after pos, or -1 if not found
index_of_from(text, needle, pos) {
if text == null { return -1 }
if pos < 0 { pos = 0 }
local n = text.size()
if pos >= n { return -1 }
local m = needle.size()
if m <= 0 { return pos }
local i = pos
local limit = n - m
loop (i <= limit) {
if text.substring(i, i + m) == needle { return i }
i = i + 1
}
return -1
}
}

View File

@ -0,0 +1,32 @@
[module]
name = "selfhost.shared"
version = "1.0.0"
[exports]
# JSON adapter facade and common helpers
json_adapter = "json_adapter.hako"
common.mini_vm_scan = "common/mini_vm_scan.hako"
common.mini_vm_binop = "common/mini_vm_binop.hako"
common.mini_vm_compare = "common/mini_vm_compare.hako"
common.string_helpers = "common/string_helpers.hako"
common.string_ops = "common/string_ops.hako"
common.box_helpers = "common/box_helpers.hako"
# JSON tooling
json.mir_builder_min = "json/mir_builder_min.hako"
json.mir_v1_adapter = "json/mir_v1_adapter.hako"
json.core.json_cursor = "json/json_cursor.hako"
json.utils.json_utils = "json/json_utils.hako"
# MIR helpers (exported as stable module names)
mir.schema = "mir/mir_schema_box.hako"
mir.builder = "mir/block_builder_box.hako"
mir.io = "mir/mir_io_box.hako"
mir.json_emit = "mir/json_emit_box.hako"
[private]
# Internal builders kept private for now
# json/mir_builder2.hako, json/json_inst_encode_box.hako
[dependencies]
"selfhost.vm" = "^1.0.0"

View File

@ -0,0 +1,21 @@
// host_bridge_box.hako — HostBridgeBox (Phase A, Hako-side thin adapter)
// Responsibility: Provide minimal API to construct/call host boxes for VM backend.
// Note: Phase A supports only a small subset (FileBox). Phase B wires full Hako ABI.
static box HostBridgeBox {
// Create a box by name with args (Phase B: extern → Rust HostBridge)
// Route: hostbridge.box_new(name, args)
box_new(name, args) {
if args == null { args = new ArrayBox() }
return hostbridge.box_new(name, args)
}
// Call a method by name with Array args (Phase B: extern → Rust HostBridge)
// Route: hostbridge.box_call(receiver, method, args) → lowered to Extern("hostbridge.box_call")
box_call(inst, method, args) {
if inst == null { return null }
if args == null { args = new ArrayBox() }
return hostbridge.box_call(inst, method, args)
}
}

View File

@ -0,0 +1,105 @@
// json_scan.hako — JsonScanBox
// Escape-aware JSON structure scanning helpers.
using "lang/src/shared/json/core/string_scan.hako" as StringScanBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box JsonScanBox {
// minimal string→int (delegate to shared helper)
_str_to_int(s) { return StringHelpers.to_i64(s) }
// Seek the end index (inclusive) of an object starting at `start` (must be '{').
seek_obj_end(text, start) {
if text == null { return -1 }
if start < 0 || start >= text.size() { return -1 }
if call("String.substring/2", text, start, start+1) != "{" { return -1 }
local n = text.size()
local depth = 0
local i = start
local in_str = 0
loop (i < n) {
local ch = StringScanBox.read_char(text, i)
if in_str == 1 {
if ch == "\\" {
i = i + 2
continue
}
if ch == "\"" {
in_str = 0
i = i + 1
continue
}
i = i + 1
continue
}
if ch == "\"" {
local j = StringScanBox.find_quote(text, i+1)
if j < 0 { return -1 }
i = j + 1
continue
}
if ch == "{" {
depth = depth + 1
} else if ch == "}" {
depth = depth - 1
if depth == 0 { return i }
}
i = i + 1
}
return -1
}
// Seek the end index (inclusive) of an array starting at `start` (must be '[').
seek_array_end(text, start) {
if text == null { return -1 }
// Normalize start to integer (defensive against stringified input)
local start_s = "" + start
local start_i = me._str_to_int(start_s)
local n = text.size()
if start_i < 0 || start_i >= n { return -1 }
local first_ch = text.substring(start_i, start_i+1)
if first_ch != "[" { return -1 }
// main loop (escape-aware)
local depth = 0
local i = start_i
local in_str = 0
loop (i < n) {
local ch = StringScanBox.read_char(text, i)
if in_str == 1 {
if ch == "\\" {
i = i + 2
continue
}
if ch == "\"" {
in_str = 0
i = i + 1
continue
}
i = i + 1
continue
}
if ch == "\"" {
local j = StringScanBox.find_quote(text, i+1)
if j < 0 { return -1 }
i = j + 1
continue
}
if ch == "[" {
depth = depth + 1
} else if ch == "]" {
depth = depth - 1
if depth == 0 { return i }
}
i = i + 1
}
return -1
}
// Search for either plain or escaped key pattern starting at `pos`.
find_key_dual(text, plain, escaped, pos) {
if text == null { return -1 }
if pos < 0 { pos = 0 }
local p = StringScanBox.index_of_from(text, plain, pos)
if p >= 0 { return p }
return StringScanBox.index_of_from(text, escaped, pos)
}
}

View File

@ -0,0 +1,59 @@
// string_scan.hako — StringScanBox
// Escape-aware string scanning helpers for JSON/text processing.
using "lang/src/shared/common/string_ops.hako" as StringOps
static box StringScanBox {
// Return the single-character string at index i (empty if out of bounds)
read_char(text, i) {
if text == null { return "" }
if i < 0 { return "" }
local n = text.size()
if i >= n { return "" }
return text.substring(i, i+1)
}
// Find next unescaped double quote starting at or after pos
find_quote(text, pos) {
return me.find_unescaped(text, "\"", pos)
}
// Find substring starting from position (simple, non-escape-aware)
index_of_from(text, needle, pos) {
return StringOps.index_of_from(text, needle, pos)
}
// Find next occurrence of character `ch` in `text` at or after `pos`,
// ignoring escaped occurrences (preceded by a backslash).
find_unescaped(text, ch, pos) {
if text == null { return -1 }
if pos < 0 { pos = 0 }
local n = text.size()
local i = pos
loop (i < n) {
local c = me.read_char(text, i)
if c == "\\" {
// skip escape sequence
i = i + 2
continue
}
if c == ch { return i }
i = i + 1
}
return -1
}
// Given a starting index at a double quote, find the closing quote index
// respecting escape sequences; returns -1 if unterminated.
scan_string_end(text, start) {
if text == null { return -1 }
if start < 0 || start >= text.size() { return -1 }
if text.substring(start, start+1) != "\"" { return -1 }
local i = start + 1
local n = text.size()
loop (i < n) {
local c = me.read_char(text, i)
if c == "\\" { i = i + 2 continue }
if c == "\"" { return i }
i = i + 1
}
return -1
}
}

View File

@ -0,0 +1,12 @@
// json_canonical_box.hako — JsonCanonicalBox (Phase 20.5 Gate A unification)
// Responsibility: Provide a single entry to canonicalize JSON strings.
// Current behavior: identity (no-op) until Extern anchor bridging is wired.
// Future: call host anchor `nyash_json_canonicalize_h` via Extern/HostBridge to obtain canonical form.
static box JsonCanonicalBox {
canonicalize(json) {
// Phase-A: identityhost bridge未接続の間は常にno-op
// 期待動作: 文字列正規化は将来のExternに委譲。現状はそのまま返す。
return "" + json
}
}

View File

@ -0,0 +1,44 @@
// json_cursor.hako — JsonCursorBox (thin scan facade)
// Responsibility: provide a minimal, escape-aware scanning facade used by JsonFragBox
// Delegates to StringOps, StringScanBox and JsonScanBox.
using "lang/src/shared/common/string_ops.hako" as StringOps
using "lang/src/shared/json/core/string_scan.hako" as StringScanBox
using "lang/src/shared/json/core/json_scan.hako" as JsonScanBox
static box JsonCursorBox {
index_of_from(hay, needle, pos) {
return StringOps.index_of_from(hay, needle, pos)
}
// Alias: find_from (compat)
find_from(hay, needle, pos) { return me.index_of_from(hay, needle, pos) }
scan_string_end(text, quote_pos) { return StringScanBox.scan_string_end(text, quote_pos) }
seek_array_end(text, lbracket_pos) { return JsonScanBox.seek_array_end(text, lbracket_pos) }
seek_obj_end(text, start) { return JsonScanBox.seek_obj_end(text, start) }
find_key_dual(text, plain, escaped, pos) { return JsonScanBox.find_key_dual(text, plain, escaped, pos) }
// Extract a sequence of optional sign + digits starting at start_pos
digits_from(text, start_pos) {
if text == null { return "" }
local i = start_pos
local n = text.size()
local out = ""
if i < n {
local ch = text.substring(i, i+1)
if ch == "-" {
out = out + ch
i = i + 1
}
}
loop(i < n) {
local ch = text.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" {
out = out + ch
i = i + 1
} else { break }
}
return out
}
}
static box JsonCursorMain { main(args){ return 0 } }

View File

@ -0,0 +1,25 @@
// json_inst_encode_box.hako — Encode one MIR instruction Map -> JSON text (single return)
static box JsonInstEncodeBox {
_i(n) { return "" + n }
_q(s) { return s }
encode(node) {
if node == null { return "{}" }
local op = node.get("op")
return match op {
"const" => "{\"op\":\"const\",\"dst\":" + me._i(node.get("dst")) + ",\"value\":{\"type\":\"i64\",\"value\":" + me._i(node.get("value").get("value")) + "}}"
"compare" => "{\"op\":\"compare\",\"cmp\":" + me._q(node.get("cmp")) + ",\"lhs\":" + me._i(node.get("lhs")) + ",\"rhs\":" + me._i(node.get("rhs")) + ",\"dst\":" + me._i(node.get("dst")) + "}"
"binop" => "{\"op\":\"binop\",\"op_kind\":" + me._q(node.get("op_kind")) + ",\"lhs\":" + me._i(node.get("lhs")) + ",\"rhs\":" + me._i(node.get("rhs")) + ",\"dst\":" + me._i(node.get("dst")) + "}"
"branch" => "{\"op\":\"branch\",\"cond\":" + me._i(node.get("cond")) + ",\"then\":" + me._i(node.get("then")) + ",\"else\":" + me._i(node.get("else_id")) + "}"
"jump" => "{\"op\":\"jump\",\"target\":" + me._i(node.get("target")) + "}"
"ret" => "{\"op\":\"ret\",\"value\":" + me._i(node.get("value")) + "}"
"copy" => "{\"op\":\"copy\",\"dst\":" + me._i(node.get("dst")) + ",\"src\":" + me._i(node.get("src")) + "}"
"call" => "{\"op\":\"call\",\"name\":" + me._q(node.get("name")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
"boxcall" => "{\"op\":\"boxcall\",\"method\":" + me._q(node.get("method")) + ",\"recv\":" + me._i(node.get("recv")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
"newbox" => "{\"op\":\"newbox\",\"box_type\":" + me._q(node.get("box_type")) + ",\"args\":" + node.get("args_text") + ",\"dst\":" + me._i(node.get("dst")) + "}"
"mir_call" => "{\"op\":\"mir_call\"}"
_ => "{}"
}
}
}

View File

@ -0,0 +1,211 @@
// JsonUtilsBox — JSON読み取りユーティリティの共通箱
// 責務: JSON値抽出・JSON構造パース・トップレベル配列分割
// Extracted from JsonProgramBox (480行 → 345行, 削減 ~135行)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box JsonUtilsBox {
// Extract JSON value by key (returns value with '@' + end position marker)
extract_value(json, key) {
if json == null { return null }
local pattern = "\"" + key + "\""
local idx = StringHelpers.index_of(json, 0, pattern)
if idx < 0 { return null }
idx = idx + pattern.size()
idx = StringHelpers.skip_ws(json, idx)
if json.substring(idx, idx + 1) != ":" { return null }
idx = StringHelpers.skip_ws(json, idx + 1)
local res = me.read_value(json, idx)
local at = StringHelpers.last_index_of(res, "@")
if at < 0 { return null }
return res.substring(0, at)
}
// Extract string value by key with default fallback
extract_string_value(json, key, default_value) {
local raw = me.extract_value(json, key)
if raw == null { return default_value }
local trimmed = StringHelpers.trim(raw)
if trimmed.size() >= 2 && trimmed.substring(0,1) == "\"" && trimmed.substring(trimmed.size()-1, trimmed.size()) == "\"" {
return me.unescape_string(trimmed.substring(1, trimmed.size()-1))
}
return default_value
}
// Read JSON value (dispatch to appropriate reader)
read_value(json, idx) {
local n = json.size()
if idx >= n { return "@" + StringHelpers.int_to_str(idx) }
local ch = json.substring(idx, idx + 1)
if ch == "\"" { return me.read_string(json, idx) }
if ch == "{" { return me.read_object(json, idx) }
if ch == "[" { return me.read_array(json, idx) }
return me.read_literal(json, idx)
}
// Read JSON string (escape-aware) with position marker
read_string(json, idx) {
local i = idx + 1
local n = json.size()
local done = 0
loop(done == 0 && i < n) {
local ch = json.substring(i, i + 1)
if ch == "\\" {
i = i + 2
} else {
if ch == "\"" { done = 1 i = i + 1 } else { i = i + 1 }
}
}
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
}
// Skip JSON string (returns end position)
skip_string(json, idx) {
local i = idx + 1
local n = json.size()
local done = 0
loop(done == 0 && i < n) {
local ch = json.substring(i, i + 1)
if ch == "\\" { i = i + 2 }
else { if ch == "\"" { done = 1 i = i + 1 } else { i = i + 1 } }
}
return i
}
// Read JSON object (bracket-aware) with position marker
read_object(json, idx) {
local depth = 0
local i = idx
local n = json.size()
loop(i < n) {
local ch = json.substring(i, i + 1)
if ch == "\"" {
i = me.skip_string(json, i)
} else {
if ch == "{" { depth = depth + 1 }
else if ch == "}" {
depth = depth - 1
if depth == 0 { i = i + 1 break }
}
i = i + 1
}
}
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
}
// Read JSON array (bracket-aware) with position marker
read_array(json, idx) {
local depth = 0
local i = idx
local n = json.size()
loop(i < n) {
local ch = json.substring(i, i + 1)
if ch == "\"" {
i = me.skip_string(json, i)
} else {
if ch == "[" { depth = depth + 1 }
else if ch == "]" {
depth = depth - 1
if depth == 0 { i = i + 1 break }
}
i = i + 1
}
}
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
}
// Read JSON literal (number/true/false/null) with position marker
read_literal(json, idx) {
local n = json.size()
local i = idx
loop(i < n) {
local ch = json.substring(i, i + 1)
if ch == "," || ch == "}" || ch == "]" { break }
i = i + 1
}
return json.substring(idx, i) + "@" + StringHelpers.int_to_str(i)
}
// Split JSON array at top-level commas (depth-aware, escape-aware)
split_top_level(array_json) {
local out = new ArrayBox()
local n = array_json.size()
local i = 1
local start = 1
local depth = 0
local in_string = 0
loop(i < n - 1) {
local ch = array_json.substring(i, i + 1)
if in_string == 1 {
if ch == "\\" {
i = i + 2
} else {
if ch == "\"" { in_string = 0 }
i = i + 1
}
} else {
if ch == "\"" {
in_string = 1
i = i + 1
} else {
if ch == "{" || ch == "[" { depth = depth + 1 }
else if ch == "}" || ch == "]" { depth = depth - 1 }
else if ch == "," && depth == 0 {
local part = array_json.substring(start, i)
out.push(part)
start = i + 1
}
i = i + 1
}
}
}
if start < n - 1 {
out.push(array_json.substring(start, n - 1))
}
return out
}
// Unescape JSON string (convert \n, \t, \r, \\, \" to actual characters)
unescape_string(s) {
if s == null { return "" }
local out = ""
local i = 0
local n = s.size()
loop(i < n) {
local ch = s.substring(i, i + 1)
if ch == "\\" && i + 1 < n {
local nx = s.substring(i + 1, i + 2)
if nx == "\"" {
out = out + "\""
i = i + 2
} else {
if nx == "\\" {
out = out + "\\"
i = i + 2
} else {
if nx == "n" {
out = out + "\n"
i = i + 2
} else {
if nx == "r" {
out = out + "\r"
i = i + 2
} else {
if nx == "t" {
out = out + "\t"
i = i + 2
} else {
out = out + ch
i = i + 1
}
}
}
}
}
} else {
out = out + ch
i = i + 1
}
}
return out
}
}

View File

@ -0,0 +1,159 @@
// mir_builder2.hako — Instance builder引数渡しバグ回避のため、非staticで内部状態を保持
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using lang.compiler.emit.common.json_emit as JsonEmitBox
using lang.compiler.emit.common.mir_emit as MirEmitBox
using lang.compiler.emit.common.header_emit as HeaderEmitBox
box MirJsonBuilder2 {
st: MapBox
// ---- lifecycle ----
setup() {
me.st = map({
buf: "",
phase: 0,
first_inst: 1,
blocks: new ArrayBox(),
cur_block_index: -1,
fn_name: "",
prefer_rebuild: 0,
append_headers: 0,
append_insts: 0
})
}
// ---- internal helpers ----
_get_buf() { return me.st.get("buf") }
_set_buf(s) { me.st.set("buf", s) }
_append(s) { local b = me._get_buf() me._set_buf(b + s) }
_append_header(s) {
local on = me.st.get("append_headers")
if on != null && on == 1 { me._append(s) }
}
_append_inst_json(node) {
// Append per-instruction JSON only when explicitly enabled (dev aid)
local on = me.st.get("append_insts")
if on != null && on == 1 { me._append(JsonEmitBox.to_json(node)) }
}
_int_to_str(n) { return StringHelpers.int_to_str(n) }
_cur_insts() {
local blks = me.st.get("blocks")
if blks == null || blks.size == null { return null }
local n = blks.size()
if n <= 0 { return null }
local idx = me.st.get("cur_block_index")
if idx == null || idx < 0 || idx >= n { idx = n - 1 }
local blk = blks.get(idx)
return blk.get("instructions")
}
_comma_if_needed() {
// Only relevant when appending instruction JSON text
local ai = me.st.get("append_insts")
if ai == null || ai != 1 { return }
local first = me.st.get("first_inst")
if first == 1 { me.st.set("first_inst", 0) return }
me._append(",")
}
// ---- building APIインスタンスメソッド----
start_module() { me.st.set("phase", 1) me._append_header("{\"functions\":[") }
start_function(name) {
me.st.set("phase", 2)
me.st.set("fn_name", name)
me.st.set("blocks", new ArrayBox())
me.st.set("cur_block_index", -1)
me._append_header("{\"name\":" + StringHelpers.json_quote(name) + ",\"params\":[],\"blocks\":[")
}
start_block(id) {
me.st.set("phase", 3)
me.st.set("first_inst", 1)
// 配列側
local blk = map({ id: id, instructions: new ArrayBox() })
local blks = me.st.get("blocks")
blks.push(blk)
me.st.set("cur_block_index", blks.size() - 1)
// 文字列側
me._append_header("{\"id\":" + me._int_to_str(id) + ",\"instructions\":[")
}
end_block_continue() { me._append_header("]},") }
end_all() {
me.st.set("phase", 5)
// Prefer route to rebuild on to_string() for safer parity
me.st.set("prefer_rebuild", 1)
me._append_header("]}]}]}")
}
add_const(dst, val) {
me._comma_if_needed()
// 構造
local node = MirEmitBox.make_const(dst, val)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
// テキスト
me._append_inst_json(node)
}
add_compare(kind, lhs, rhs, dst) {
me._comma_if_needed()
local node = MirEmitBox.make_compare(kind, lhs, rhs, dst)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
me._append_inst_json(node)
}
add_copy(dst, src) {
me._comma_if_needed()
local node = MirEmitBox.make_copy(dst, src)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
me._append_inst_json(node)
}
add_branch(cond, then_id, else_id) {
me._comma_if_needed()
local node = MirEmitBox.make_branch(cond, then_id, else_id)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
me._append_inst_json(node)
}
add_jump(target) {
me._comma_if_needed()
local node = MirEmitBox.make_jump(target)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
me._append_inst_json(node)
}
add_ret(val) {
me._comma_if_needed()
local node = MirEmitBox.make_ret(val)
local insts = me._cur_insts()
if insts != null && insts.push != null { insts.push(node) }
me._append_inst_json(node)
}
// ---- outputs ----
to_string() {
local pref = me.st.get("prefer_rebuild")
if pref != null && pref == 1 { return me.to_string_rebuild() }
return me._get_buf()
}
to_string_rebuild() {
// 構造blocks → instructionsを正として再構築HeaderEmitBox 経由)
local name = me.st.get("fn_name")
local blks = me.st.get("blocks")
local blocks = new ArrayBox()
if blks != null && blks.size != null {
local n = blks.size()
local i = 0
loop (i < n) {
local blk = blks.get(i)
local bid = blk.get("id")
local insts = blk.get("instructions")
blocks.push(HeaderEmitBox.make_block(bid, insts))
i = i + 1
}
}
local fns = new ArrayBox()
fns.push(HeaderEmitBox.make_function_main(blocks))
return JsonEmitBox.to_json(HeaderEmitBox.make_module_with_functions(fns))
}
}
static box MirJsonBuilder2Stub { main(args) { return 0 } }

View File

@ -0,0 +1,439 @@
// mir_builder_min.nyash — Minimal MIR(JSON v0) builder for selfhost tests
// Scope: selfhost only (apps/selfhost/...); no core/runtime changes.
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
using lang.compiler.emit.common.json_emit as JsonEmitBox
using lang.compiler.emit.common.mir_emit as MirEmitBox
using lang.compiler.emit.common.call_emit as CallEmitBox
using lang.compiler.emit.common.newbox_emit as NewBoxEmitBox
using "lang/src/shared/json/json_inst_encode_box.hako" as JsonInstEncodeBox
box MirJsonBuilderMin {
buf: StringBox
phase: IntegerBox
first_inst: IntegerBox
blocks: ArrayBox
cur_block_index: IntegerBox
fn_name: StringBox
trace: IntegerBox
verify: IntegerBox
birth() {
me.buf = ""
me.phase = 0
me.first_inst = 1
me.blocks = new ArrayBox()
me.cur_block_index = -1
me.fn_name = ""
me.trace = 0
me.verify = 0
}
// Internal helpers
_get_buf() { return me.buf }
_set_buf(s) { me.buf = s return me }
_append(s) {
me.buf = me.buf + s
return me
}
_int_to_str(n) { return StringHelpers.int_to_str(n) }
_to_i64(x) { return StringHelpers.to_i64(x) }
_quote(s) { return StringHelpers.json_quote(s) }
_ids_range_json(start, count) {
local i = 0
local out = "["
loop (i < count) {
if i > 0 { out = out + "," }
out = out + me._int_to_str(start + i)
i = i + 1
}
out = out + "]"
return out
}
_ids_array_from_json_text(arr_text) {
local out = new ArrayBox()
if arr_text == null { return out }
local s = "" + arr_text
local i = 0
loop (i < s.size()) {
local ch = s.substring(i, i+1)
if ch >= "0" && ch <= "9" {
local j = i
loop (j < s.size()) {
local cj = s.substring(j, j+1)
if !(cj >= "0" && cj <= "9") { break }
j = j + 1
}
local num = me._to_i64(s.substring(i, j))
out.push(num)
i = j
} else {
i = i + 1
}
}
return out
}
_ensure_phase(want) {
if me.phase + 1 < want { return me }
return me
}
// Public builder steps
start_module() {
if me.trace == 1 { print("[DEBUG start_module] starting") }
me.phase = 1
return me._append("{\"functions\":[")
}
start_function(name) {
if me.trace == 1 { print("[DEBUG start_function] name=" + name) }
me.phase = 2
me.fn_name = name
me.blocks = new ArrayBox()
me.cur_block_index = -1
if me.trace == 1 { print("[DEBUG start_function] after set, fn_name=" + me.fn_name) }
local b = "{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
return me._append(b)
}
start_block(id) {
me.phase = 3
me.first_inst = 1
local blk = map({ id: id, instructions: new ArrayBox() })
me.blocks.push(blk)
me.cur_block_index = me.blocks.size() - 1
local b = "{\"id\":" + me._int_to_str(id) + ",\"instructions\":["
return me._append(b)
}
end_block_continue() {
return me._append("]},")
}
_comma_if_needed() {
if me.first_inst == 1 {
me.first_inst = 0
return me
}
return me._append(",")
}
add_const(dst, val) {
me._comma_if_needed()
local node = MirEmitBox.make_const(dst, val)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_compare(kind, lhs, rhs, dst) {
me._comma_if_needed()
local node = MirEmitBox.make_compare(kind, lhs, rhs, dst)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_binop(kind, lhs, rhs, dst) {
me._comma_if_needed()
local node = { op:"binop", op_kind:kind, lhs:lhs, rhs:rhs, dst:dst }
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_ret(val) {
me._comma_if_needed()
local node = MirEmitBox.make_ret(val)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_call_ids(name, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = CallEmitBox.make_call(name, args, dst)
node.set("args_text", args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_call_range(name, start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(start + i)
i = i + 1
}
local args_text = me._ids_range_json(start, count)
local node = CallEmitBox.make_call(name, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_boxcall_range(method, recv_id, args_start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(args_start + i)
i = i + 1
}
local args_text = me._ids_range_json(args_start, count)
local node = CallEmitBox.make_boxcall(method, recv_id, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_newbox_range(box_type, args_start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(args_start + i)
i = i + 1
}
local node = NewBoxEmitBox.with_args_array(NewBoxEmitBox.make_new(box_type, args, dst), args)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_newbox_ids(box_type, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = NewBoxEmitBox.make_new(box_type, args, dst)
node = NewBoxEmitBox.with_args_text(node, args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
_args_array_json_from_ids(ids_start, count) {
return me._ids_range_json(ids_start, count)
}
add_mir_call_global_ids(name, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = CallEmitBox.make_mir_call_global(name, args, dst)
node.set("args_text", args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_extern_ids(name, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = CallEmitBox.make_mir_call_extern(name, args, dst)
node.set("args_text", args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_method_ids(method, recv_id, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
node.set("args_text", args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_constructor_ids(box_type, args_json_text, dst) {
me._comma_if_needed()
local args = me._ids_array_from_json_text(args_json_text)
local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
node.set("args_text", args_json_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_global_range(name, start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(start + i)
i = i + 1
}
local args_text = me._ids_range_json(start, count)
local node = CallEmitBox.make_mir_call_global(name, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_extern_range(name, start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) { args.push(start + i) i = i + 1 }
local args_text = me._ids_range_json(start, count)
local node = CallEmitBox.make_mir_call_extern(name, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_method_range(method, recv_id, args_start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(args_start + i)
i = i + 1
}
local args_text = me._ids_range_json(args_start, count)
local node = CallEmitBox.make_mir_call_method(method, recv_id, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_mir_call_constructor_range(box_type, args_start, count, dst) {
me._comma_if_needed()
local args = new ArrayBox()
local i = 0
loop(i < count) {
args.push(args_start + i)
i = i + 1
}
local args_text = me._ids_range_json(args_start, count)
local node = CallEmitBox.make_mir_call_constructor(box_type, args, dst)
node.set("args_text", args_text)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_branch(cond, then_id, else_id) {
me._comma_if_needed()
local node = MirEmitBox.make_branch(cond, then_id, else_id)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_jump(target) {
me._comma_if_needed()
local node = MirEmitBox.make_jump(target)
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
add_copy(dst, src) {
me._comma_if_needed()
local node = { op:"copy", dst:dst, src:src }
me._push_inst_map(node)
return me._append(JsonEmitBox.to_json(node))
}
end_all() {
me.phase = 5
return me._append("]}]}]}")
}
to_string() { return me._get_buf() }
_cur_insts() {
local blks = me.blocks
if blks == null { return null }
local n = 0
n = BoxHelpers.array_len(blks)
if n <= 0 { return null }
local idx = me.cur_block_index
if idx == null { idx = -1 }
if idx < 0 || idx >= n { idx = n - 1 }
local blk = BoxHelpers.array_get(blks, idx)
if blk == null { return null }
return BoxHelpers.map_get(blk, "instructions")
}
verify_builder_state() { return 0 }
_verify_enabled() {
return me.verify
}
_trace_enabled() {
return me.trace
}
_push_inst(node) {
local before = 0
local insts0 = me._cur_insts()
before = BoxHelpers.array_len(insts0)
if insts0 == null || insts0.push == null { return }
insts0.push(node)
if me._verify_enabled() == 1 {
local insts1 = me._cur_insts()
local after = BoxHelpers.array_len(insts1)
local op_str = BoxHelpers.map_get(node, "op")
if after != before + 1 { print("[builder-verify] size mismatch before=" + before + " after=" + after + " op=" + op_str) }
}
if me._trace_enabled() == 1 {
local op_str_trace = BoxHelpers.map_get(node, "op")
print("[builder-trace] push op=" + op_str_trace)
}
}
_push_inst_map(node) { return me._push_inst(node) }
enable_trace(on) {
if on == null { on = 1 }
me.trace = on
return me
}
enable_verify(on) {
if on == null { on = 1 }
me.verify = on
return me
}
get_current_instructions() { return me._cur_insts() }
get_blocks_array() { return me.blocks }
get_function_structure() {
local blks = me.get_blocks_array()
return map({ name: me.fn_name, params: new ArrayBox(), blocks: blks })
}
emit_to_string() { return me.to_string() }
_inst_json(node) { return JsonInstEncodeBox.encode(node) }
to_string_rebuild() {
local name = me.fn_name
local blks = me.get_blocks_array()
local blks_size_str = "null"
local blks_len = BoxHelpers.array_len(blks)
if blks_len >= 0 { blks_size_str = me._int_to_str(blks_len) }
print("[DEBUG rebuild] fn_name=" + name + " blks.size()=" + blks_size_str)
local out = "{\"functions\":[{\"name\":" + me._quote(name) + ",\"params\":[],\"blocks\":["
local n = blks_len
print("[DEBUG rebuild] n=" + me._int_to_str(n))
local i = 0
loop (i < n) {
if i > 0 { out = out + "," }
local blk = BoxHelpers.array_get(blks, i)
local bid = BoxHelpers.map_get(blk, "id")
out = out + "{\"id\":" + me._int_to_str(bid) + ",\"instructions\":["
local insts = BoxHelpers.map_get(blk, "instructions")
local m = BoxHelpers.array_len(insts)
local j = 0
loop (j < m) {
if j > 0 { out = out + "," }
out = out + me._inst_json(BoxHelpers.array_get(insts, j))
j = j + 1
}
out = out + "]}"
i = i + 1
}
out = out + "]}]}"
return out
}
}

View File

@ -0,0 +1,119 @@
// mir_v1_adapter.nyash — Minimal JSON v1 (mir_call) to v0 adapter for selfhost
// Scope: selfhost only. Transforms op:"mir_call" into legacy v0 ops (call/boxcall/newbox).
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
static box MirJsonV1Adapter {
// Delegate to JsonCursorBox (escape-aware implementations, fixes 2 escape bugs)
_index_of_from(hay, needle, pos) {
return JsonCursorBox.index_of_from(hay, needle, pos)
}
_seek_obj_end(text, obj_start) {
return JsonCursorBox.seek_obj_end(text, obj_start)
}
_seek_array_end(text, pos_after_bracket) {
// JsonCursorBox.seek_array_end expects pos at '[', adjust since we receive pos after '['
if text == null { return -1 }
if pos_after_bracket <= 0 { return -1 }
local bracket_pos = pos_after_bracket - 1
return JsonCursorBox.seek_array_end(text, bracket_pos)
}
_read_digits(text, pos) {
return JsonCursorBox.digits_from(text, pos)
}
_read_string(text, pos_after_quote) {
local end = me._index_of_from(text, "\"", pos_after_quote)
if end < 0 { return "" }
return text.substring(pos_after_quote, end)
}
// Convert one mir_call object JSON segment to v0 legacy op JSON
_convert_mir_call(seg) {
// dst
local dpos = me._index_of_from(seg, "\"dst\":", 0)
local dst = 0
if dpos >= 0 {
local dd = me._read_digits(seg, dpos + 6)
if dd != "" { dst = 0 + ("" + dd).size() // placeholder to avoid lints
dst = 0 // reassign after parse
// parse int
local acc = 0
local i = 0
loop(i < dd.size()) { acc = acc * 10 + ("0123456789".indexOf(dd.substring(i,i+1))) i = i + 1 }
dst = acc
}
}
// args array substring (reuse existing as-is)
local ak = me._index_of_from(seg, "\"args\":[", 0)
local args_json = "[]"
if ak >= 0 {
local lb = me._index_of_from(seg, "[", ak)
if lb >= 0 {
local rb = me._seek_array_end(seg, lb + 1)
if rb > lb { args_json = seg.substring(lb, rb + 1) }
}
}
// callee type
local ck = me._index_of_from(seg, "\"callee\":{\"type\":\"", 0)
if ck < 0 { return seg }
local ct = me._read_string(seg, ck + 18)
if ct == "Global" {
local nk = me._index_of_from(seg, "\"name\":\"", ck)
local name = ""
if nk >= 0 { name = me._read_string(seg, nk + 8) }
return "{\"op\":\"call\",\"name\":\"" + name + "\",\"args\":" + args_json + ",\"dst\":" + dst + "}"
}
if ct == "Method" {
local mk = me._index_of_from(seg, "\"method\":\"", ck)
local method = ""
if mk >= 0 { method = me._read_string(seg, mk + 10) }
local rk = me._index_of_from(seg, "\"receiver\":", ck)
local recv = 0
if rk >= 0 {
local rd = me._read_digits(seg, rk + 11)
if rd != "" {
local acc2 = 0
local i2 = 0
loop(i2 < rd.size()) { acc2 = acc2 * 10 + ("0123456789".indexOf(rd.substring(i2,i2+1))) i2 = i2 + 1 }
recv = acc2
}
}
return "{\"op\":\"boxcall\",\"method\":\"" + method + "\",\"recv\":" + recv + ",\"args\":" + args_json + ",\"dst\":" + dst + "}"
}
if ct == "Constructor" {
local tk = me._index_of_from(seg, "\"box_type\":\"", ck)
local tname = ""
if tk >= 0 { tname = me._read_string(seg, tk + 12) }
return "{\"op\":\"newbox\",\"box_type\":\"" + tname + "\",\"args\":" + args_json + ",\"dst\":" + dst + "}"
}
// default: return original segment
return seg
}
// Public API: convert all mir_call objects within MIR(JSON v1) to v0 legacy ops
to_v0(mjson) {
if mjson == null { return null }
local out = mjson
local cur = 0
loop(true) {
local op = me._index_of_from(out, "\"op\":\"mir_call\"", cur)
if op < 0 { break }
// find object bounds
local start = op
loop(start >= 0) {
if out.substring(start, start+1) == "{" { break }
start = start - 1
}
if start < 0 { break }
local end = me._seek_obj_end(out, start)
if end <= start { break }
local seg = out.substring(start, end)
local rep = me._convert_mir_call(seg)
out = out.substring(0, start) + rep + out.substring(end, out.size())
cur = start + rep.size()
}
return out
}
}
static box MirJsonV1AdapterStub { main(args) { return 0 } }

View File

@ -0,0 +1,26 @@
// mir_v1_meta_inject_box.hako — Inject metadata.extern_c and v1 header into MIR(JSON)
// Responsibility: Convert a minimal v0-like MIR JSON (with only {"functions":[...]})
// into a v1 root with kind/schema_version/metadata.extern_c while preserving functions.
static box MirV1MetaInjectBox {
// Injects: {"kind":"MIR","schema_version":"1.0","metadata":{"extern_c":EXTERNS}, ...}
// - mir_json must be an object JSON starting with '{' and ending with '}'
// - externs_json must be a JSON array text (e.g., "[]" or "[{\"func\":...,\"symbol\":...}]")
inject_meta_externs(mir_json, externs_json) {
if mir_json == null { return null }
local s = "" + mir_json
if s.size() < 2 { return s }
if s.substring(0,1) != "{" { return s }
local ext = externs_json
if ext == null { ext = "[]" }
if ext.size() == 0 { ext = "[]" }
// Construct v1 root by inserting header + metadata at the beginning
// { <insert here> existing_without_leading_brace
local tail = s.substring(1, s.size())
local head = "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"metadata\":{\"extern_c\":" + ext + "},"
return head + tail
}
}
static box MirV1MetaInjectMain { main(args) { return 0 } }

View File

@ -0,0 +1,74 @@
// json_frag.hako — JSON v0 断片抽出ユーティリティBox
// 責務: 文字列JSONから key:int / key:str を簡便に取り出す。
// 非責務: 実行・評価構造検査やVM実行は他箱に委譲
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box JsonFragBox {
// 基本ヘルパ
index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
read_digits(text, pos) { return StringHelpers.read_digits(text, pos) }
_str_to_int(s) { return StringHelpers.to_i64(s) }
// key に続く数値(最初の一致)を返す。見つからなければ null。
get_int(seg, key) {
local pat1 = "\"" + key + "\":"
local p = me.index_of_from(seg, pat1, 0)
if p >= 0 {
local v = me.read_digits(seg, p + pat1.size())
if v != "" { return me._str_to_int(v) }
}
return null
}
// key に続く "..." の文字列(最初の一致)を返す。見つからなければ空文字。
get_str(seg, key) {
local pat = "\"" + key + "\":\""
local p = me.index_of_from(seg, pat, 0)
if p >= 0 {
local vstart = p + pat.size() // start of value (right after opening quote)
local vend = JsonCursorBox.scan_string_end(seg, vstart - 1)
if vend > vstart { return seg.substring(vstart, vend) }
}
return ""
}
// Strict variants: emit an error when the key is missing
get_int_strict(seg, key) {
local v = me.get_int(seg, key)
if v == null {
print("[ERROR] Missing key: " + key)
}
return v
}
get_str_strict(seg, key) {
local v = me.get_str(seg, key)
if v == "" {
print("[ERROR] Missing key: " + key)
}
return v
}
// ブロック0の instructions を丸ごと返す(配列の中身のみ返す)。
block0_segment(mjson) {
if mjson == null { return "" }
// Find the instructions array start reliably
local key = "\"instructions\":["
local pk = call("String.indexOf/2", mjson, key)
if pk < 0 { return "" }
// '[' position
local arr_bracket = pk + key.size() - 1
// Use escape-aware scanner to find matching ']'
local endp = JsonCursorBox.seek_array_end(mjson, arr_bracket)
if endp < 0 { return "" }
return mjson.substring(arr_bracket + 1, endp)
}
// Alias for legacy/buggy resolvers that drop underscores in method names.
// Keep as a thin forwarder to preserve strict naming in source while
// unblocking runtimes that accidentally call `block0segment`.
}

View File

@ -0,0 +1,18 @@
// Adapter for JSON cursor operations (extracted)
// Wraps MiniJsonCur and exposes a stable facade
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonCur
static box MiniJson {
read_quoted_from(s, pos) {
local cur = new MiniJsonCur()
return cur.read_quoted_from(s, pos)
}
read_digits_from(s, pos) {
local cur = new MiniJsonCur()
return cur.read_digits_from(s, pos)
}
next_non_ws(s, pos) {
local cur = new MiniJsonCur()
return cur.next_non_ws(s, pos)
}
}

View File

@ -0,0 +1,21 @@
# selfhost/shared/mir — JSON emit / LoopForm helpers
責務
- MIR(JSON v0) の構築・出力Gate C 互換)。
- ループ構造LoopForm: Header/Latch/Exitの最小組み立て。
方針(型依存ロジックの統一)
- Array の長さ: `ArrayBox.size/1``StringHelpers.to_i64` で整数化。
- Map の取得: `MapBox.get/2``type`/`value` 等)でアクセス。
- 整数化: `StringHelpers.to_i64` を使用。文字列ヒューリスティックは禁止。
- JSON 文字列化: `StringHelpers.json_quote` を使用(安全なエスケープ)。
禁止Fail-Fast 防止のため)
- `.getField` 相当のフィールド直参照の混入。
- 文字列化結果からの数値抽出(`"MapBox("` 判定など)。
関連
- `json_emit_box.hako`: Gate C JSON 出力numbers unwrapped
- `mir_schema_box.hako`: MIR(JSON) 構築ヘルパーv0 スキーマ)。
- `loop_form_box.hako`: LoopForm 構造の最小組み立て。

View File

@ -0,0 +1,409 @@
// selfhost/shared/mir/block_builder_box.hako
// BlockBuilderBox — small helpers to assemble P1 shapes
using "lang/src/shared/mir/mir_schema_box.hako" as MirSchemaBox
using "lang/src/shared/mir/loop_form_box.hako" as LoopFormBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box BlockBuilderBox {
_array_len(arr) {
if arr == null { return 0 }
return StringHelpers.to_i64(call("ArrayBox.size/1", arr))
}
_unwrap_vid(val) {
if val == null { return null }
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local inner = call("MapBox.get/2", val, "value")
if inner != null { return StringHelpers.to_i64(inner) }
}
return StringHelpers.to_i64(val)
}
// Ensure every block ends with a terminator (ret/jump/branch/throw)
_ensure_terminators(mod_full) {
if mod_full == null { return mod_full }
local fns = call("MapBox.get/2", mod_full, "functions")
if fns == null { return mod_full }
local fn_count = me._array_len(fns)
local fi = 0
loop(fi < fn_count) {
local func = call("ArrayBox.get/2", fns, fi)
if func != null {
local blocks = call("MapBox.get/2", func, "blocks")
if blocks != null {
local bn = me._array_len(blocks)
local bi = 0
loop(bi < bn) {
local block = call("ArrayBox.get/2", blocks, bi)
local insts = null
if block != null { insts = call("MapBox.get/2", block, "instructions") }
if insts != null {
local n = me._array_len(insts)
if n > 0 {
local last = call("ArrayBox.get/2", insts, n - 1)
local op = ""
if last != null {
local maybe_op = call("MapBox.get/2", last, "op")
if maybe_op != null { op = "" + maybe_op }
}
if !(op == "ret" || op == "return" || op == "jump" || op == "branch" || op == "throw") {
local idx = 0
local max_vid = -1
local last_dst = -1
loop(idx < n) {
local inst = call("ArrayBox.get/2", insts, idx)
local dst_map = null
if inst != null { dst_map = call("MapBox.get/2", inst, "dst") }
local vid = me._unwrap_vid(dst_map)
if vid != null {
if vid > max_vid { max_vid = vid }
last_dst = vid
}
idx = idx + 1
}
if last_dst < 0 {
last_dst = max_vid + 1
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(last_dst, 0))
}
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(last_dst))
}
} else {
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(0, 0))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(0))
}
}
bi = bi + 1
}
}
}
fi = fi + 1
}
return mod_full
}
// const→ret (returns literal)
const_ret(val) {
local insts = new ArrayBox()
// Public name route: MirSchemaBox.* helpers
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(1, val))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(1))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(module)
}
_ops_str_from_insts(insts) {
if insts == null { return "" }
local i = 0; local out = ""
local count = me._array_len(insts)
loop(i < count) {
if i > 0 { out = out + "," }
local it = call("ArrayBox.get/2", insts, i)
if it != null {
local op = call("MapBox.get/2", it, "op")
if op != null { out = out + op } else { out = out + "?" }
} else { out = out + "?" }
i = i + 1
}
return out
}
_ops_str_from_blocks(blocks) {
if blocks == null { return "" }
local i = 0; local all = ""
local count = me._array_len(blocks)
loop(i < count) {
if i > 0 { all = all + "|" }
local b = call("ArrayBox.get/2", blocks, i)
local insts = null
if b != null { insts = call("MapBox.get/2", b, "instructions") }
all = all + me._ops_str_from_insts(insts)
i = i + 1
}
return all
}
const_ret_ops(val) {
// Op-shape helper (no MIR dependency): const → ret
// Returns a stable summary string for lightweight tests
return "const,ret"
}
// compare→branch→jump→ret (diamond)
compare_branch(lhs, rhs, cmp) {
// entry
local b0 = new ArrayBox()
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, lhs))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, rhs))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_compare(cmp, 1, 2, 3))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_branch(3, 1, 2))
// then/else
local b1 = new ArrayBox(); call("ArrayBox.push/2", b1, MirSchemaBox.inst_const(6, 1)); call("ArrayBox.push/2", b1, MirSchemaBox.inst_jump(3))
local b2 = new ArrayBox(); call("ArrayBox.push/2", b2, MirSchemaBox.inst_const(6, 0)); call("ArrayBox.push/2", b2, MirSchemaBox.inst_jump(3))
// merge
local b3 = new ArrayBox(); call("ArrayBox.push/2", b3, MirSchemaBox.inst_ret(6))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0))
call("ArrayBox.push/2", blocks, MirSchemaBox.block(1, b1))
call("ArrayBox.push/2", blocks, MirSchemaBox.block(2, b2))
call("ArrayBox.push/2", blocks, MirSchemaBox.block(3, b3))
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(module)
}
// binop: const lhs/rhs; binop -> dst=3; ret 3
binop(lhs, rhs, opk) {
local b0 = new ArrayBox()
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, lhs))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, rhs))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_binop(opk, 1, 2, 3))
call("ArrayBox.push/2", b0, MirSchemaBox.inst_ret(3))
local blocks = new ArrayBox(); call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0))
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(module)
}
binop_ops(lhs, rhs, opk) {
// Op-shape helper (no MIR dependency): const,const → binop → ret
// Returns a stable summary string for lightweight tests
return "const,const,binop,ret"
}
// loop_counter: ops summary only配列/Map生成を伴わない純テスト用
loop_counter_ops(limit) {
// Summary of operations by shape (preheader|header|body paths|latch|exit)
// P1 intent: const,const,const,const,const,const,jump|compare,branch|binop,jump|phi,phi,jump|phi,ret
return "const,const,const,const,const,const,jump|compare,branch|binop,jump|phi,phi,jump|phi,ret"
}
// loop_counter: while (i < limit) { i = i + 1 } ; return i
loop_counter(limit) {
local b0 = new ArrayBox(); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(1, 0)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(2, limit)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_const(4, 1)); call("ArrayBox.push/2", b0, MirSchemaBox.inst_jump(1))
local b1 = new ArrayBox(); call("ArrayBox.push/2", b1, MirSchemaBox.inst_compare("Lt", 1, 2, 3)); call("ArrayBox.push/2", b1, MirSchemaBox.inst_branch(3, 2, 3))
local b2 = new ArrayBox(); call("ArrayBox.push/2", b2, MirSchemaBox.inst_binop("Add", 1, 4, 1)); call("ArrayBox.push/2", b2, MirSchemaBox.inst_jump(1))
local b3 = new ArrayBox(); call("ArrayBox.push/2", b3, MirSchemaBox.inst_ret(1))
local blocks = new ArrayBox();
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, b0)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(1, b1)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(2, b2)); call("ArrayBox.push/2", blocks, MirSchemaBox.block(3, b3))
local module = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(module)
}
// LoopForm variant (with PHI + structured exit) — used by LoopForm tests/bring-up
loop_counter_loopform(limit, skip_value, break_value) {
local module = LoopFormBox.loop_counter(limit, skip_value, break_value)
return me._ensure_terminators(module)
}
loop_counter_loopform_json(limit, skip_value, break_value) {
local module = me.loop_counter_loopform(limit, skip_value, break_value)
return JsonEmitBox.to_json(module)
}
to_gate_c_json(module) {
if module == null { return "" }
// NOTE: Caller (e.g. extern_call_ival_ret) already calls _ensure_terminators
// Double-call causes corruption. Skip normalization here.
return JsonEmitBox.to_json(module)
}
compare_branch_ops(lhs, rhs, cmp) {
// Op-shape helper (no MIR dependency):
// b0: const,const,compare,branch | b1: const,jump | b2: const,jump | b3: ret
return "const,const,compare,branch|const,jump|const,jump|ret"
}
// --- P3 helpers: minimal mir_call shapes ---
// extern call (e.g., nyrt.ops.op_eq) with provided arg ids and return
extern_call_ret(name, args_ids, dst) {
local b0 = [ MirSchemaBox.inst_mir_call_extern(name, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
return me._ensure_terminators(_m)
}
global_call_ret(name, args_ids, dst, name_literal) {
local actual_name = name
if name_literal != null {
local repr = "" + name
if repr.indexOf("ArrayBox(") == 0 {
actual_name = name_literal
}
}
local b0 = [ MirSchemaBox.inst_mir_call_global(actual_name, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
return me._ensure_terminators(_m)
}
method_call_ret(method, recv_id, args_ids, dst, method_literal) {
local actual_method = method
if method_literal != null {
local repr = "" + method
if repr.indexOf("ArrayBox(") == 0 {
actual_method = method_literal
}
}
local b0 = [ MirSchemaBox.inst_mir_call_method(actual_method, recv_id, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
return me._ensure_terminators(_m)
}
constructor_call_ret(box_type, args_ids, dst, box_type_literal) {
local actual_box_type = box_type
if box_type_literal != null {
local repr = "" + box_type
if repr.indexOf("ArrayBox(") == 0 {
actual_box_type = box_type_literal
}
}
local b0 = [ MirSchemaBox.inst_mir_call_constructor(actual_box_type, args_ids, dst), MirSchemaBox.inst_ret(dst) ]
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
return me._ensure_terminators(_m)
}
// --- P4 thin adapters: immediate-value variants -------------------------
// These helpers materialize integer constants for arguments/receiver and
// then reuse the minimal call+ret shapes above. Output remains
// { version, kind, functions: [...] } compatible.
extern_call_ival_ret(name, arg_vals, name_literal) {
// Materialize r1..rN from arg_vals, call -> r(N+1), ret r(N+1)
// Pattern: exact copy of const_ret structure (new ArrayBox(), direct inline)
local insts = new ArrayBox()
// Preserve literal name even if caller-side evaluation overwrote the first argument.
local actual_name = name
if name_literal != null {
local name_repr = "" + name
if name_repr.indexOf("ArrayBox(") == 0 {
actual_name = name_literal
}
}
local n = 0
local i = 0
local m = 0
if arg_vals != null { m = me._array_len(arg_vals) }
loop(i < m) {
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
i = i + 1
}
n = m
local args_ids = new ArrayBox()
i = 0
loop(i < n) {
call("ArrayBox.push/2", args_ids, i)
i = i + 1
}
local dst = n
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_extern(actual_name, args_ids, dst))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(_m)
}
global_call_ival_ret(name, arg_vals, name_literal) {
local insts = new ArrayBox()
local actual_name = name
if name_literal != null {
local repr = "" + name
if repr.indexOf("ArrayBox(") == 0 {
actual_name = name_literal
}
}
local m = 0
if arg_vals != null { m = me._array_len(arg_vals) }
local i = 0
loop(i < m) {
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
i = i + 1
}
local args_ids = new ArrayBox()
i = 0
loop(i < m) {
call("ArrayBox.push/2", args_ids, i)
i = i + 1
}
local dst = m
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_global(actual_name, args_ids, dst))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(_m)
}
method_call_ival_ret(method, recv_val, arg_vals, method_literal) {
local insts = new ArrayBox()
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(0, recv_val))
local m = 0
if arg_vals != null { m = me._array_len(arg_vals) }
local i = 0
loop(i < m) {
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(1 + i, call("ArrayBox.get/2", arg_vals, i)))
i = i + 1
}
local args_ids = new ArrayBox()
i = 0
loop(i < m) {
call("ArrayBox.push/2", args_ids, 1 + i)
i = i + 1
}
local dst = 1 + m
local actual_method = method
if method_literal != null {
local repr = "" + method
if repr.indexOf("ArrayBox(") == 0 {
actual_method = method_literal
}
}
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_method(actual_method, 0, args_ids, dst))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(_m)
}
constructor_call_ival_ret(box_type, arg_vals, box_type_literal) {
local insts = new ArrayBox()
local m = 0
if arg_vals != null { m = me._array_len(arg_vals) }
local i = 0
loop(i < m) {
call("ArrayBox.push/2", insts, MirSchemaBox.inst_const(i, call("ArrayBox.get/2", arg_vals, i)))
i = i + 1
}
local args_ids = new ArrayBox()
i = 0
loop(i < m) {
call("ArrayBox.push/2", args_ids, i)
i = i + 1
}
local dst = m
local actual_box_type = box_type
if box_type_literal != null {
local repr = "" + box_type
if repr.indexOf("ArrayBox(") == 0 {
actual_box_type = box_type_literal
}
}
call("ArrayBox.push/2", insts, MirSchemaBox.inst_mir_call_constructor(actual_box_type, args_ids, dst))
call("ArrayBox.push/2", insts, MirSchemaBox.inst_ret(dst))
local blocks = new ArrayBox()
call("ArrayBox.push/2", blocks, MirSchemaBox.block(0, insts))
local _m = MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
return me._ensure_terminators(_m)
}
// ctor(ArrayBox) → method(size) → ret
ctor_then_size_ret() {
local b0 = [
MirSchemaBox.inst_mir_call_constructor("ArrayBox", new ArrayBox(), 1),
MirSchemaBox.inst_mir_call_method("size", 1, new ArrayBox(), 2),
MirSchemaBox.inst_ret(2)
]
local _m = MirSchemaBox.module(MirSchemaBox.fn_main([ MirSchemaBox.block(0, b0) ]))
return me._ensure_terminators(_m)
}
}

View File

@ -0,0 +1,290 @@
// selfhost/shared/mir/json_emit_box.hako
// JsonEmitBox — Gate C JSON emitter (schema_version 1.0, numbers unwrapped)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
static box JsonEmitBox {
_expect_map(val, context) {
if BoxHelpers.is_map(val) == 1 { return val }
print("[JsonEmitBox] dev assert failed: expected MapBox for " + context)
call("MapBox.get/2", val, "__json_emit_expect_map")
return val
}
_expect_array(val, context) {
if BoxHelpers.is_array(val) == 1 { return val }
print("[JsonEmitBox] dev assert failed: expected ArrayBox for " + context)
call("ArrayBox.get/2", val, 0)
return val
}
_expect_i64(val, context) {
if val == null {
print("[JsonEmitBox] dev assert failed: expected i64 (non-null) for " + context)
call("MapBox.get/2", val, "__json_emit_expect_i64_null")
return 0
}
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local inner = call("MapBox.get/2", val, "value")
if inner != null { return inner }
print("[JsonEmitBox] dev assert failed: missing value in " + context)
call("MapBox.get/2", val, "__json_emit_expect_i64_value")
return 0
}
# Assume numeric immediate; avoid module function coercion for safety
return val
}
// ---- helpers ------------------------------------------------------------
_int_str(val) {
return StringHelpers.int_to_str(me._expect_i64(val, "integer field"))
}
_emit_vid_or_null(val) {
if val == null { return "null" }
return me._int_str(val)
}
_emit_vid_array(arr) {
if arr == null { return "[]" }
local n = BoxHelpers.array_len(arr)
return "[" + me._emit_vid_array_rec(arr, 0, n) + "]"
}
_emit_vid_array_rec(arr, idx, len) {
if idx >= len { return "" }
local head = me._emit_vid_or_null(call("ArrayBox.get/2", arr, idx))
local tail = me._emit_vid_array_rec(arr, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
_emit_effects(effects) {
if effects == null { return "[]" }
local n = BoxHelpers.array_len(effects)
return "[" + me._emit_effects_rec(effects, 0, n) + "]"
}
_emit_effects_rec(effects, idx, len) {
if idx >= len { return "" }
local head = me._quote(call("ArrayBox.get/2", effects, idx))
local tail = me._emit_effects_rec(effects, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
_emit_callee(callee) {
if callee == null { return "{\"type\":\"Extern\",\"name\":\"\"}" }
callee = me._expect_map(callee, "mir_call.callee")
local ty_box = call("MapBox.get/2", callee, "type")
local ty = ""
if ty_box != null { ty = "" + ty_box }
if ty == "Extern" {
local name = call("MapBox.get/2", callee, "name")
if name == null { name = "" }
return "{\"type\":\"Extern\",\"name\":" + me._quote(name) + "}"
}
if ty == "ModuleFunction" {
local name = call("MapBox.get/2", callee, "name")
if name == null { name = "" }
return "{\"type\":\"ModuleFunction\",\"name\":" + me._quote(name) + "}"
}
if ty == "Method" {
local box_name = call("MapBox.get/2", callee, "box_name")
if box_name == null { box_name = "" }
local method = call("MapBox.get/2", callee, "method")
if method == null { method = "" }
local receiver = call("MapBox.get/2", callee, "receiver")
return "{\"type\":\"Method\",\"box_name\":" + me._quote(box_name) +
",\"method\":" + me._quote(method) + ",\"receiver\":" + me._emit_vid_or_null(receiver) + "}"
}
if ty == "Constructor" {
local box_type = call("MapBox.get/2", callee, "box_type")
if box_type == null { box_type = "" }
return "{\"type\":\"Constructor\",\"box_type\":" + me._quote(box_type) + "}"
}
return "{\"type\":" + me._quote(ty) + "}"
}
_emit_box_value(val) {
if val == null { return "{\"type\":\"i64\",\"value\":0}" }
local ty = "i64"
local inner = val
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local ty_box = call("MapBox.get/2", val, "type")
if ty_box != null { ty = "" + ty_box }
local inner_box = call("MapBox.get/2", val, "value")
if inner_box != null { inner = inner_box }
}
return "{\"type\":" + me._quote(ty) + ",\"value\":" + me._int_str(inner) + "}"
}
_quote(s) {
if s == null { return "\"\"" }
return StringHelpers.json_quote("" + s)
}
_emit_phi_values(values) {
if values == null { return "[]" }
local n = BoxHelpers.array_len(values)
return "[" + me._emit_phi_rec(values, 0, n) + "]"
}
_emit_phi_rec(values, idx, len) {
if idx >= len { return "" }
local item = call("ArrayBox.get/2", values, idx)
local block_id = null
local value_id = null
if item != null {
block_id = call("MapBox.get/2", item, "block")
value_id = call("MapBox.get/2", item, "value")
}
local head = "{\"block\":" + me._int_str(block_id) + ",\"value\":" + me._int_str(value_id) + "}"
local tail = me._emit_phi_rec(values, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
_emit_inst(inst) {
if inst == null { return "{}" }
inst = me._expect_map(inst, "instruction")
local op = call("MapBox.get/2", inst, "op")
if op == null { op = "" }
if op == "const" {
return "{\"op\":\"const\",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) +
",\"value\":" + me._emit_box_value(call("MapBox.get/2", inst, "value")) + "}"
}
if op == "ret" || op == "return" {
return "{\"op\":\"ret\",\"value\":" + me._int_str(call("MapBox.get/2", inst, "value")) + "}"
}
if op == "binop" {
local kind = call("MapBox.get/2", inst, "op_kind")
if kind == null { kind = "" }
return "{\"op\":\"binop\",\"op_kind\":" + me._quote(kind) + ",\"lhs\":" +
me._int_str(call("MapBox.get/2", inst, "lhs")) + ",\"rhs\":" + me._int_str(call("MapBox.get/2", inst, "rhs")) +
",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) + "}"
}
if op == "compare" {
local cmp = call("MapBox.get/2", inst, "cmp")
if cmp == null { cmp = "" }
return "{\"op\":\"compare\",\"cmp\":" + me._quote(cmp) + ",\"lhs\":" +
me._int_str(call("MapBox.get/2", inst, "lhs")) + ",\"rhs\":" + me._int_str(call("MapBox.get/2", inst, "rhs")) +
",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) + "}"
}
if op == "branch" {
return "{\"op\":\"branch\",\"cond\":" + me._int_str(call("MapBox.get/2", inst, "cond")) +
",\"then\":" + me._int_str(call("MapBox.get/2", inst, "then")) +
",\"else\":" + me._int_str(call("MapBox.get/2", inst, "else")) + "}"
}
if op == "jump" {
return "{\"op\":\"jump\",\"target\":" + me._int_str(call("MapBox.get/2", inst, "target")) + "}"
}
if op == "phi" {
return "{\"op\":\"phi\",\"dst\":" + me._int_str(call("MapBox.get/2", inst, "dst")) +
",\"values\":" + me._emit_phi_values(call("MapBox.get/2", inst, "values")) + "}"
}
if op == "mir_call" {
// SSOT: payload("mir_call") のみを受理。トップレベルの互換フィールドは読まない。
local payload = call("MapBox.get/2", inst, "mir_call")
if payload == null { return "{\"op\":\"mir_call\"}" }
payload = me._expect_map(payload, "mir_call payload")
local dst_json = me._emit_vid_or_null(call("MapBox.get/2", inst, "dst"))
local callee_json = me._emit_callee(call("MapBox.get/2", payload, "callee"))
local args_box = call("MapBox.get/2", payload, "args")
me._expect_array(args_box, "mir_call.args")
local effects_box = call("MapBox.get/2", payload, "effects")
me._expect_array(effects_box, "mir_call.effects")
local args_json = me._emit_vid_array(args_box)
local effects_json = me._emit_effects(effects_box)
return "{\"op\":\"mir_call\",\"dst\":" + dst_json +
",\"mir_call\":{\"callee\":" + callee_json +
",\"args\":" + args_json +
",\"effects\":" + effects_json + "}}"
}
// Fallback: emit op only (unexpected instruction kinds are outside P1 scope)
return "{\"op\":" + me._quote(op) + "}"
}
_emit_block(block) {
if block == null { return "{\"id\":0,\"instructions\":[]}" }
local insts = call("MapBox.get/2", block, "instructions")
if insts == null {
return "{\"id\":" + me._int_str(call("MapBox.get/2", block, "id")) + ",\"instructions\":[]}"
}
local n = BoxHelpers.array_len(insts)
local body = me._emit_block_rec(insts, 0, n)
return "{\"id\":" + me._int_str(call("MapBox.get/2", block, "id")) + ",\"instructions\":[" + body + "]}"
}
_emit_block_rec(insts, idx, len) {
if idx >= len { return "" }
local head = me._emit_inst(call("ArrayBox.get/2", insts, idx))
local tail = me._emit_block_rec(insts, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
_emit_function(func) {
if func == null {
return "{\"name\":\"main\",\"blocks\":[]}"
}
local name = call("MapBox.get/2", func, "name")
if name == null { name = "main" }
// Optional fields: params (array) / flags (map)
local params = call("MapBox.get/2", func, "params")
local flags = call("MapBox.get/2", func, "flags")
local blocks = call("MapBox.get/2", func, "blocks")
if blocks == null {
if params != null || flags != null {
local head = "{\"name\":" + me._quote(name)
if params != null { head = head + ",\"params\":" + JSON.stringify(params) }
if flags != null { head = head + ",\"flags\":" + JSON.stringify(flags) }
return head + ",\"blocks\":[]}"
}
return "{\"name\":" + me._quote(name) + ",\"blocks\":[]}"
}
local n = BoxHelpers.array_len(blocks)
local body = me._emit_function_rec(blocks, 0, n)
local head2 = "{\"name\":" + me._quote(name)
if params != null { head2 = head2 + ",\"params\":" + JSON.stringify(params) }
if flags != null { head2 = head2 + ",\"flags\":" + JSON.stringify(flags) }
return head2 + ",\"blocks\":[" + body + "]}"
}
_emit_function_rec(blocks, idx, len) {
if idx >= len { return "" }
local head = me._emit_block(call("ArrayBox.get/2", blocks, idx))
local tail = me._emit_function_rec(blocks, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
to_json(module) {
if module == null { return "" }
// Prefer single-function fallbackを強化: has() 未実装環境でも repr チェックで検出
local f0 = call("MapBox.get/2", module, "functions_0")
if f0 != null {
local repr = "" + f0
if repr.indexOf("MapBox(") == 0 || repr.indexOf("HostHandleBox(") == 0 {
local one = me._emit_function(f0)
return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[" + one + "]}"
}
}
// Legacy path: try functions array if available
local funcs = call("MapBox.get/2", module, "functions")
if funcs == null { return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[]}" }
local n = BoxHelpers.array_len(funcs)
local body = me._emit_module_rec(funcs, 0, n)
return "{\"kind\":\"MIR\",\"schema_version\":\"1.0\",\"functions\":[" + body + "]}"
}
_emit_module_rec(funcs, idx, len) {
// Defensive fallback: some environments report size=0 for host-managed arrays.
// If len==0 at entry, try to probe index 0 once to recover a single element.
if idx == 0 && len == 0 {
local first = call("ArrayBox.get/2", funcs, 0)
if first != null { len = 1 }
}
if idx >= len { return "" }
local head = me._emit_function(call("ArrayBox.get/2", funcs, idx))
local tail = me._emit_module_rec(funcs, idx + 1, len)
if tail == "" { return head }
return head + "," + tail
}
}

View File

@ -0,0 +1,104 @@
// selfhost/shared/mir/loop_form_box.hako
// LoopFormBox — minimal loop structure builder (P2: continue/break snapshots + Exit PHI)
using "lang/src/shared/mir/mir_schema_box.hako" as MirSchemaBox
static box LoopFormBox {
// while (i < limit) {
// if (i == break_value) break;
// if (i == skip_value) continue;
// sum = sum + i;
// i = i + 1;
// }
// Builds LoopForm structure with Header/Latch PHI + Exit PHI (continue/break aware)
loop_counter(limit, skip_value, break_value) {
if skip_value == null { skip_value = 2 }
if break_value == null { break_value = limit }
// Preheader (block 0): initialise loop-carried values and constants, then jump header
local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, 0)) // r1 = 0 (initial i)
pre.push(MirSchemaBox.inst_const(2, limit)) // r2 = limit
pre.push(MirSchemaBox.inst_const(3, 1)) // r3 = step
pre.push(MirSchemaBox.inst_const(4, 0)) // r4 = sum
pre.push(MirSchemaBox.inst_const(5, skip_value)) // r5 = continue trigger
pre.push(MirSchemaBox.inst_const(6, break_value))// r6 = break trigger
pre.push(MirSchemaBox.inst_jump(1)) // goto header
// Header (block 1): PHI(i), PHI(sum), compare, branch
local header = new ArrayBox()
local phi_i_inputs = new ArrayBox()
phi_i_inputs.push(MirSchemaBox.phi_incoming(0, 1)) // from preheader
phi_i_inputs.push(MirSchemaBox.phi_incoming(7, 18)) // from latch
header.push(MirSchemaBox.inst_phi(10, phi_i_inputs)) // r10 = current i
local phi_sum_inputs = new ArrayBox()
phi_sum_inputs.push(MirSchemaBox.phi_incoming(0, 4)) // from preheader
phi_sum_inputs.push(MirSchemaBox.phi_incoming(7, 19)) // from latch
header.push(MirSchemaBox.inst_phi(11, phi_sum_inputs))// r11 = current sum
header.push(MirSchemaBox.inst_compare("Lt", 10, 2, 12)) // r12 = (i < limit)
header.push(MirSchemaBox.inst_branch(12, 2, 8)) // body or exit
// Body entry (block 2): if (i == break) -> break else check continue
local body_check_break = new ArrayBox()
body_check_break.push(MirSchemaBox.inst_compare("Eq", 10, 6, 13)) // r13 = (i == break)
body_check_break.push(MirSchemaBox.inst_branch(13, 3, 4))
// Break path (block 3): jump directly to exit; exit PHI snapshots current sum
local break_block = new ArrayBox()
break_block.push(MirSchemaBox.inst_jump(8))
// Continue check (block 4): if (i == skip) -> continue else body work
local continue_check = new ArrayBox()
continue_check.push(MirSchemaBox.inst_compare("Eq", 10, 5, 14)) // r14 = (i == skip)
continue_check.push(MirSchemaBox.inst_branch(14, 5, 6))
// Continue path (block 5): snapshot sum, increment i, jump latch
local continue_block = new ArrayBox()
continue_block.push(MirSchemaBox.inst_binop("Add", 10, 3, 15)) // r15 = i + step
continue_block.push(MirSchemaBox.inst_jump(7))
// Body work (block 6): sum += i; i += step; jump latch
local body_block = new ArrayBox()
body_block.push(MirSchemaBox.inst_binop("Add", 11, 10, 16)) // r16 = sum + i
body_block.push(MirSchemaBox.inst_binop("Add", 10, 3, 17)) // r17 = i + step
body_block.push(MirSchemaBox.inst_jump(7))
// Latch (block 7): PHI merge for continue/normal, then jump header
local latch_block = new ArrayBox()
local latch_phi_i = new ArrayBox()
latch_phi_i.push(MirSchemaBox.phi_incoming(5, 15)) // continue path
latch_phi_i.push(MirSchemaBox.phi_incoming(6, 17)) // body path
latch_block.push(MirSchemaBox.inst_phi(18, latch_phi_i)) // feeds header.i
local latch_phi_sum = new ArrayBox()
latch_phi_sum.push(MirSchemaBox.phi_incoming(5, 11)) // continue keeps sum
latch_phi_sum.push(MirSchemaBox.phi_incoming(6, 16)) // body updated sum
latch_block.push(MirSchemaBox.inst_phi(19, latch_phi_sum)) // feeds header.sum
latch_block.push(MirSchemaBox.inst_jump(1))
// Exit (block 8): Merge sums from header fallthrough and break edge, then return
local exit_vals = new ArrayBox()
exit_vals.push(MirSchemaBox.phi_incoming(1, 11)) // normal completion
exit_vals.push(MirSchemaBox.phi_incoming(3, 11)) // break path
local exit_block = new ArrayBox()
exit_block.push(MirSchemaBox.inst_phi(20, exit_vals))
exit_block.push(MirSchemaBox.inst_ret(20))
// Assemble blocks
local blocks = new ArrayBox()
blocks.push(MirSchemaBox.block(0, pre))
blocks.push(MirSchemaBox.block(1, header))
blocks.push(MirSchemaBox.block(2, body_check_break))
blocks.push(MirSchemaBox.block(3, break_block))
blocks.push(MirSchemaBox.block(4, continue_check))
blocks.push(MirSchemaBox.block(5, continue_block))
blocks.push(MirSchemaBox.block(6, body_block))
blocks.push(MirSchemaBox.block(7, latch_block))
blocks.push(MirSchemaBox.block(8, exit_block))
return MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
}
}

View File

@ -0,0 +1,216 @@
// mir_io_box.hako — MirIoBox (Phase A, Hako-side implementation)
// Responsibility: provide minimal read/validate/cursor API using existing locator boxes.
// Provider selection:
// - Default: scan (string scanning, no plugin required)
// - Optional: yyjson (JsonDoc/JsonNode via HostBridge extern) when JSON plugin is available
// FailFast:
// - validate() checks kind/schema/functions
// - validate_function(): terminator required + jump/branch target existence (scan path ensured; provider path WIP)
using "lang/src/vm/boxes/result_box.hako" as Result
using "lang/src/vm/boxes/result_helpers.hako" as ResultHelpers
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
using "lang/src/shared/json/json_canonical_box.hako" as JsonCanonicalBox
using "lang/src/vm/hakorune-vm/function_locator.hako" as FunctionLocatorBox
using "lang/src/vm/hakorune-vm/blocks_locator.hako" as BlocksLocatorBox
using "lang/src/vm/hakorune-vm/instrs_locator.hako" as InstrsLocatorBox
using "lang/src/vm/hakorune-vm/backward_object_scanner.hako" as BackwardObjectScannerBox
using "lang/src/vm/hakorune-vm/block_iterator.hako" as BlockIteratorBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
using "lang/src/shared/common/box_helpers.hako" as BoxHelpers
static box MirIoBox {
// Internal: provider gate (yyjson)
_provider_gate_on() {
// Accept HAKO_JSON_PROVIDER or NYASH_JSON_PROVIDER (alias). Value: 'yyjson'
// Use extern env.local.get to avoid direct field access to `env`.
local v = call("env.local.get/1", "HAKO_JSON_PROVIDER")
if v == null || v == "" { v = call("env.local.get/1", "NYASH_JSON_PROVIDER") }
if v == null || v == "" { return 0 }
if v == "yyjson" || v == "YYJSON" { return 1 }
return 0
}
// Provider policy
// - Default path uses string scanning (no plugin dependency)
// - When JSON provider (yyjson) is available via HostBridge, provider path becomes eligible
// - This box must remain Fail-Fast: invalid kind/schema/functions should be rejected deterministically
// - Provider enablement is transparent; do not add silent fallbacks
// Return canonicalized full MIR JSON (keys sorted, arrays preserve order)
normalize(json) {
return JsonCanonicalBox.canonicalize(json)
}
// Provider selection: prefer JSON provider when available; otherwise use scan locators
// 判定は _json_root() の成否で行うENV不要
// Helper: parse JSON and return root JsonNodeBox (provider=yyjson)
_json_root(json) {
// Minimal provider (default OFF). When ON, attempt JsonDocBox.parse+root via HostBridge.
if me._provider_gate_on() == 0 { return null }
if json == null { return Result.Err("json provider: input is null") }
// Construct JsonDocBox without ArrayBox dependency
local doc = hostbridge.box_new0("JsonDocBox")
if doc == null { return Result.Err("json provider: box_new failed") }
// parse(json) — 1-arg convenience wrapper
hostbridge.box_call1(doc, "parse", json)
// check error()
local err = hostbridge.box_call0(doc, "error")
if err != null && err != "" { return Result.Err("json provider: parse failed: " + err) }
// root() — existence確認のみ成功時は Ok(0) を返す)
local node = hostbridge.box_call0(doc, "root")
if node == null { return Result.Err("json provider: root failed") }
return Result.Ok(0)
}
validate(json) {
if json == null { return Result.Err("null json") }
// Optional: canonicalize full MIR JSON at ingress (identity/no-op for now)
json = JsonCanonicalBox.canonicalize(json)
// Accept full root (preferred)
if json.indexOf("\"kind\":\"MIR\"") >= 0 {
if json.indexOf("\"schema_version\":\"1.0\"") < 0 { return Result.Err("invalid schema_version") }
if json.indexOf("\"functions\"") < 0 { return Result.Err("functions missing") }
return Result.Ok(0)
}
// Tolerate narrowed JSON (single function object) used by nyvm bridge
if json.indexOf("\"instructions\"") >= 0 && json.indexOf("\"id\"") >= 0 {
return Result.Ok(0)
}
return Result.Err("invalid kind")
}
// Return function JSON meta (content)
functions(json) {
// Canonicalize full root when present (identity until host bridge is wired)
json = JsonCanonicalBox.canonicalize(json)
// Provider disabled: always use scan locator
local loc = FunctionLocatorBox.locate(json)
if loc.is_Err() { return loc }
return loc
}
// Return blocks[] meta (content without brackets)
blocks(func_json) {
// Provider disabled: always use scan locator
local loc = BlocksLocatorBox.locate(func_json)
if loc.is_Err() { return loc }
return loc
}
// Return instructions content and single flag
instructions(block_json) {
// Provider disabled: always use scan locator
local loc = InstrsLocatorBox.locate(block_json)
if loc.is_Err() { return loc }
return loc
}
// Return last terminator object (as string)
terminator(block_json) {
// Provider disabled: use scan locator then parse last object
local loc = InstrsLocatorBox.locate(block_json)
if loc.is_Err() { return loc }
local meta = loc.as_Ok()
local insts = BoxHelpers.map_get(meta, "content")
if insts == "" { return Result.Err("terminator not found") }
local single = BoxHelpers.map_get(meta, "single")
if single != null && single == 1 { return Result.Ok(insts) }
// use small window for large JSON (8192 bytes)
local last = BackwardObjectScannerBox.scan_last_object_opt_window(insts, 200000, 8192)
if last.is_Err() { return Result.Err("terminator scan failed: " + last.as_Err()) }
return Result.Ok(last.as_Ok())
}
// Strict validate for function JSON (narrow): terminator required and references valid
validate_function(func_json) {
// collect block ids
local bl = me.blocks(func_json)
if bl.is_Err() { return bl }
local meta = bl.as_Ok()
local content = BoxHelpers.map_get(meta, "content")
local ids = new MapBox()
// iterate blocks
local pos = 0
loop(true) {
local it = BlockIteratorBox.next(content, pos)
if it.is_Err() { break }
local m = it.as_Ok()
local obj = BoxHelpers.map_get(m, "obj")
pos = BoxHelpers.map_get(m, "next_pos")
// parse id
local key_id = "\"id\":"
local p = obj.indexOf(key_id)
if p < 0 { return Result.Err("block id missing") }
p = p + key_id.size()
// skip ws
loop(p < obj.size()) { local ch = obj.substring(p,p+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p = p + 1 continue } break }
local digs = StringHelpers.read_digits(obj, p)
if digs == "" { return Result.Err("invalid block id") }
ids.set(StringHelpers.int_to_str(StringHelpers.to_i64(digs)), 1)
// require terminator
if obj.indexOf("\"terminator\":{") < 0 { return Result.Err("terminator missing") }
}
// validate each block terminator references
pos = 0
loop(true) {
local it = BlockIteratorBox.next(content, pos)
if it.is_Err() { break }
local m = it.as_Ok()
pos = BoxHelpers.map_get(m, "next_pos")
local obj = BoxHelpers.map_get(m, "obj")
// extract terminator object
local ts = obj.indexOf("\"terminator\":{")
if ts < 0 { return Result.Err("terminator missing") }
local te = ts + "\"terminator\":".size()
// naive: use general locator to get terminator via instructions fallback if needed
local term = me.terminator(obj)
if term.is_Err() { return Result.Err("terminator parse failed") }
local tj = term.as_Ok()
// op tolerant
local k = "\"op\""
local p2 = tj.indexOf(k)
if p2 < 0 { return Result.Err("terminator op not found") }
p2 = p2 + k.size()
loop(p2 < tj.size()) { local ch2 = tj.substring(p2,p2+1) if ch2 == ":" { p2 = p2 + 1 break } if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { p2 = p2 + 1 continue } return Result.Err("terminator op colon not found") }
loop(p2 < tj.size()) { local ch3 = tj.substring(p2,p2+1) if ch3 == " " || ch3 == "\n" || ch3 == "\r" || ch3 == "\t" { p2 = p2 + 1 continue } break }
if tj.substring(p2,p2+1) != "\"" { return Result.Err("terminator op quote not found") }
local e2 = JsonCursorBox.index_of_from(tj, "\"", p2+1)
if e2 < 0 { return Result.Err("terminator op end not found") }
local op = tj.substring(p2+1, e2)
if op == "jump" {
local kt = "\"target\":"
local pt = tj.indexOf(kt)
if pt < 0 { return Result.Err("jump: target missing") }
pt = pt + kt.size()
loop(pt < tj.size()) {
local ch = tj.substring(pt,pt+1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { pt = pt + 1 continue }
break
}
local digs2 = StringHelpers.read_digits(tj, pt)
if digs2 == "" { return Result.Err("jump: invalid target") }
local key = StringHelpers.int_to_str(StringHelpers.to_i64(digs2))
if call("MapBox.get/2", ids, key) == null { return Result.Err("jump: unknown target") }
} else { if op == "branch" {
local k1 = "\"then_bb\":"
local p3 = tj.indexOf(k1)
if p3 < 0 { return Result.Err("branch: then_bb missing") }
p3 = p3 + k1.size()
loop(p3 < tj.size()) { local ch = tj.substring(p3,p3+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p3 = p3 + 1 continue } break }
local d1 = StringHelpers.read_digits(tj, p3)
if d1 == "" { return Result.Err("branch: invalid then_bb") }
local k2 = "\"else_bb\":"
local p4 = tj.indexOf(k2)
if p4 < 0 { return Result.Err("branch: else_bb missing") }
p4 = p4 + k2.size()
loop(p4 < tj.size()) { local ch = tj.substring(p4,p4+1) if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { p4 = p4 + 1 continue } break }
local d2 = StringHelpers.read_digits(tj, p4)
if d2 == "" { return Result.Err("branch: invalid else_bb") }
if call("MapBox.get/2", ids, StringHelpers.int_to_str(StringHelpers.to_i64(d1))) == null { return Result.Err("branch: unknown then_bb") }
if call("MapBox.get/2", ids, StringHelpers.int_to_str(StringHelpers.to_i64(d2))) == null { return Result.Err("branch: unknown else_bb") }
} else { if op == "ret" { /* ok */ } else { return Result.Err("terminator: unsupported op "" + op + """) } }
}
}
return Result.Ok(0)
}
}

View File

@ -0,0 +1,243 @@
// selfhost/shared/mir/mir_schema_box.hako
// MirSchemaBox — minimal MIR(JSON v0) constructors (P1 scope)
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box MirSchemaBox {
_expect_map(val, context) {
if val == null {
print("[MirSchemaBox] dev assert failed: expected MapBox (non-null) for " + context)
call("MapBox.get/2", val, "__mir_schema_expect_map_null")
return val
}
local repr = "" + val
if repr.indexOf("MapBox(") == 0 { return val }
print("[MirSchemaBox] dev assert failed: expected MapBox for " + context)
call("MapBox.get/2", val, "__mir_schema_expect_map")
return val
}
_expect_array(val, context) {
if val == null {
print("[MirSchemaBox] dev assert failed: expected ArrayBox (non-null) for " + context)
call("ArrayBox.get/2", val, 0)
return val
}
local repr = "" + val
if repr.indexOf("ArrayBox(") == 0 { return val }
print("[MirSchemaBox] dev assert failed: expected ArrayBox for " + context)
call("ArrayBox.get/2", val, 0)
return val
}
_expect_i64(val, context) {
if val == null {
print("[MirSchemaBox] dev assert failed: expected i64 (non-null) for " + context)
call("MapBox.get/2", val, "__mir_schema_expect_i64_null")
return 0
}
local repr = "" + val
if repr.indexOf("MapBox(") == 0 {
local ty = call("MapBox.get/2", val, "type")
if ty != null {
local ty_str = "" + ty
if ty_str != "i64" && ty_str != "int" && ty_str != "integer" {
print("[MirSchemaBox] dev assert failed: unexpected type " + ty_str + " in " + context)
call("MapBox.get/2", val, "__mir_schema_expect_i64_type")
return 0
}
}
local inner = call("MapBox.get/2", val, "value")
if inner != null { return StringHelpers.to_i64(inner) }
print("[MirSchemaBox] dev assert failed: missing value in " + context)
call("MapBox.get/2", val, "__mir_schema_expect_i64_value")
return 0
}
if StringHelpers.is_numeric_str("" + val) == 1 { return StringHelpers.to_i64(val) }
print("[MirSchemaBox] dev assert failed: expected numeric value for " + context)
call("MapBox.get/2", val, "__mir_schema_expect_i64_direct")
return 0
}
_len(arr) {
if arr == null { return 0 }
// ArrayBox.size/1 は MapBox-wrapped integer を返すので、unwrap する
local size_val = call("ArrayBox.size/1", arr)
local repr = "" + size_val
if repr.indexOf("MapBox(") == 0 {
local inner = call("MapBox.get/2", size_val, "value")
if inner != null { return inner }
}
return size_val
}
// Scalars (normalize to i64 for P1)
i(x) {
// Return MapBox { type: "i64", value: x }
local m = new MapBox()
m.set("type", "i64")
m.set("value", x)
return m
}
// Instructions
inst_const(dst, val) {
local m = new MapBox()
m.set("op", "const")
m.set("dst", this.i(dst))
m.set("value", this.i(val))
return m
}
inst_ret(val) {
local m = new MapBox()
m.set("op", "ret")
m.set("value", this.i(val))
return m
}
inst_compare(cmp, lhs, rhs, dst) {
local m = new MapBox()
m.set("op", "compare")
m.set("cmp", cmp)
m.set("lhs", this.i(lhs))
m.set("rhs", this.i(rhs))
m.set("dst", this.i(dst))
return m
}
inst_binop(kind, lhs, rhs, dst) {
local m = new MapBox()
m.set("op", "binop")
m.set("op_kind", kind)
m.set("lhs", this.i(lhs))
m.set("rhs", this.i(rhs))
m.set("dst", this.i(dst))
return m
}
inst_branch(cond, then_id, else_id) {
local m = new MapBox()
m.set("op", "branch")
m.set("cond", this.i(cond))
m.set("then", this.i(then_id))
m.set("else", this.i(else_id))
return m
}
inst_jump(target) {
local m = new MapBox()
m.set("op", "jump")
m.set("target", this.i(target))
return m
}
// Phi instruction (v0 schema)
phi_incoming(block_id, value_id) {
local m = new MapBox()
m.set("block", this.i(block_id))
m.set("value", this.i(value_id))
return m
}
inst_phi(dst, incoming) {
local m = new MapBox()
m.set("op", "phi")
m.set("dst", this.i(dst))
local arr = new ArrayBox()
if incoming != null {
local n = me._len(incoming)
local i = 0
loop(i < n) {
arr.push(call("ArrayBox.get/2", incoming, i))
i = i + 1
}
}
m.set("values", arr)
return m
}
// Unified call (mir_call) variants — minimal P3
_wrap_ids(arr) {
local out = new ArrayBox()
if arr == null { return out }
me._expect_array(arr, "mir_call args source")
local i = 0
local n = me._len(arr)
loop(i < n) {
out.push(this.i(call("ArrayBox.get/2", arr, i)))
i = i + 1
}
return out
}
inst_mir_call_extern(name, arg_ids, dst) {
local callee = new MapBox(); callee.set("type", "Extern"); callee.set("name", name)
local payload = new MapBox()
payload.set("callee", callee)
payload.set("args", this._wrap_ids(arg_ids))
payload.set("effects", new ArrayBox())
local m = new MapBox()
m.set("op", "mir_call")
m.set("dst", this.i(dst))
me._expect_map(payload, "mir_call payload (extern)")
m.set("mir_call", payload)
return m
}
inst_mir_call_global(name, arg_ids, dst) {
local callee = new MapBox(); callee.set("type", "Global"); callee.set("name", name)
local payload = new MapBox()
payload.set("callee", callee)
payload.set("args", this._wrap_ids(arg_ids))
payload.set("effects", new ArrayBox())
local m = new MapBox()
m.set("op", "mir_call")
m.set("dst", this.i(dst))
me._expect_map(payload, "mir_call payload (global)")
m.set("mir_call", payload)
return m
}
inst_mir_call_method(method, recv_id, arg_ids, dst) {
local callee = new MapBox()
callee.set("type", "Method")
callee.set("method", method)
callee.set("receiver", this.i(recv_id))
local m = new MapBox()
m.set("op", "mir_call")
m.set("dst", this.i(dst))
local payload = new MapBox()
payload.set("callee", callee)
payload.set("args", this._wrap_ids(arg_ids))
payload.set("effects", new ArrayBox())
payload = me._expect_map(payload, "mir_call payload (method)")
m.set("mir_call", payload)
return m
}
inst_mir_call_constructor(box_type, arg_ids, dst) {
local callee = new MapBox(); callee.set("type", "Constructor"); callee.set("box_type", box_type)
local m = new MapBox()
m.set("op", "mir_call")
m.set("dst", this.i(dst))
local payload = new MapBox()
payload.set("callee", callee)
payload.set("args", this._wrap_ids(arg_ids))
payload.set("effects", new ArrayBox())
me._expect_map(payload, "mir_call payload (constructor)")
m.set("mir_call", payload)
return m
}
// Block/Module
block(id, insts) {
local m = new MapBox()
m.set("id", this.i(id))
m.set("instructions", insts)
return m
}
fn_main(blocks) {
local m = new MapBox()
m.set("name", "main")
m.set("blocks", blocks)
return m
}
module(fn_main) {
local m = new MapBox()
m.set("version", this.i(0))
m.set("kind", "MIR")
// Avoid push() to sidestep host-slot gating; build as literal for determinism
local funcs = [ fn_main ]
m.set("functions", funcs)
// Fallback access path for environments where Array.size/1 is unreliable
m.set("functions_0", fn_main)
return m
}
}