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:
121
lang/src/shared/common/box_helpers.hako
Normal file
121
lang/src/shared/common/box_helpers.hako
Normal 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 } }
|
||||
276
lang/src/shared/common/mini_vm_binop.hako
Normal file
276
lang/src/shared/common/mini_vm_binop.hako
Normal 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))
|
||||
}
|
||||
}
|
||||
49
lang/src/shared/common/mini_vm_compare.hako
Normal file
49
lang/src/shared/common/mini_vm_compare.hako
Normal 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
|
||||
}
|
||||
}
|
||||
79
lang/src/shared/common/mini_vm_scan.hako
Normal file
79
lang/src/shared/common/mini_vm_scan.hako
Normal 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 ""
|
||||
}
|
||||
}
|
||||
39
lang/src/shared/common/modules_inspect_box.hako
Normal file
39
lang/src/shared/common/modules_inspect_box.hako
Normal file
@ -0,0 +1,39 @@
|
||||
// modules_inspect_box.hako — ModulesInspectBox
|
||||
// Responsibility: Helpers for Dir-as-NS module inspection from Nyash code.
|
||||
// Non‑IO, 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 } }
|
||||
|
||||
174
lang/src/shared/common/string_helpers.hako
Normal file
174
lang/src/shared/common/string_helpers.hako
Normal 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
|
||||
}
|
||||
}
|
||||
23
lang/src/shared/common/string_ops.hako
Normal file
23
lang/src/shared/common/string_ops.hako
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user