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:
16
lang/src/vm/boxes/README.md
Normal file
16
lang/src/vm/boxes/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# VM Boxes — Shared Helpers (Guard)
|
||||
|
||||
Responsibility
|
||||
- Pure helpers used by engines (op handlers, scanners, compare, JSON frag/cursor).
|
||||
|
||||
Allowed
|
||||
- Import from `lang/src/shared/*` and other boxes/*.
|
||||
|
||||
Forbidden
|
||||
- Engine orchestration (no main run loop, no dispatch)
|
||||
- Direct I/O or plugin/ABI calls (engines should own those)
|
||||
|
||||
Notes
|
||||
- Mini‑VM's minimal executor currently lives here (`mir_vm_min.hako`). It may
|
||||
move under `engines/mini/` later; keep it box‑pure (no I/O) until then.
|
||||
|
||||
123
lang/src/vm/boxes/arithmetic.hako
Normal file
123
lang/src/vm/boxes/arithmetic.hako
Normal file
@ -0,0 +1,123 @@
|
||||
// arithmetic.hako — ArithmeticBox
|
||||
// Responsibility: safe decimal Add/Sub/Mul helpers and simple i64 adapters.
|
||||
// Non-responsibility: VM execution, JSON parsing, compare semantics.
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box ArithmeticBox {
|
||||
// Internal helpers operate on decimal strings to avoid overflow.
|
||||
_to_dec_str(x) {
|
||||
local s = "" + x
|
||||
local i = 0
|
||||
loop(i < s.size()) { local ch = s.substring(i,i+1) if ch=="+" || ch==" " { i=i+1 } else { break } }
|
||||
s = s.substring(i, s.size())
|
||||
i = 0
|
||||
loop(i < s.size() && s.substring(i,i+1)=="0") { i = i + 1 }
|
||||
if i >= s.size() { return "0" }
|
||||
return s.substring(i, s.size())
|
||||
}
|
||||
|
||||
_cmp_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
if sa.size() < sb.size() { return -1 }
|
||||
if sa.size() > sb.size() { return 1 }
|
||||
local i = 0
|
||||
loop(i < sa.size()) {
|
||||
local ca = sa.substring(i,i+1)
|
||||
local cb = sb.substring(i,i+1)
|
||||
if ca != cb { if ca < cb { return -1 } else { return 1 } }
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
_add_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
local i = sa.size() - 1
|
||||
local j = sb.size() - 1
|
||||
local carry = 0
|
||||
local out = new ArrayBox()
|
||||
loop(i >= 0 || j >= 0 || carry > 0) {
|
||||
local da = 0
|
||||
local db = 0
|
||||
if i >= 0 { da = ("0123456789").indexOf(sa.substring(i,i+1)) i = i - 1 }
|
||||
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j,j+1)) j = j - 1 }
|
||||
local s = da + db + carry
|
||||
carry = s / 10
|
||||
local d = s % 10
|
||||
out.push(("0123456789").substring(d, d+1))
|
||||
}
|
||||
local k = out.size()
|
||||
local res = ""
|
||||
loop(k > 0) { k = k - 1 res = res + (""+out.get(k)) }
|
||||
return res
|
||||
}
|
||||
|
||||
_sub_dec(a, b) {
|
||||
// Supports negative result: if a<b, return "-" + (b-a)
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
local c = me._cmp_dec(sa, sb)
|
||||
if c == 0 { return "0" }
|
||||
if c < 0 { return "-" + me._sub_dec(sb, sa) }
|
||||
local i = sa.size() - 1
|
||||
local j = sb.size() - 1
|
||||
local borrow = 0
|
||||
local out = new ArrayBox()
|
||||
loop(i >= 0) {
|
||||
local da = ("0123456789").indexOf(sa.substring(i, i+1)) - borrow
|
||||
local db = 0
|
||||
if j >= 0 { db = ("0123456789").indexOf(sb.substring(j, j+1)) j = j - 1 }
|
||||
if da < db { da = da + 10 borrow = 1 } else { borrow = 0 }
|
||||
local d = da - db
|
||||
out.push(("0123456789").substring(d, d+1))
|
||||
i = i - 1
|
||||
}
|
||||
local k = out.size() - 1
|
||||
loop(true) { if k > 0 && out.get(k) == "0" { k = k - 1 } else { break } }
|
||||
local res = ""
|
||||
loop(k >= 0) { res = res + (""+out.get(k)) k = k - 1 }
|
||||
return res
|
||||
}
|
||||
|
||||
_mul_dec(a, b) {
|
||||
local sa = me._to_dec_str(a)
|
||||
local sb = me._to_dec_str(b)
|
||||
if sa == "0" || sb == "0" { return "0" }
|
||||
local na = sa.size()
|
||||
local nb = sb.size()
|
||||
local res = new ArrayBox()
|
||||
local t = 0
|
||||
loop(t < na+nb) { res.push(0) t = t + 1 }
|
||||
local ia = na - 1
|
||||
loop(ia >= 0) {
|
||||
local da = ("0123456789").indexOf(sa.substring(ia, ia+1))
|
||||
local carry = 0
|
||||
local ib = nb - 1
|
||||
loop(ib >= 0) {
|
||||
local db = ("0123456789").indexOf(sb.substring(ib, ib+1))
|
||||
local idx = ia + ib + 1
|
||||
local sum = res.get(idx) + da * db + carry
|
||||
res.set(idx, sum % 10)
|
||||
carry = sum / 10
|
||||
ib = ib - 1
|
||||
}
|
||||
res.set(ia, res.get(ia) + carry)
|
||||
ia = ia - 1
|
||||
}
|
||||
local k = 0
|
||||
loop(k < res.size() && res.get(k) == 0) { k = k + 1 }
|
||||
local out = ""
|
||||
loop(k < res.size()) { out = out + ("0123456789").substring(res.get(k), res.get(k)+1) k = k + 1 }
|
||||
if out == "" { return "0" } else { return out }
|
||||
}
|
||||
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
|
||||
// Public adapters: operate on i64-likes and return i64-likes.
|
||||
add_i64(a, b) { return me._str_to_int(me._add_dec(a, b)) }
|
||||
sub_i64(a, b) { return me._str_to_int(me._sub_dec(a, b)) }
|
||||
mul_i64(a, b) { return me._str_to_int(me._mul_dec(a, b)) }
|
||||
}
|
||||
22
lang/src/vm/boxes/cfg_navigator.hako
Normal file
22
lang/src/vm/boxes/cfg_navigator.hako
Normal file
@ -0,0 +1,22 @@
|
||||
// cfg_navigator.hako — CfgNavigatorBox(ブロックの先頭/末尾シーク)
|
||||
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box CfgNavigatorBox {
|
||||
// Provide index_of_from to avoid tail-based fallback/ambiguous resolution
|
||||
index_of_from(text, needle, pos) { return StringOps.index_of_from(text, needle, pos) }
|
||||
|
||||
_int_to_str(n) {
|
||||
if n == 0 { return "0" }
|
||||
if n < 0 { return "-" + me._int_to_str(0 - n) }
|
||||
local v=n local out="" local digits="0123456789"
|
||||
loop(v>0){ local d=v%10 local ch=digits.substring(d,d+1) out=ch+out v=v/10 }
|
||||
return out
|
||||
}
|
||||
// Escape-aware array end finder via JsonCursorBox
|
||||
_seek_array_end(text, pos){ return JsonCursorBox.seek_array_end(text, pos) }
|
||||
|
||||
block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 }
|
||||
block_insts_end(mjson,insts_start){ return JsonCursorBox.seek_array_end(mjson,insts_start) }
|
||||
}
|
||||
28
lang/src/vm/boxes/compare_ops.hako
Normal file
28
lang/src/vm/boxes/compare_ops.hako
Normal file
@ -0,0 +1,28 @@
|
||||
// compare_ops.hako — CompareOpsBox
|
||||
// Responsibility: mapping of symbols to kinds and evaluating compares.
|
||||
// Non-responsibility: scanning/VM execution/arithmetic.
|
||||
|
||||
static box CompareOpsBox {
|
||||
map_symbol(sym) {
|
||||
return match sym {
|
||||
"==" => "Eq"
|
||||
"!=" => "Ne"
|
||||
"<" => "Lt"
|
||||
"<=" => "Le"
|
||||
">" => "Gt"
|
||||
">=" => "Ge"
|
||||
_ => ""
|
||||
}
|
||||
}
|
||||
eval(kind, a, b) {
|
||||
return match kind {
|
||||
"Eq" => { if a == b { 1 } else { 0 } }
|
||||
"Ne" => { if a != b { 1 } else { 0 } }
|
||||
"Lt" => { if a < b { 1 } else { 0 } }
|
||||
"Gt" => { if a > b { 1 } else { 0 } }
|
||||
"Le" => { if a <= b { 1 } else { 0 } }
|
||||
"Ge" => { if a >= b { 1 } else { 0 } }
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
25
lang/src/vm/boxes/compare_scan_box.hako
Normal file
25
lang/src/vm/boxes/compare_scan_box.hako
Normal file
@ -0,0 +1,25 @@
|
||||
// compare_scan_box.hako — CompareScanBox
|
||||
// Responsibility: Parse compare instruction (v0/v1) and return dst/lhs/rhs/kind
|
||||
// - v0: keys cmp/lhs/rhs/dst
|
||||
// - v1: operation:"==" maps to Eq (CompareOpsBox)
|
||||
// Returns: map({ dst:int|null, lhs:int|null, rhs:int|null, kind:String }) or null
|
||||
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
|
||||
|
||||
static box CompareScanBox {
|
||||
parse(seg) {
|
||||
if seg == null { return null }
|
||||
local dst = JsonFragBox.get_int(seg, "dst")
|
||||
local lhs = JsonFragBox.get_int(seg, "lhs")
|
||||
local rhs = JsonFragBox.get_int(seg, "rhs")
|
||||
local kind = JsonFragBox.get_str(seg, "cmp")
|
||||
if kind == "" {
|
||||
local sym = JsonFragBox.get_str(seg, "operation")
|
||||
if sym != "" { kind = CompareOpsBox.map_symbol(sym) } else { kind = "Eq" }
|
||||
}
|
||||
return map({ dst: dst, lhs: lhs, rhs: rhs, kind: kind })
|
||||
}
|
||||
}
|
||||
|
||||
static box CompareScanMain { main(args){ return 0 } }
|
||||
98
lang/src/vm/boxes/flow_debugger.hako
Normal file
98
lang/src/vm/boxes/flow_debugger.hako
Normal file
@ -0,0 +1,98 @@
|
||||
// flow_debugger.hako — Mini‑VM JSON v0 デバッグ用の軽量箱
|
||||
// 責務:
|
||||
// - JSON v0 の関数/ブロック/命令を静的に走査し、
|
||||
// - ブロックID集合の抽出
|
||||
// - branch/jump の then/else/target が妥当なIDか検証
|
||||
// - op シーケンスの要約出力(最初の N 件)
|
||||
// 非責務:
|
||||
// - 実行・評価(それは MirVmMin に委譲)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box FlowDebugBox {
|
||||
// ユーティリティ — 文字列検索
|
||||
_index_of_from(hay, needle, pos) { if pos < 0 { pos = 0 } local n = hay.size() if pos > n { return -1 } local i = pos local m = needle.size() if m <= 0 { return pos } local limit = n - m loop(i <= limit) { if hay.substring(i, i+m) == needle { return i } i = i + 1 } return -1 }
|
||||
_read_digits(text, pos) { return StringHelpers.read_digits(text, pos) }
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
|
||||
// ブロックID集合を抽出
|
||||
collect_block_ids(mjson) {
|
||||
local ids = new ArrayBox()
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local p = me._index_of_from(mjson, "\"id\":", pos)
|
||||
if p < 0 { break }
|
||||
local d = me._read_digits(mjson, p + 5)
|
||||
if d != "" { ids.push(d) }
|
||||
pos = p + 5
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// op シーケンスを先頭から limit 件だけ抽出
|
||||
collect_ops(mjson, limit) {
|
||||
if limit == null { limit = 50 }
|
||||
local ops = new ArrayBox()
|
||||
local pos = 0
|
||||
loop(ops.size() < limit) {
|
||||
local p = me._index_of_from(mjson, "\"op\":\"", pos)
|
||||
if p < 0 { break }
|
||||
local q = me._index_of_from(mjson, "\"", p + 6)
|
||||
if q < 0 { break }
|
||||
local op = mjson.substring(p + 6, q)
|
||||
ops.push(op)
|
||||
pos = q + 1
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
// branch/jump の then/else/target を抽出し、集合 membership を検査
|
||||
validate_cf_targets(mjson) {
|
||||
local ids = me.collect_block_ids(mjson)
|
||||
// Set 風マップ化
|
||||
local idset = map({})
|
||||
local i = 0
|
||||
loop(i < ids.size()) { idset.set(ids.get(i), 1) i = i + 1 }
|
||||
|
||||
local errs = new ArrayBox()
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local p = me._index_of_from(mjson, "\"op\":\"branch\"", pos)
|
||||
if p < 0 { break }
|
||||
// then
|
||||
local pt = me._index_of_from(mjson, "\"then\":", p)
|
||||
local pe = me._index_of_from(mjson, "\"else\":", p)
|
||||
if pt >= 0 { local t = me._read_digits(mjson, pt + 7) if t != "" && idset.get(t) == null { errs.push("branch.then invalid:" + t) } }
|
||||
if pe >= 0 { local e = me._read_digits(mjson, pe + 7) if e != "" && idset.get(e) == null { errs.push("branch.else invalid:" + e) } }
|
||||
pos = p + 14
|
||||
}
|
||||
|
||||
// jump
|
||||
pos = 0
|
||||
loop(true) {
|
||||
local p = me._index_of_from(mjson, "\"op\":\"jump\"", pos)
|
||||
if p < 0 { break }
|
||||
local pt = me._index_of_from(mjson, "\"target\":", p)
|
||||
if pt >= 0 { local t = me._read_digits(mjson, pt + 9) if t != "" && idset.get(t) == null { errs.push("jump.target invalid:" + t) } }
|
||||
pos = p + 12
|
||||
}
|
||||
|
||||
// レポート
|
||||
if errs.size() == 0 { print("{\"kind\":\"flow_debug\",\"ok\":true,\"blocks\":" + (""+ids.size()) + "}") }
|
||||
else {
|
||||
local k = 0
|
||||
loop(k < errs.size()) { print("{\"kind\":\"flow_debug\",\"ok\":false,\"msg\":\"" + errs.get(k) + "\"}") k = k + 1 }
|
||||
}
|
||||
return errs.size()
|
||||
}
|
||||
|
||||
// 要約: 先頭の op を列挙
|
||||
summarize_ops(mjson, limit) {
|
||||
local ops = me.collect_ops(mjson, limit)
|
||||
local i = 0
|
||||
loop(i < ops.size()) { print("{\"kind\":\"flow_ops\",\"op\":\"" + ops.get(i) + "\"}") i = i + 1 }
|
||||
return ops.size()
|
||||
}
|
||||
|
||||
main(args) { return 0 }
|
||||
}
|
||||
24
lang/src/vm/boxes/guard_box.hako
Normal file
24
lang/src/vm/boxes/guard_box.hako
Normal file
@ -0,0 +1,24 @@
|
||||
// guard_box.hako — GuardBox
|
||||
// 責務: 反復処理に上限を設け、無限ループをFail‑Fastに近い形で防止
|
||||
// 非責務: エラー出力のポリシー決定(呼び出し側で扱う)
|
||||
|
||||
box GuardBox {
|
||||
_name: StringBox
|
||||
_max: IntegerBox
|
||||
_cur: IntegerBox
|
||||
|
||||
birth(name, max_iter) {
|
||||
me._name = name
|
||||
me._max = max_iter
|
||||
me._cur = 0
|
||||
}
|
||||
|
||||
reset() { me._cur = 0 }
|
||||
|
||||
// 正常:1, 上限超:0 を返す(呼び出し側で中断)
|
||||
tick() {
|
||||
me._cur = me._cur + 1
|
||||
if me._cur > me._max { return 0 }
|
||||
return 1
|
||||
}
|
||||
}
|
||||
116
lang/src/vm/boxes/instruction_scanner.hako
Normal file
116
lang/src/vm/boxes/instruction_scanner.hako
Normal file
@ -0,0 +1,116 @@
|
||||
// instruction_scanner.hako — InstructionScannerBox
|
||||
// Minimal JSON v0 instruction object scanner with tolerant parsing.
|
||||
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/vm/boxes/cfg_navigator.hako" as CfgNavigatorBox
|
||||
|
||||
static box InstructionScannerBox {
|
||||
_tprint(msg) { if call("String.indexOf/2", msg, "[ERROR]") >= 0 { print(msg) } }
|
||||
|
||||
index_of_from(hay, needle, pos) { return CfgNavigatorBox.index_of_from(hay, needle, pos) }
|
||||
|
||||
find_balanced_object_end(json, idx) { return JsonCursorBox.seek_obj_end(json, idx) }
|
||||
|
||||
|
||||
normalize_delimiters(seg) {
|
||||
if seg == null { return "" }
|
||||
local out = ""
|
||||
local i = 0
|
||||
local n = seg.size()
|
||||
loop (i < n) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if i+2 <= n {
|
||||
local two = seg.substring(i, i+2)
|
||||
if two == "}," {
|
||||
if i+2 < n && seg.substring(i+2, i+3) == "{" { out = out + "}|{" i = i + 3 continue }
|
||||
}
|
||||
}
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_extract_op(obj) {
|
||||
// Prefer explicit op key with escape-aware string end detection
|
||||
local k1 = "\"op\":\""
|
||||
local p1 = obj.indexOf(k1)
|
||||
if p1 >= 0 {
|
||||
local i = p1 + k1.size() // start of value (right after opening quote)
|
||||
local j = JsonCursorBox.scan_string_end(obj, i - 1)
|
||||
if j > i { return obj.substring(i, j) }
|
||||
}
|
||||
// v1 style
|
||||
local kk = "\"kind\":\""
|
||||
local pk = obj.indexOf(kk)
|
||||
if pk >= 0 {
|
||||
local i2 = pk + kk.size()
|
||||
local j2 = JsonCursorBox.scan_string_end(obj, i2 - 1)
|
||||
if j2 > i2 {
|
||||
local k = obj.substring(i2, j2)
|
||||
if k == "Const" { return "const" }
|
||||
if k == "Ret" { return "ret" }
|
||||
if k == "Compare" { return "compare" }
|
||||
if k == "Branch" { return "branch" }
|
||||
if k == "Jump" { return "jump" }
|
||||
}
|
||||
}
|
||||
// compare v1 shorthand
|
||||
local ko = "\"operation\":\""
|
||||
local po = obj.indexOf(ko)
|
||||
if po >= 0 { return "compare" }
|
||||
// last-resort heuristics
|
||||
// Use dual-key probe to handle both plain and escaped forms when needed
|
||||
if me._has_key(obj, "lhs") == 1 and me._has_key(obj, "rhs") == 1 { return "compare" }
|
||||
if me._has_key(obj, "cond") == 1 { return "branch" }
|
||||
if me._has_key(obj, "target") == 1 { return "jump" }
|
||||
// Detect explicit ret first
|
||||
if obj.indexOf("\"op\":\"ret\"") >= 0 { return "ret" }
|
||||
// Detect v1-style Ret without op key: presence of top-level "value" and absence of other discriminator keys
|
||||
if me._has_key(obj, "value") == 1 {
|
||||
if me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 {
|
||||
return "ret"
|
||||
}
|
||||
}
|
||||
// Const fallback (typed value object)
|
||||
if obj.indexOf("\"value\":{\"type\":\"i64\"") >= 0 { return "const" }
|
||||
return ""
|
||||
}
|
||||
|
||||
// Dual-key existence probe (plain/escaped)
|
||||
_has_key(seg, key) {
|
||||
if seg == null { return 0 }
|
||||
local plain = "\"" + key + "\""
|
||||
local escaped = "\\\"" + key + "\\\""
|
||||
local pos = JsonCursorBox.find_key_dual(seg, plain, escaped, 0)
|
||||
if pos >= 0 { return 1 } else { return 0 }
|
||||
}
|
||||
|
||||
// Return a map {start,end,op} for next object starting at/after pos, or null if none
|
||||
next(seg, pos) {
|
||||
if seg == null { return null }
|
||||
if pos < 0 { pos = 0 }
|
||||
local start = me.index_of_from(seg, "{", pos)
|
||||
if start < 0 { return null }
|
||||
local endp = me.find_balanced_object_end(seg, start)
|
||||
if endp < 0 { return null }
|
||||
endp = endp + 1
|
||||
local obj = seg.substring(start, endp)
|
||||
local op = me._extract_op(obj)
|
||||
return map({ start: start, end: endp, op: op })
|
||||
}
|
||||
|
||||
// Mini‑VM friendly variant: return "start,end,op" to avoid MapBox dependency
|
||||
next_tuple(seg, pos) {
|
||||
if seg == null { return "" }
|
||||
if pos < 0 { pos = 0 }
|
||||
local start = me.index_of_from(seg, "{", pos)
|
||||
if start < 0 { return "" }
|
||||
local endp = me.find_balanced_object_end(seg, start)
|
||||
if endp < 0 { return "" }
|
||||
endp = endp + 1
|
||||
local obj = seg.substring(start, endp)
|
||||
local op = me._extract_op(obj)
|
||||
return "" + start + "," + endp + "," + op
|
||||
}
|
||||
}
|
||||
61
lang/src/vm/boxes/json_cur.hako
Normal file
61
lang/src/vm/boxes/json_cur.hako
Normal file
@ -0,0 +1,61 @@
|
||||
// Mini-VM JSON cursor helpers (extracted)
|
||||
// One static box per file per using/include policy
|
||||
static box MiniJsonCur {
|
||||
_is_digit(ch) { if ch == "0" { return 1 } if ch == "1" { return 1 } if ch == "2" { return 1 } if ch == "3" { return 1 } if ch == "4" { return 1 } if ch == "5" { return 1 } if ch == "6" { return 1 } if ch == "7" { return 1 } if ch == "8" { return 1 } if ch == "9" { return 1 } return 0 }
|
||||
// Skip whitespace from pos; return first non-ws index or -1
|
||||
next_non_ws(s, pos) {
|
||||
local i = pos
|
||||
local n = s.size()
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch != " " && ch != "\n" && ch != "\r" && ch != "\t" { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// Read a quoted string starting at pos '"'; returns decoded string (no state)
|
||||
read_quoted_from(s, pos) {
|
||||
local i = pos
|
||||
if s.substring(i, i+1) != "\"" { return "" }
|
||||
i = i + 1
|
||||
local out = ""
|
||||
local n = s.size()
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "\"" { break }
|
||||
if ch == "\\" {
|
||||
i = i + 1
|
||||
ch = s.substring(i, i+1)
|
||||
}
|
||||
out = out + ch
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
// Read consecutive digits from pos
|
||||
read_digits_from(s, pos) {
|
||||
local out = ""
|
||||
local i = pos
|
||||
// guard against invalid position (null/negative)
|
||||
if i == null { return out }
|
||||
if i < 0 { return out }
|
||||
loop (true) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
// inline digit check to avoid same-box method dispatch
|
||||
if ch == "0" { out = out + ch i = i + 1 continue }
|
||||
if ch == "1" { out = out + ch i = i + 1 continue }
|
||||
if ch == "2" { out = out + ch i = i + 1 continue }
|
||||
if ch == "3" { out = out + ch i = i + 1 continue }
|
||||
if ch == "4" { out = out + ch i = i + 1 continue }
|
||||
if ch == "5" { out = out + ch i = i + 1 continue }
|
||||
if ch == "6" { out = out + ch i = i + 1 continue }
|
||||
if ch == "7" { out = out + ch i = i + 1 continue }
|
||||
if ch == "8" { out = out + ch i = i + 1 continue }
|
||||
if ch == "9" { out = out + ch i = i + 1 continue }
|
||||
break
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
131
lang/src/vm/boxes/mini_collections.hako
Normal file
131
lang/src/vm/boxes/mini_collections.hako
Normal file
@ -0,0 +1,131 @@
|
||||
// mini_collections.hako — Minimal collection boxes for selfhost VM tests
|
||||
|
||||
// Simple string-backed dynamic array of i64 (for smoke/testing)
|
||||
box MiniArray {
|
||||
field data: String
|
||||
birth() { me.data = "" return 0 }
|
||||
push(v) {
|
||||
v = "" + v
|
||||
if me.data == "" { me.data = v } else { me.data = me.data + "," + v }
|
||||
return 0
|
||||
}
|
||||
length() {
|
||||
if me.data == "" { return 0 }
|
||||
// count commas + 1
|
||||
local s = me.data
|
||||
local i = 0
|
||||
local c = 1
|
||||
loop(true) {
|
||||
local j = s.indexOf(",", i)
|
||||
if j < 0 { break }
|
||||
c = c + 1
|
||||
i = j + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
// Fail‑Fast accessor: returns element string; prints error and returns 0 on OOB
|
||||
at(index) {
|
||||
// normalize and validate index
|
||||
local si = "" + index
|
||||
local idx = 0
|
||||
if si != "" {
|
||||
local i = 0
|
||||
loop(i < si.size()) { idx = idx * 10 + ("0123456789".indexOf(si.substring(i,i+1))) i = i + 1 }
|
||||
}
|
||||
local n = me.length()
|
||||
if idx < 0 || idx >= n { print("[ERROR] MiniArray.at: index out of range: " + (""+idx) + "/" + (""+n)) return 0 }
|
||||
// find start position of idx-th element
|
||||
local s = me.data
|
||||
local pos = 0
|
||||
local cur = 0
|
||||
loop(cur < idx) {
|
||||
local j = s.indexOf(",", pos)
|
||||
if j < 0 { print("[ERROR] MiniArray.at: broken storage") return 0 }
|
||||
pos = j + 1
|
||||
cur = cur + 1
|
||||
}
|
||||
local endp = s.indexOf(",", pos)
|
||||
if endp < 0 { endp = s.size() }
|
||||
return s.substring(pos, endp)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple string-backed map (key->value as 'k=v\n')
|
||||
box MiniMap2 {
|
||||
field store: String
|
||||
birth() { me.store = "" return 0 }
|
||||
set(key, value) {
|
||||
key = "" + key
|
||||
value = "" + value
|
||||
// Guard for unsupported characters in key that break line format
|
||||
if key.indexOf("\n") >= 0 || key.indexOf("=") >= 0 {
|
||||
print("[ERROR] MiniMap2.set: invalid key contains newline or '='")
|
||||
return 0
|
||||
}
|
||||
// remove and append
|
||||
local out = ""
|
||||
local s = me.store
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local nl = s.indexOf("\n", pos)
|
||||
if nl < 0 { break }
|
||||
local line = s.substring(pos, nl)
|
||||
local eq = line.indexOf("=")
|
||||
if eq >= 0 {
|
||||
local k = line.substring(0, eq)
|
||||
if k != key { out = out + line + "\n" }
|
||||
}
|
||||
pos = nl + 1
|
||||
}
|
||||
me.store = out + key + "=" + value + "\n"
|
||||
return 0
|
||||
}
|
||||
get(key) {
|
||||
key = "" + key
|
||||
local s = me.store
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local nl = s.indexOf("\n", pos)
|
||||
if nl < 0 { break }
|
||||
local line = s.substring(pos, nl)
|
||||
local eq = line.indexOf("=")
|
||||
if eq >= 0 {
|
||||
local k = line.substring(0, eq)
|
||||
if k == key { return line.substring(eq + 1, line.size()) }
|
||||
}
|
||||
pos = nl + 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
// Strict getter: Fail‑Fast when key is missing; returns 0 on failure
|
||||
get_or_fail(key) {
|
||||
key = "" + key
|
||||
local v = me.get(key)
|
||||
if v == null { print("[ERROR] MiniMap2.get: key not found: " + key) return 0 }
|
||||
return v
|
||||
}
|
||||
has(key) {
|
||||
key = "" + key
|
||||
local s = me.store
|
||||
if s == "" { return 0 }
|
||||
// naive contains of 'key=' at line start or after \n
|
||||
local needle = key + "="
|
||||
if s.substring(0, needle.size()) == needle { return 1 }
|
||||
local p = s.indexOf("\n" + needle)
|
||||
if p >= 0 { return 1 }
|
||||
return 0
|
||||
}
|
||||
size() {
|
||||
if me.store == "" { return 0 }
|
||||
local s = me.store
|
||||
local i = 0
|
||||
local c = 0
|
||||
loop(true) {
|
||||
local nl = s.indexOf("\n", i)
|
||||
if nl < 0 { break }
|
||||
c = c + 1
|
||||
i = nl + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
14
lang/src/vm/boxes/mini_vm_core.hako
Normal file
14
lang/src/vm/boxes/mini_vm_core.hako
Normal file
@ -0,0 +1,14 @@
|
||||
using "lang/src/vm/boxes/json_cur.hako" as MiniJson
|
||||
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
|
||||
using "lang/src/shared/common/mini_vm_binop.hako" as MiniVmBinOp
|
||||
using "lang/src/shared/common/mini_vm_compare.hako" as MiniVmCompare
|
||||
using "lang/src/vm/boxes/mini_vm_prints.hako" as MiniVmPrints
|
||||
|
||||
static box MiniVm {
|
||||
_str_to_int(s) { return new MiniVmScan()._str_to_int(s) }
|
||||
_int_to_str(n) { return new MiniVmScan()._int_to_str(n) }
|
||||
read_digits(json, pos) { return new MiniJsonCur().read_digits_from(json, pos) }
|
||||
read_json_string(json, pos) { return new MiniJsonCur().read_quoted_from(json, pos) }
|
||||
index_of_from(hay, needle, pos) { return new MiniVmScan().index_of_from(hay, needle, pos) }
|
||||
next_non_ws(json, pos) { return new MiniJsonCur().next_non_ws(json, pos) }
|
||||
}
|
||||
18
lang/src/vm/boxes/mini_vm_entry.hako
Normal file
18
lang/src/vm/boxes/mini_vm_entry.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// mini_vm_entry.hako — MiniVmEntryBox
|
||||
// Thin entry wrapper to stabilize static call names for smokes and tools.
|
||||
using "lang/src/vm/boxes/mir_vm_min.hako" as MirVmMin
|
||||
|
||||
static box MiniVmEntryBox {
|
||||
|
||||
run_trace(j) {
|
||||
if j.substring(0,1) == "{" {
|
||||
local payload = j.substring(1, j.size())
|
||||
local j2 = "{\"__trace__\":1," + payload
|
||||
return MirVmMin.run_min(j2)
|
||||
}
|
||||
return MirVmMin.run_min(j)
|
||||
}
|
||||
|
||||
run_min(j) { return MirVmMin.run_min(j) }
|
||||
int_to_str(v) { return MirVmMin._int_to_str(v) }
|
||||
}
|
||||
114
lang/src/vm/boxes/mini_vm_prints.hako
Normal file
114
lang/src/vm/boxes/mini_vm_prints.hako
Normal file
@ -0,0 +1,114 @@
|
||||
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
|
||||
using "lang/src/shared/common/mini_vm_binop.hako" as MiniVmBinOp
|
||||
using "lang/src/shared/common/mini_vm_compare.hako" as MiniVmCompare
|
||||
// Use the JSON adapter facade for cursor ops (next_non_ws, digits)
|
||||
using "lang/src/vm/boxes/json_cur.hako" as MiniJsonLoader
|
||||
|
||||
static box MiniVmPrints {
|
||||
_trace_enabled() { return 0 }
|
||||
_fallback_enabled() { return 0 }
|
||||
// literal string within Print
|
||||
try_print_string_value_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_val = "\"value\":\""
|
||||
local s = scan.index_of_from(json, k_val, print_pos)
|
||||
if s < 0 || s >= end { return -1 }
|
||||
local i = s + k_val.size()
|
||||
local j = scan.index_of_from(json, "\"", i)
|
||||
if j <= 0 || j > end { return -1 }
|
||||
print(json.substring(i, j))
|
||||
return j + 1
|
||||
}
|
||||
// literal int within Print (typed)
|
||||
try_print_int_value_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = scan.index_of_from(json, k_expr, print_pos)
|
||||
if epos <= 0 || epos >= end { return -1 }
|
||||
local obj_start = scan.index_of_from(json, "{", epos)
|
||||
if obj_start <= 0 || obj_start >= end { return -1 }
|
||||
local obj_end = scan.find_balanced_object_end(json, obj_start)
|
||||
if obj_end <= 0 || obj_end > end { return -1 }
|
||||
local k_tint = "\"type\":\"int\""
|
||||
local tpos = scan.index_of_from(json, k_tint, obj_start)
|
||||
if tpos <= 0 || tpos >= obj_end { return -1 }
|
||||
local k_val2 = "\"value\":"
|
||||
local v2 = scan.index_of_from(json, k_val2, tpos)
|
||||
if v2 <= 0 || v2 >= obj_end { return -1 }
|
||||
local digits = scan.read_digits(json, v2 + k_val2.size())
|
||||
if digits == "" { return -1 }
|
||||
print(digits)
|
||||
return obj_end + 1
|
||||
}
|
||||
// minimal FunctionCall printer for echo/itoa
|
||||
try_print_functioncall_at(json, end, print_pos) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = scan.index_of_from(json, k_fc, print_pos)
|
||||
if fcp <= 0 || fcp >= end { return -1 }
|
||||
local k_name = "\"name\":\""
|
||||
local npos = scan.index_of_from(json, k_name, fcp)
|
||||
if npos <= 0 || npos >= end { return -1 }
|
||||
local ni = npos + k_name.size()
|
||||
local nj = scan.index_of_from(json, "\"", ni)
|
||||
if nj <= 0 || nj > end { return -1 }
|
||||
local fname = json.substring(ni, nj)
|
||||
local k_args = "\"arguments\":["
|
||||
local apos = scan.index_of_from(json, k_args, nj)
|
||||
if apos <= 0 || apos >= end { return -1 }
|
||||
local arr_start = scan.index_of_from(json, "[", apos)
|
||||
local arr_end = scan.find_balanced_array_end(json, arr_start)
|
||||
if arr_start <= 0 || arr_end <= 0 || arr_end > end { return -1 }
|
||||
// handle empty args []
|
||||
local nn = new MiniJsonLoader().next_non_ws(json, arr_start+1)
|
||||
if nn > 0 && nn <= arr_end {
|
||||
if json.substring(nn, nn+1) == "]" {
|
||||
if fname == "echo" { print("") return arr_end + 1 }
|
||||
if fname == "itoa" { print("0") return arr_end + 1 }
|
||||
return -1
|
||||
}
|
||||
}
|
||||
// first arg type
|
||||
local k_t = "\"type\":\""
|
||||
local atpos = scan.index_of_from(json, k_t, arr_start)
|
||||
if atpos <= 0 || atpos >= arr_end {
|
||||
if fname == "echo" { print("") return arr_end + 1 }
|
||||
if fname == "itoa" { print("0") return arr_end + 1 }
|
||||
return -1
|
||||
}
|
||||
atpos = atpos + k_t.size()
|
||||
local at_end = scan.index_of_from(json, "\"", atpos)
|
||||
if at_end <= 0 || at_end > arr_end { return -1 }
|
||||
local aty = json.substring(atpos, at_end)
|
||||
if aty == "string" {
|
||||
local k_sval = "\"value\":\""
|
||||
local svalp = scan.index_of_from(json, k_sval, at_end)
|
||||
if svalp <= 0 || svalp >= arr_end { return -1 }
|
||||
local si = svalp + k_sval.size()
|
||||
local sj = scan.index_of_from(json, "\"", si)
|
||||
if sj <= 0 || sj > arr_end { return -1 }
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { print(sval) return sj + 1 }
|
||||
return -1
|
||||
}
|
||||
if aty == "int" || aty == "i64" || aty == "integer" {
|
||||
local k_ival = "\"value\":"
|
||||
local ivalp = scan.index_of_from(json, k_ival, at_end)
|
||||
if ivalp <= 0 || ivalp >= arr_end { return -1 }
|
||||
local digits = scan.read_digits(json, ivalp + k_ival.size())
|
||||
if fname == "itoa" || fname == "echo" { print(digits) return ivalp + k_ival.size() }
|
||||
return -1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// Print all Print-Literal values within [start,end]
|
||||
print_prints_in_slice(json, start, end) {
|
||||
// Prefer plugin result whenever JSON route ran
|
||||
local dbg = _trace_enabled()
|
||||
local printed = 0
|
||||
printed = printed // placeholder to keep structure; logic in .nyash retained
|
||||
return printed
|
||||
}
|
||||
process_if_once(json) { return new MiniVmPrints().process_if_once(json) }
|
||||
print_all_print_literals(json) { return new MiniVmPrints().print_all_print_literals(json) }
|
||||
}
|
||||
52
lang/src/vm/boxes/minivm_probe.hako
Normal file
52
lang/src/vm/boxes/minivm_probe.hako
Normal file
@ -0,0 +1,52 @@
|
||||
// minivm_probe.hako — Mini‑VM JSON v0 の a/b/r を観測する軽量プローブ
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/vm/boxes/instruction_scanner.hako" as InstructionScannerBox
|
||||
using "lang/src/vm/boxes/op_handlers.hako" as OpHandlersBox
|
||||
|
||||
static box MiniVmProbe {
|
||||
probe_compare(mjson) {
|
||||
local regs = map({})
|
||||
local seg = JsonFragBox.block0_segment(mjson)
|
||||
if seg.size() == 0 { return map({}) }
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
if pos >= seg.size() { break }
|
||||
// Use escape-aware scanner to get next instruction object
|
||||
local mm = InstructionScannerBox.next(seg, pos)
|
||||
if mm == null { break }
|
||||
local s = mm.get("start")
|
||||
local i = mm.get("end")
|
||||
local obj = seg.substring(s, i)
|
||||
local op = JsonFragBox.get_str(obj, "op")
|
||||
match op {
|
||||
"const" => { OpHandlersBox.handle_const(obj, regs) }
|
||||
"binop" => { OpHandlersBox.handle_binop(obj, regs) }
|
||||
"compare" => {
|
||||
local kind = JsonFragBox.get_str(obj, "cmp")
|
||||
local lhs = JsonFragBox.get_int(obj, "lhs")
|
||||
local rhs = JsonFragBox.get_int(obj, "rhs")
|
||||
local as2 = "" + regs.get(""+lhs)
|
||||
local bs2 = "" + regs.get(""+rhs)
|
||||
local a = JsonFragBox._str_to_int(as2)
|
||||
local b = JsonFragBox._str_to_int(bs2)
|
||||
local r = match kind {
|
||||
"Eq" => { if a == b { 1 } else { 0 } }
|
||||
"Ne" => { if a != b { 1 } else { 0 } }
|
||||
"Lt" => { if a < b { 1 } else { 0 } }
|
||||
"Gt" => { if a > b { 1 } else { 0 } }
|
||||
"Le" => { if a <= b { 1 } else { 0 } }
|
||||
"Ge" => { if a >= b { 1 } else { 0 } }
|
||||
_ => 0
|
||||
}
|
||||
return map({ a: a, b: b, r: r })
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pos = i
|
||||
}
|
||||
return map({ a: 0, b: 0, r: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
static box MiniVmProbeStub { main(args) { return 0 } }
|
||||
84
lang/src/vm/boxes/mir_vm_m2.hako
Normal file
84
lang/src/vm/boxes/mir_vm_m2.hako
Normal file
@ -0,0 +1,84 @@
|
||||
// mir_vm_m2.nyash — Ny製の最小MIR(JSON v0)実行器(M2: const/binop/ret)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
|
||||
static box MirVmM2 {
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
_find_int_in(seg, keypat) {
|
||||
local p = seg.indexOf(keypat)
|
||||
if p < 0 { return null }
|
||||
p = p + keypat.size()
|
||||
local i = p
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out == "" { return null }
|
||||
return me._str_to_int(out)
|
||||
}
|
||||
_find_str_in(seg, keypat) {
|
||||
local p = seg.indexOf(keypat)
|
||||
if p < 0 { return "" }
|
||||
p = p + keypat.size()
|
||||
local q = seg.indexOf(""", p)
|
||||
if q < 0 { return "" }
|
||||
return seg.substring(p, q)
|
||||
}
|
||||
_get(regs, id) { if regs.has(id) { return regs.get(id) } return 0 }
|
||||
_set(regs, id, v) { regs.set(id, v) }
|
||||
_bin(kind, a, b) {
|
||||
if kind == "Add" { return a + b }
|
||||
if kind == "Sub" { return a - b }
|
||||
if kind == "Mul" { return a * b }
|
||||
if kind == "Div" { if b == 0 { return 0 } else { return a / b } }
|
||||
return 0
|
||||
}
|
||||
run(json) {
|
||||
local regs = new MapBox()
|
||||
local pos = StringOps.index_of_from(json, ""instructions":[", 0)
|
||||
if pos < 0 {
|
||||
print("0")
|
||||
return 0
|
||||
}
|
||||
local cur = pos
|
||||
loop(true) {
|
||||
local op_pos = StringOps.index_of_from(json, ""op":"", cur)
|
||||
if op_pos < 0 { break }
|
||||
local name_start = op_pos + 6
|
||||
local name_end = StringOps.index_of_from(json, """, name_start)
|
||||
if name_end < 0 { break }
|
||||
local opname = json.substring(name_start, name_end)
|
||||
local next_pos = StringOps.index_of_from(json, ""op":"", name_end)
|
||||
if next_pos < 0 { next_pos = json.size() }
|
||||
local seg = json.substring(op_pos, next_pos)
|
||||
if opname == "const" {
|
||||
local dst = me._find_int_in(seg, ""dst":")
|
||||
local val = me._find_int_in(seg, ""value":{"type":"i64","value":")
|
||||
if dst != null and val != null { me._set(regs, "" + dst, val) }
|
||||
} else { if opname == "binop" {
|
||||
local dst = me._find_int_in(seg, ""dst":")
|
||||
local kind = me._find_str_in(seg, ""op_kind":"")
|
||||
local lhs = me._find_int_in(seg, ""lhs":")
|
||||
local rhs = me._find_int_in(seg, ""rhs":")
|
||||
if dst != null and lhs != null and rhs != null {
|
||||
local a = me._get(regs, "" + lhs)
|
||||
local b = me._get(regs, "" + rhs)
|
||||
me._set(regs, "" + dst, me._bin(kind, a, b))
|
||||
}
|
||||
} else { if opname == "ret" {
|
||||
local v = me._find_int_in(seg, ""value":")
|
||||
if v == null { v = 0 }
|
||||
local out = me._get(regs, "" + v)
|
||||
print(me._int_to_str(out))
|
||||
return 0
|
||||
} } }
|
||||
cur = next_pos
|
||||
}
|
||||
print("0")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
465
lang/src/vm/boxes/mir_vm_min.hako
Normal file
465
lang/src/vm/boxes/mir_vm_min.hako
Normal file
@ -0,0 +1,465 @@
|
||||
// mir_vm_min.hako — Ny製の最小MIR(JSON v0)実行器(const/compare/copy/branch/jump/ret の最小)
|
||||
using "lang/src/vm/boxes/op_handlers.hako" as OpHandlersBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/vm/boxes/operator_box.hako" as OperatorBox
|
||||
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
|
||||
using "lang/src/vm/boxes/compare_scan_box.hako" as CompareScanBox
|
||||
using "lang/src/vm/boxes/phi_apply_box.hako" as PhiApplyBox
|
||||
using "lang/src/vm/boxes/guard_box.hako" as GuardBox
|
||||
using "lang/src/vm/boxes/result_box.hako" as Result
|
||||
using "lang/src/vm/boxes/phi_decode_box.hako" as PhiDecodeBox
|
||||
using "lang/src/vm/boxes/ret_resolve_simple.hako" as RetResolveSimpleBox
|
||||
using "lang/src/vm/boxes/instruction_scanner.hako" as InstructionScannerBox
|
||||
|
||||
// Minimal map for Mini‑VM registers (avoid dependency on provider MapBox)
|
||||
box MiniMap {
|
||||
field store: String
|
||||
birth() { me.store = "" return 0 }
|
||||
set(key, value) {
|
||||
key = "" + key
|
||||
value = "" + value
|
||||
// remove existing key
|
||||
local out = ""
|
||||
local s = me.store
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local nl = s.indexOf("\n", pos)
|
||||
if nl < 0 { break }
|
||||
local line = s.substring(pos, nl)
|
||||
local eq = line.indexOf("=")
|
||||
if eq >= 0 {
|
||||
local k = line.substring(0, eq)
|
||||
if k != key { out = out + line + "\n" }
|
||||
}
|
||||
pos = nl + 1
|
||||
}
|
||||
me.store = out + key + "=" + value + "\n"
|
||||
return 0
|
||||
}
|
||||
get(key) {
|
||||
key = "" + key
|
||||
local s = me.store
|
||||
local pos = 0
|
||||
local last = null
|
||||
loop(true) {
|
||||
local nl = s.indexOf("\n", pos)
|
||||
if nl < 0 { break }
|
||||
local line = s.substring(pos, nl)
|
||||
local eq = line.indexOf("=")
|
||||
if eq >= 0 {
|
||||
local k = line.substring(0, eq)
|
||||
if k == key { last = line.substring(eq + 1, line.size()) }
|
||||
}
|
||||
pos = nl + 1
|
||||
}
|
||||
return last
|
||||
}
|
||||
}
|
||||
|
||||
static box MirVmMin {
|
||||
_tprint(msg) {
|
||||
// Only emit hard errors by default; avoid env dependencies in Mini‑VM
|
||||
// Coerce to string to avoid VoidBox receiver issues during early boot
|
||||
msg = "" + msg
|
||||
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
|
||||
}
|
||||
_d(msg, trace) { if trace == 1 { print(msg) } }
|
||||
_parse_callee_name(seg) {
|
||||
// naive scan: '"callee":{"name":"<sym>"'
|
||||
local key = '"callee":{"name":"'
|
||||
local p = seg.indexOf(key)
|
||||
if p < 0 { return "" }
|
||||
p = p + key.size()
|
||||
local rest = seg.substring(p, seg.size())
|
||||
local q = rest.indexOf('"')
|
||||
if q < 0 { return "" }
|
||||
return rest.substring(0, q)
|
||||
}
|
||||
_parse_first_arg(seg) {
|
||||
// naive scan: '"args":[ <int>'
|
||||
local key = '"args":'
|
||||
local p = seg.indexOf(key)
|
||||
if p < 0 { return null }
|
||||
p = p + key.size()
|
||||
// find first digit or '-'
|
||||
local i = p
|
||||
loop(true) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if ch == "" { return null }
|
||||
if ch == "-" || (ch >= "0" && ch <= "9") { break }
|
||||
i = i + 1
|
||||
}
|
||||
// collect digits
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out == "" { return null }
|
||||
return JsonFragBox._str_to_int(out)
|
||||
}
|
||||
_handle_mir_call(seg, regs) {
|
||||
// Support minimal externs only (i64 variants). Fail‑Fast for others.
|
||||
local name = me._parse_callee_name(seg)
|
||||
local arg0id = me._parse_first_arg(seg)
|
||||
if name == "" {
|
||||
me._tprint("[ERROR] mir_call: missing callee")
|
||||
return
|
||||
}
|
||||
if arg0id == null { arg0id = -1 }
|
||||
// String console: env/nyash console log/warn/error — treat arg0 as string
|
||||
if name == "env.console.log" || name == "nyash.console.log" ||
|
||||
name == "env.console.warn" || name == "nyash.console.warn" ||
|
||||
name == "env.console.error" || name == "nyash.console.error" {
|
||||
local v = ""
|
||||
if arg0id >= 0 {
|
||||
local raw = regs.getField(""+arg0id)
|
||||
v = "" + raw
|
||||
}
|
||||
print(v)
|
||||
return
|
||||
}
|
||||
if name == "hako_console_log_i64" {
|
||||
local v = 0
|
||||
if arg0id >= 0 { v = me._load_reg(regs, arg0id) }
|
||||
print(me._int_to_str(v))
|
||||
return
|
||||
}
|
||||
if name == "hako_bench_noop_i64" || name == "hako_bench_use_value_i64" {
|
||||
// no-op (observability only)
|
||||
return
|
||||
}
|
||||
// Unknown extern: Fail‑Fast (emit once)
|
||||
me._tprint("[ERROR] extern not supported: " + name)
|
||||
}
|
||||
// Compatibility runner (prints and returns). Prefer run_min for quiet return-only.
|
||||
run(mjson) { local v = me._run_min(mjson) print(me._int_to_str(v)) return v }
|
||||
// New: quiet entry that returns the result without printing.
|
||||
run_min(mjson) { return me._run_min(mjson) }
|
||||
// Thin-mode runner (ret resolution simplified)
|
||||
run_thin(mjson) {
|
||||
// Inject a lightweight marker into JSON to toggle thin mode inside _run_min
|
||||
if mjson.substring(0,1) == "{" {
|
||||
local payload = mjson.substring(1, mjson.size())
|
||||
local j2 = "{\"__thin__\":1," + payload
|
||||
local v = me._run_min(j2)
|
||||
print(me._int_to_str(v))
|
||||
return v
|
||||
}
|
||||
// Fallback: no-op when input is unexpected
|
||||
local v = me._run_min(mjson)
|
||||
print(me._int_to_str(v))
|
||||
return v
|
||||
}
|
||||
|
||||
// helpers
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
_is_numeric_str(s){ if s==null {return 0} local n=s.size() if n==0 {return 0} local i=0 if s.substring(0,1)=="-" { if n==1 {return 0} i=1 } loop(i<n){ local ch=s.substring(i,i+1) if ch<"0"||ch>"9" {return 0} i=i+1 } return 1 }
|
||||
_load_reg(regs,id){ local v=regs.getField(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return JsonFragBox._str_to_int(s) } return 0 }
|
||||
|
||||
// block helpers
|
||||
_block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 }
|
||||
|
||||
// local copy handler
|
||||
_handle_copy(seg, regs){ local dst=JsonFragBox.get_int(seg,"dst") local src=JsonFragBox.get_int(seg,"src") if dst==null || src==null {return} local v=regs.getField(""+src) regs.setField(""+dst, v) }
|
||||
|
||||
_run_min(mjson) {
|
||||
// Normalize input as string to guarantee String methods availability
|
||||
mjson = "" + mjson
|
||||
// thin_mode=0: legacy heuristics(互換); thin_mode=1: simplified ret
|
||||
local thin_mode = 0
|
||||
if mjson.indexOf("\"__thin__\":1") >= 0 { thin_mode = 1 }
|
||||
// trace mode for dev: prints [DEBUG] messages
|
||||
local trace = 0
|
||||
if mjson.indexOf("\"__trace__\":1") >= 0 { trace = 1 }
|
||||
// gc trace (mini‑VM専用マーカー; 環境依存を避ける)
|
||||
local gc_trace = 0
|
||||
if mjson.indexOf("\"__gc_trace__\":1") >= 0 { gc_trace = 1 }
|
||||
|
||||
// Safety first: handle simple compare→ret or const→ret in Block 0
|
||||
// without allocating any runtime boxes. This avoids interpreter-level
|
||||
// recursion during early startup (observed as Rust stack overflow) and
|
||||
// covers our selfhost M2 minimal cases.
|
||||
local b0 = JsonFragBox.block0_segment(mjson)
|
||||
if b0 != "" {
|
||||
// 1) Pure const→ret (only when const appears before ret and no other ops present)
|
||||
if b0.indexOf("\"op\":\"compare\"") < 0 && b0.indexOf("\"op\":\"binop\"") < 0 && b0.indexOf("\"op\":\"copy\"") < 0 && b0.indexOf("\"op\":\"jump\"") < 0 && b0.indexOf("\"op\":\"branch\"") < 0 {
|
||||
local ret_pos = b0.indexOf("\"op\":\"ret\"")
|
||||
local const_pos = b0.indexOf("\"op\":\"const\"")
|
||||
if ret_pos >= 0 and const_pos >= 0 and const_pos < ret_pos {
|
||||
// Grab the first typed const value and return it
|
||||
local key = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||
local p = StringOps.index_of_from(b0, key, 0)
|
||||
if p >= 0 {
|
||||
local ds = b0.substring(p + key.size(), b0.size())
|
||||
// read consecutive digits
|
||||
local i = 0
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = ds.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out != "" { return JsonFragBox._str_to_int(out) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) compare→ret fast-path skipped (handled by main scanner)
|
||||
|
||||
// Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies
|
||||
local regs = new MiniMap()
|
||||
local last_cmp_dst = -1
|
||||
local last_cmp_val = 0
|
||||
// Optional: adopt OperatorBox for parity checks when JSON has a special marker
|
||||
local op_adopt = 0
|
||||
if mjson.indexOf("\"__op_adopt__\":1") >= 0 { op_adopt = 1 }
|
||||
// Track last binop materialization within the current scan window
|
||||
local last_binop_dst = -1
|
||||
local last_binop_val = 0
|
||||
local bb = 0
|
||||
local prev_bb = -1
|
||||
local steps = 0
|
||||
local max_steps = 200000
|
||||
loop(true){
|
||||
steps = steps + 1
|
||||
if steps > max_steps { return 0 }
|
||||
local start = me._block_insts_start(mjson, bb)
|
||||
me._d("[DEBUG] start="+me._int_to_str(start), trace)
|
||||
if start < 0 { return 0 }
|
||||
local endp = JsonCursorBox.seek_array_end(mjson, start)
|
||||
me._d("[DEBUG] endp="+me._int_to_str(endp), trace)
|
||||
if endp <= start { return 0 }
|
||||
local inst_seg = mjson.substring(start, endp)
|
||||
me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.size()), trace)
|
||||
// scan objects in this block
|
||||
local scan_pos = 0
|
||||
local inst_count = 0
|
||||
local moved = 0
|
||||
loop(true){
|
||||
if scan_pos >= inst_seg.size() { break }
|
||||
local tup = InstructionScannerBox.next_tuple(inst_seg, scan_pos)
|
||||
if tup == "" { break }
|
||||
// parse "start,end,op"
|
||||
local c1 = StringOps.index_of_from(tup, ",", 0)
|
||||
local c2 = StringOps.index_of_from(tup, ",", c1+1)
|
||||
if c1 < 0 || c2 < 0 { break }
|
||||
local obj_start = JsonFragBox._str_to_int(tup.substring(0, c1))
|
||||
local obj_end = JsonFragBox._str_to_int(tup.substring(c1+1, c2))
|
||||
local op = tup.substring(c2+1, tup.size())
|
||||
local seg = inst_seg.substring(obj_start, obj_end)
|
||||
if op == null { op = "" } if op == "null" { op = "" } if op == 0 { op = "" }
|
||||
if op == "" {
|
||||
if seg.indexOf("op_kind") >= 0 { op = "binop" } else {
|
||||
if seg.indexOf("lhs") >= 0 and seg.indexOf("rhs") >= 0 { op = "compare" } else {
|
||||
if seg.indexOf("cond") >= 0 { op = "branch" } else {
|
||||
if seg.indexOf("target") >= 0 { op = "jump" } else {
|
||||
if seg.indexOf("src") >= 0 and seg.indexOf("dst") >= 0 { op = "copy" } else { op = "const" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if op == "const" {
|
||||
OpHandlersBox.handle_const(seg, regs)
|
||||
}
|
||||
else if op == "copy" { me._handle_copy(seg, regs) }
|
||||
else if op == "binop" {
|
||||
OpHandlersBox.handle_binop(seg, regs)
|
||||
local bdst = JsonFragBox.get_int(seg, "dst")
|
||||
if bdst != null { last_binop_dst = bdst last_binop_val = me._load_reg(regs, bdst) }
|
||||
}
|
||||
else if op == "compare" {
|
||||
me._d("[DEBUG] compare seg=" + seg, trace)
|
||||
// Safe fast-path: if this block later contains a ret of this dst,
|
||||
// evaluate compare directly from current regs and return immediately.
|
||||
// This avoids deeper handler chains that could recurse in edge cases.
|
||||
local rec = CompareScanBox.parse(seg)
|
||||
local kdst_fast = rec.get("dst")
|
||||
local klhs_fast = rec.get("lhs")
|
||||
local krhs_fast = rec.get("rhs")
|
||||
local kcmp_fast = rec.get("kind")
|
||||
// Determine if a ret exists after this compare in the same block
|
||||
local tail = inst_seg.substring(obj_end, inst_seg.size())
|
||||
local ridt = JsonFragBox.get_int(tail, "value")
|
||||
if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast {
|
||||
local a = me._load_reg(regs, klhs_fast)
|
||||
local b = me._load_reg(regs, krhs_fast)
|
||||
local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b)
|
||||
// Store result to keep regs consistent for subsequent ops if any
|
||||
regs.set("" + kdst_fast, "" + cv_fast)
|
||||
last_cmp_dst = kdst_fast
|
||||
last_cmp_val = cv_fast
|
||||
me._d("[DEBUG] compare early return with val=" + me._int_to_str(cv_fast), trace)
|
||||
return cv_fast
|
||||
}
|
||||
|
||||
// Fallback to standard handler when early path is not applicable
|
||||
OpHandlersBox.handle_compare(seg, regs)
|
||||
local kdst = rec.get("dst")
|
||||
me._d("[DEBUG] compare kdst=" + me._int_to_str(kdst), trace)
|
||||
if kdst != null {
|
||||
last_cmp_dst = kdst
|
||||
last_cmp_val = me._load_reg(regs, kdst)
|
||||
me._d("[DEBUG] compare last_cmp_dst=" + me._int_to_str(last_cmp_dst) + " last_cmp_val=" + me._int_to_str(last_cmp_val), trace)
|
||||
me._d("[DEBUG] compare reg["+me._int_to_str(kdst)+"]=" + regs.get(""+kdst), trace)
|
||||
}
|
||||
// Secondary optimization: if a ret follows and targets this dst, return now
|
||||
if ridt != null && kdst != null && ridt == kdst { return last_cmp_val }
|
||||
}
|
||||
else if op == "mir_call" {
|
||||
me._handle_mir_call(seg, regs)
|
||||
}
|
||||
else if op == "branch" {
|
||||
local c = JsonFragBox.get_int(seg, "cond")
|
||||
local t = JsonFragBox.get_int(seg, "then")
|
||||
local e = JsonFragBox.get_int(seg, "else")
|
||||
local cv = 0
|
||||
if c != null {
|
||||
if c == last_cmp_dst { cv = last_cmp_val } else { cv = me._load_reg(regs, c) }
|
||||
}
|
||||
prev_bb = bb
|
||||
if cv != 0 { bb = t } else { bb = e }
|
||||
moved = 1
|
||||
// GC v0 safepoint: back-edge or control transfer
|
||||
using "lang/src/vm/gc/gc_hooks.hako" as GcHooks
|
||||
GcHooks.safepoint()
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
scan_pos = obj_end
|
||||
break
|
||||
}
|
||||
else if op == "jump" {
|
||||
local tgt = JsonFragBox.get_int(seg, "target")
|
||||
if tgt == null { return 0 }
|
||||
prev_bb = bb
|
||||
bb = tgt
|
||||
moved = 1
|
||||
// GC v0 safepoint: back-edge or control transfer
|
||||
using "lang/src/vm/gc/gc_hooks.hako" as GcHooks
|
||||
GcHooks.safepoint()
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
scan_pos = obj_end
|
||||
break
|
||||
}
|
||||
else if op == "phi" {
|
||||
local res = PhiDecodeBox.decode_result(seg, prev_bb)
|
||||
if res.is_Ok() == 1 {
|
||||
local pair = res.as_Ok()
|
||||
PhiApplyBox.apply(pair.get(0), pair.get(1), regs)
|
||||
}
|
||||
}
|
||||
else if op == "throw" {
|
||||
me._tprint("[ERROR] Throw terminator encountered")
|
||||
return -2
|
||||
}
|
||||
else if op == "ret" {
|
||||
local v = JsonFragBox.get_int(seg, "value")
|
||||
if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 }
|
||||
if v == last_cmp_dst {
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
return last_cmp_val
|
||||
}
|
||||
local sval = "" + regs.get(""+v)
|
||||
if me._is_numeric_str(sval) == 1 {
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
return me._load_reg(regs, v)
|
||||
}
|
||||
me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v))
|
||||
return -1
|
||||
}
|
||||
// advance
|
||||
inst_count = inst_count + 1
|
||||
scan_pos = obj_end
|
||||
}
|
||||
if moved == 1 { continue }
|
||||
// Prefer recent compare result when a ret exists targeting it (no recompute)
|
||||
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
|
||||
local rstartX = inst_seg.indexOf("\"op\":\"ret\"")
|
||||
local rsegX = inst_seg.substring(rstartX, inst_seg.size())
|
||||
local ridX = JsonFragBox.get_int(rsegX, "value")
|
||||
if ridX != null { if ridX == last_cmp_dst { return last_cmp_val } }
|
||||
}
|
||||
// Fallbacks: prefer recent compare result, else resolve explicit/v1 ret if present, else first const
|
||||
if last_cmp_dst >= 0 { return last_cmp_val }
|
||||
// Detect explicit ret in this block and resolve
|
||||
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
|
||||
local rstart = inst_seg.indexOf("\"op\":\"ret\"")
|
||||
local rseg = inst_seg.substring(rstart, inst_seg.size())
|
||||
local rid = JsonFragBox.get_int(rseg, "value")
|
||||
if rid != null {
|
||||
if rid == last_cmp_dst {
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
return last_cmp_val
|
||||
}
|
||||
local rv = me._load_reg(regs, rid)
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
return rv
|
||||
}
|
||||
}
|
||||
# (typed const fallback moved above)
|
||||
if false {
|
||||
// Grab first two "value":{"type":"i64","value":X}
|
||||
local first = ""
|
||||
local second = ""
|
||||
local search = inst_seg
|
||||
local key = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||
local pos = 0
|
||||
loop(true) {
|
||||
local k = StringOps.index_of_from(search, key, pos)
|
||||
if k < 0 { break }
|
||||
local ds = search.substring(k + key.size(), search.size())
|
||||
// read consecutive digits as number
|
||||
local i = 0
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = ds.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out != "" { if first == "" { first = out } else { second = out } }
|
||||
if second != "" { break }
|
||||
pos = k + key.size() + i
|
||||
}
|
||||
if first != "" && second != "" {
|
||||
local lv = 0
|
||||
local rv = 0
|
||||
// simple to_i64
|
||||
local i0 = 0
|
||||
loop(i0 < first.size()) { lv = lv * 10 + ("0123456789".indexOf(first.substring(i0,i0+1))) i0 = i0 + 1 }
|
||||
local i1 = 0
|
||||
loop(i1 < second.size()) { rv = rv * 10 + ("0123456789".indexOf(second.substring(i1,i1+1))) i1 = i1 + 1 }
|
||||
// cmp: parse cmp op
|
||||
local cmp_key = "\"cmp\":\""
|
||||
local pk = inst_seg.indexOf(cmp_key)
|
||||
local cmp = "Eq"
|
||||
if pk >= 0 {
|
||||
local i = pk + cmp_key.size()
|
||||
local j = StringOps.index_of_from(inst_seg, "\"", i)
|
||||
if j > i { cmp = inst_seg.substring(i, j) }
|
||||
}
|
||||
local cv = CompareOpsBox.eval(cmp, lv, rv)
|
||||
// ret id
|
||||
local rstart4 = inst_seg.indexOf("\"op\":\"ret\"")
|
||||
local rseg4 = inst_seg.substring(rstart4, inst_seg.size())
|
||||
local rid4 = JsonFragBox.get_int(rseg4, "value")
|
||||
if rid4 != null { return cv }
|
||||
}
|
||||
}
|
||||
{
|
||||
local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val)
|
||||
if r != null {
|
||||
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
|
||||
return r
|
||||
}
|
||||
}
|
||||
// Final fallback: return first const (dst=1) if present
|
||||
local first = me._load_reg(regs, 1)
|
||||
return first
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
165
lang/src/vm/boxes/op_handlers.hako
Normal file
165
lang/src/vm/boxes/op_handlers.hako
Normal file
@ -0,0 +1,165 @@
|
||||
// op_handlers.hako — OpHandlersBox
|
||||
// Minimal handlers for const/compare/ret etc. with loose JSON key parsing.
|
||||
using "lang/src/vm/boxes/arithmetic.hako" as ArithmeticBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
// Prefer logical module names to avoid file-path using in strict profiles
|
||||
static box OpHandlersBox {
|
||||
_tprint(msg) {
|
||||
// Only emit hard errors by default; avoid env dependencies in Mini‑VM
|
||||
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
|
||||
}
|
||||
_is_numeric_str(s) { return StringHelpers.is_numeric_str(s) }
|
||||
|
||||
_str_to_int(s) { return JsonFragBox._str_to_int(s) }
|
||||
|
||||
// --- Safe decimal arithmetic on strings (non-negative integers only) ---
|
||||
|
||||
_find_int_in(seg, keypat) {
|
||||
local p = seg.indexOf(keypat)
|
||||
if p < 0 { return null }
|
||||
p = p + keypat.size()
|
||||
local i = p
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { i = i + 1 continue }
|
||||
if ch == "-" { out = out + ch i = i + 1 continue }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out == "" { return null }
|
||||
return me._str_to_int(out)
|
||||
}
|
||||
|
||||
_find_str_in(seg, keypat) {
|
||||
local p = seg.indexOf(keypat)
|
||||
if p < 0 { return "" }
|
||||
p = p + keypat.size()
|
||||
// Use substring to work around indexOf not supporting start position
|
||||
local rest = seg.substring(p, seg.size())
|
||||
local q = rest.indexOf("\"")
|
||||
if q < 0 { return "" }
|
||||
return rest.substring(0, q)
|
||||
}
|
||||
|
||||
_find_kv_int(seg, key) {
|
||||
// pattern: "key":<num>
|
||||
local pat = "\"" + key + "\":"
|
||||
return me._find_int_in(seg, pat)
|
||||
}
|
||||
|
||||
_find_kv_str(seg, key) {
|
||||
// pattern: "key":"..."
|
||||
local pat = "\"" + key + "\":\""
|
||||
return me._find_str_in(seg, pat)
|
||||
}
|
||||
|
||||
_load_reg(regs, id) {
|
||||
local v = regs.getField("" + id)
|
||||
if v == null { return 0 }
|
||||
local s = "" + v
|
||||
if me._is_numeric_str(s) == 1 { return me._str_to_int(s) }
|
||||
return 0
|
||||
}
|
||||
|
||||
handle_const(seg, regs) {
|
||||
// Prefer internal scanner to avoid resolver differences in strict profiles
|
||||
local dst = me._find_kv_int(seg, "dst")
|
||||
if dst == null { return }
|
||||
// Try direct value first
|
||||
local val = me._find_kv_int(seg, "value")
|
||||
if val == null {
|
||||
// Nested pattern (i64): "value":{"type":"i64","value":N}
|
||||
local pat_i64 = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||
local p = seg.indexOf(pat_i64)
|
||||
if p >= 0 {
|
||||
// Minimal digit read (inline)
|
||||
local i = p + pat_i64.size()
|
||||
local out = ""
|
||||
loop(true) {
|
||||
local ch = seg.substring(i, i+1)
|
||||
if ch == "" { break }
|
||||
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
|
||||
}
|
||||
if out != "" { val = me._str_to_int(out) }
|
||||
}
|
||||
}
|
||||
if val != null {
|
||||
regs.setField("" + dst, val)
|
||||
return
|
||||
}
|
||||
// String literal support: "value":{"type":"string","value":"..."}
|
||||
{
|
||||
local pat_str = "\"value\":{\"type\":\"string\",\"value\":\""
|
||||
local ps = seg.indexOf(pat_str)
|
||||
if ps >= 0 {
|
||||
local start = ps + pat_str.size()
|
||||
// Use escape-aware scanner to find the string end at the matching quote
|
||||
local vend = JsonCursorBox.scan_string_end(seg, start - 1)
|
||||
if vend > start {
|
||||
local s = seg.substring(start, vend)
|
||||
regs.setField("" + dst, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: direct string value pattern "value":"..."
|
||||
{
|
||||
local pat_v = "\"value\":\""
|
||||
local pv = seg.indexOf(pat_v)
|
||||
if pv >= 0 {
|
||||
local start = pv + pat_v.size()
|
||||
local vend = JsonCursorBox.scan_string_end(seg, start - 1)
|
||||
if vend > start {
|
||||
local s2 = seg.substring(start, vend)
|
||||
regs.setField("" + dst, s2)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default when nothing matched
|
||||
regs.setField("" + dst, 0)
|
||||
}
|
||||
|
||||
handle_compare(seg, regs) {
|
||||
local kind = me._find_kv_str(seg, "cmp")
|
||||
if kind == "" {
|
||||
local sym = me._find_kv_str(seg, "operation")
|
||||
if sym == "" {
|
||||
print("[ERROR] Missing key: cmp")
|
||||
return
|
||||
}
|
||||
kind = CompareOpsBox.map_symbol(sym)
|
||||
}
|
||||
local lhs = me._find_kv_int(seg, "lhs")
|
||||
local rhs = me._find_kv_int(seg, "rhs")
|
||||
local dst = me._find_kv_int(seg, "dst")
|
||||
if lhs == null { print("[ERROR] Missing key: lhs") return }
|
||||
if rhs == null { print("[ERROR] Missing key: rhs") return }
|
||||
if dst == null { print("[ERROR] Missing key: dst") return }
|
||||
local a = me._load_reg(regs, lhs)
|
||||
local b = me._load_reg(regs, rhs)
|
||||
local r = CompareOpsBox.eval(kind, a, b)
|
||||
// Store as numeric string to simplify downstream _load_reg parsing
|
||||
regs.setField("" + dst, "" + r)
|
||||
}
|
||||
|
||||
handle_binop(seg, regs) {
|
||||
// Minimal Add/Sub/Mul/Div/Mod for selfhost Mini‑VM tests
|
||||
local kind = JsonFragBox.get_str_strict(seg, "op_kind")
|
||||
local lhs = JsonFragBox.get_int_strict(seg, "lhs")
|
||||
local rhs = JsonFragBox.get_int_strict(seg, "rhs")
|
||||
local dst = JsonFragBox.get_int_strict(seg, "dst")
|
||||
if lhs == null || rhs == null || dst == null { return }
|
||||
local a = me._load_reg(regs, lhs)
|
||||
local b = me._load_reg(regs, rhs)
|
||||
if kind == "Add" { regs.setField(""+dst, ArithmeticBox.add_i64(a, b)) }
|
||||
else if kind == "Sub" { regs.setField(""+dst, ArithmeticBox.sub_i64(a, b)) }
|
||||
else if kind == "Mul" { regs.setField(""+dst, ArithmeticBox.mul_i64(a, b)) }
|
||||
else if kind == "Div" { if b == 0 { regs.setField(""+dst, 0) } else { regs.setField(""+dst, a / b) } }
|
||||
else if kind == "Mod" { if b == 0 { regs.setField(""+dst, 0) } else { regs.setField(""+dst, a % b) } }
|
||||
}
|
||||
}
|
||||
36
lang/src/vm/boxes/operator_box.hako
Normal file
36
lang/src/vm/boxes/operator_box.hako
Normal file
@ -0,0 +1,36 @@
|
||||
// operator_box.hako — OperatorBox (debug/parity for Mini‑VM)
|
||||
// Responsibility: Provide Compare/Arithmetic/Unary helpers behind a clean API
|
||||
// for self‑hosted (Ny) components. Intended for debugging and parity checks.
|
||||
// Non‑responsibility: Being called from the Rust VM runtime (non‑reentry policy).
|
||||
|
||||
using "lang/src/vm/boxes/arithmetic.hako" as ArithmeticBox
|
||||
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
|
||||
|
||||
static box OperatorBox {
|
||||
// Binary operators on integers (minimal set for Mini‑VM parity)
|
||||
apply2(kind, a, b) {
|
||||
if kind == "Add" { return ArithmeticBox.add_i64(a, b) }
|
||||
if kind == "Sub" { return ArithmeticBox.sub_i64(a, b) }
|
||||
if kind == "Mul" { return ArithmeticBox.mul_i64(a, b) }
|
||||
if kind == "Div" { if b == 0 { return 0 } else { return a / b } }
|
||||
if kind == "Mod" { if b == 0 { return 0 } else { return a % b } }
|
||||
// Bit ops (optional)
|
||||
if kind == "BitAnd" { return a & b }
|
||||
if kind == "BitOr" { return a | b }
|
||||
if kind == "BitXor" { return a ^ b }
|
||||
if kind == "Shl" { return a << b }
|
||||
if kind == "Shr" { return a >> b }
|
||||
return 0
|
||||
}
|
||||
|
||||
// Unary operators on integers/bools (minimal set)
|
||||
unary(kind, a) {
|
||||
if kind == "Neg" { return 0 - a }
|
||||
if kind == "Not" { return (a == 0) ? 1 : 0 }
|
||||
if kind == "BitNot" { return ~a }
|
||||
return a
|
||||
}
|
||||
|
||||
// Compare returns 0/1
|
||||
compare(kind, a, b) { return CompareOpsBox.eval(kind, a, b) }
|
||||
}
|
||||
18
lang/src/vm/boxes/phi_apply_box.hako
Normal file
18
lang/src/vm/boxes/phi_apply_box.hako
Normal file
@ -0,0 +1,18 @@
|
||||
// phi_apply_box.hako — PhiApplyBox
|
||||
// 責務: φ の適用(dst レジスタに vin の値をロードして書き込む)
|
||||
// 非責務: φ のデコードやスキャン(呼び出し元で行う)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
|
||||
static box PhiApplyBox {
|
||||
// 内部: 文字列が整数表現かを緩く判定
|
||||
_is_numeric_str(s){ if s==null {return 0} return StringHelpers.is_numeric_str(s) }
|
||||
_str_to_int(s){ return StringHelpers.to_i64(s) }
|
||||
_load_reg(regs,id){ local v=regs.get(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return me._str_to_int(s) } return 0 }
|
||||
|
||||
apply(dst, vin, regs) {
|
||||
if dst == null || vin == null { return }
|
||||
local vv = me._load_reg(regs, vin)
|
||||
regs.set(""+dst, vv)
|
||||
}
|
||||
}
|
||||
123
lang/src/vm/boxes/phi_decode_box.hako
Normal file
123
lang/src/vm/boxes/phi_decode_box.hako
Normal file
@ -0,0 +1,123 @@
|
||||
// phi_decode_box.hako — PhiDecodeBox
|
||||
// Responsibility: decode a minimal PHI instruction segment from JSON v0 text.
|
||||
// Input: seg (string for one instruction object), prev_bb (i64 of predecessor bb)
|
||||
// Output: [dst:int, vin:int] when resolvable; otherwise [null,null]
|
||||
// Non-goals: MIR execution, register I/O (caller applies values), full JSON parser.
|
||||
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
using "lang/src/vm/boxes/result_box.hako" as Result
|
||||
|
||||
static box PhiDecodeBox {
|
||||
// tiny helpers to avoid raw quote/backslash sequences in source (prelude‑safe)
|
||||
_dq() { return "\"" }
|
||||
_lb() { return "{" }
|
||||
_rb() { return "}" }
|
||||
_lsq() { return "[" }
|
||||
_rsq() { return "]" }
|
||||
// Decode single incoming (legacy/simple form): {"op":"phi","dst":D,"pred":P,"value":V}
|
||||
_decode_single(seg) {
|
||||
local dst = JsonFragBox.get_int(seg, "dst")
|
||||
local pred = JsonFragBox.get_int(seg, "pred")
|
||||
local vin = JsonFragBox.get_int(seg, "value")
|
||||
// single-form requires dst and value
|
||||
if dst == null { return { type: "err", code: "phi:invalid-object:dst-missing" } }
|
||||
if vin == null { return { type: "err", code: "phi:invalid-object:value-missing" } }
|
||||
local out = new ArrayBox()
|
||||
out.push(dst)
|
||||
out.push(vin)
|
||||
// pred may be null in simple form
|
||||
return { type: "ok", pair: out, pred: pred }
|
||||
}
|
||||
|
||||
// Decode array incoming form: {"op":"phi","dst":D, "values":[ {"pred":P,"value":V}, ... ]}
|
||||
_decode_array(seg, prev_bb) {
|
||||
// Find start of values array
|
||||
local key = me._dq()+"values"+me._dq()+":"+me._lsq()
|
||||
local p = seg.indexOf(key)
|
||||
if p < 0 { return null }
|
||||
local arr_br = p + key.size() - 1 // points at '['
|
||||
local i = arr_br + 1
|
||||
local endp = JsonCursorBox.seek_array_end(seg, arr_br)
|
||||
local n = seg.size()
|
||||
if endp >= 0 { n = endp }
|
||||
local best_dst = JsonFragBox.get_int(seg, "dst")
|
||||
if best_dst == null { return { type: "err", code: "phi:invalid-object:dst-missing" } }
|
||||
// Iterate objects inside values array; pick first matching pred==prev_bb, else remember the first
|
||||
local chosen_v = null
|
||||
local fallback_v = null
|
||||
local valid_cnt = 0
|
||||
local invalid_cnt = 0
|
||||
local guard = 0
|
||||
local elem_cnt = 0
|
||||
loop(i < n) {
|
||||
guard = guard + 1
|
||||
if guard > 512 { break }
|
||||
// seek next object head '{'
|
||||
local ob = JsonFragBox.index_of_from(seg, me._lb(), i)
|
||||
if ob < 0 || ob >= n { break }
|
||||
local ob_end = JsonCursorBox.seek_obj_end(seg, ob)
|
||||
if ob_end < 0 || ob_end > n { invalid_cnt = invalid_cnt + 1 break }
|
||||
elem_cnt = elem_cnt + 1
|
||||
local obj = seg.substring(ob, ob_end+1)
|
||||
// read pred/value from object segment
|
||||
local pred = JsonFragBox.get_int(obj, "pred")
|
||||
local vin = JsonFragBox.get_int(obj, "value")
|
||||
if vin != null {
|
||||
valid_cnt = valid_cnt + 1
|
||||
if fallback_v == null { fallback_v = vin }
|
||||
if pred != null && prev_bb != null && pred == prev_bb { chosen_v = vin i = ob_end + 1 break }
|
||||
} else {
|
||||
invalid_cnt = invalid_cnt + 1
|
||||
}
|
||||
i = ob_end + 1
|
||||
}
|
||||
local vin2 = chosen_v
|
||||
if vin2 == null { vin2 = fallback_v }
|
||||
if vin2 == null {
|
||||
// values[] present but no valid entries
|
||||
if elem_cnt == 0 { return { type: "err", code: "phi:no-values:empty" } }
|
||||
if valid_cnt == 0 { return { type: "err", code: "phi:no-values:all-malformed" } }
|
||||
return { type: "err", code: "phi:no-values" }
|
||||
}
|
||||
local pair = new ArrayBox()
|
||||
pair.push(best_dst)
|
||||
pair.push(vin2)
|
||||
return { type: "ok", pair: pair, pred: prev_bb }
|
||||
}
|
||||
|
||||
// Public: decode seg into [dst, vin] using prev_bb when provided
|
||||
decode(seg, prev_bb) {
|
||||
// Prefer array form; fallback to single form
|
||||
local arr = me._decode_array(seg, prev_bb)
|
||||
if arr != null {
|
||||
if arr.get != null { return arr.get("pair") }
|
||||
// If arr is an error map, propagate by returning as-is
|
||||
return arr
|
||||
}
|
||||
local one = me._decode_single(seg)
|
||||
if one != null {
|
||||
if one.get != null { return one.get("pair") }
|
||||
return one
|
||||
}
|
||||
local out = new ArrayBox()
|
||||
out.push(null)
|
||||
out.push(null)
|
||||
return out
|
||||
}
|
||||
|
||||
// Public: decode seg and wrap into Result.Ok([dst,vin]) or Result.Err(msg)
|
||||
decode_result(seg, prev_bb) {
|
||||
// Prefer array-form; if not present, fall back to single-form
|
||||
local arr = me._decode_array(seg, prev_bb)
|
||||
if arr != null {
|
||||
local typ = arr.get("type")
|
||||
if typ == "ok" { return Result.Ok(arr.get("pair")) }
|
||||
return Result.Err(arr.get("code"))
|
||||
}
|
||||
local one = me._decode_single(seg)
|
||||
local typ2 = one.get("type")
|
||||
if typ2 == "ok" { return Result.Ok(one.get("pair")) }
|
||||
return Result.Err(one.get("code"))
|
||||
}
|
||||
}
|
||||
29
lang/src/vm/boxes/release_manager.hako
Normal file
29
lang/src/vm/boxes/release_manager.hako
Normal file
@ -0,0 +1,29 @@
|
||||
// ReleaseManagerBox — explicit release for identity/extern/user instances
|
||||
// Responsibility: Provide a stable Nyash API to finalize boxes explicitly.
|
||||
// Implementation: delegates to extern env.runtime.release (best‑effort)
|
||||
|
||||
box ReleaseManagerBox {
|
||||
release(obj) {
|
||||
// Call through runtime externs; no return value
|
||||
env.runtime.release(obj)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Release all objects in an array (best‑effort)
|
||||
release_many(arr) {
|
||||
if arr == null { return 0 }
|
||||
// Defensive: tolerate both ArrayBox and array-like values
|
||||
local n = 0
|
||||
if arr.length != null { n = arr.size() } else { return 0 }
|
||||
local i = 0
|
||||
loop(i < n) {
|
||||
local v = arr.get(i)
|
||||
env.runtime.release(v)
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
static box ReleaseManagerStub { main(args) { return 0 } }
|
||||
|
||||
8
lang/src/vm/boxes/result_box.hako
Normal file
8
lang/src/vm/boxes/result_box.hako
Normal file
@ -0,0 +1,8 @@
|
||||
// result_box.hako — Result / ResultBox
|
||||
// 責務: 処理結果の統一表現(成功値 or エラーメッセージ)
|
||||
// 使い方: Result.Ok(val) / Result.Err(msg) → ResultBox
|
||||
|
||||
@enum Result {
|
||||
Ok(value)
|
||||
Err(error)
|
||||
}
|
||||
21
lang/src/vm/boxes/result_helpers.hako
Normal file
21
lang/src/vm/boxes/result_helpers.hako
Normal file
@ -0,0 +1,21 @@
|
||||
// result_helpers.hako — Module functions for Result enum (introspection without ArrayBox)
|
||||
// Responsibility: expose stable module functions for smokes and app code
|
||||
// ResultHelpers.is_Ok(r: EnumBox) -> 0|1
|
||||
// ResultHelpers.is_Err(r: EnumBox) -> 0|1
|
||||
// Implementation detail: uses EnumBox.tag() which is supported by the router
|
||||
|
||||
static box ResultHelpers {
|
||||
is_Ok(r) {
|
||||
if r == null { return 0 }
|
||||
// InstanceBox-based enum (macro): tag is stored in field "_tag"
|
||||
local t = r.getField("_tag")
|
||||
if t == "Ok" { return 1 }
|
||||
return 0
|
||||
}
|
||||
is_Err(r) {
|
||||
if r == null { return 0 }
|
||||
local t = r.getField("_tag")
|
||||
if t == "Err" { return 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
47
lang/src/vm/boxes/ret_resolve_simple.hako
Normal file
47
lang/src/vm/boxes/ret_resolve_simple.hako
Normal file
@ -0,0 +1,47 @@
|
||||
// ret_resolve_simple.hako — RetResolveSimpleBox
|
||||
// Responsibility: Resolve "ret" for a single block instruction segment robustly.
|
||||
// Inputs: inst_seg (string of objects), regs (MapBox), last_cmp_dst/val (ints)
|
||||
// Output: i64 value or null when no ret is present
|
||||
|
||||
using "lang/src/shared/json/utils/json_frag.hako" as JsonFragBox
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box RetResolveSimpleBox {
|
||||
_to_i64(s) { return StringHelpers.to_i64(s) }
|
||||
_load_reg(regs, id) {
|
||||
local v = regs.get("" + id)
|
||||
if v == null { return 0 }
|
||||
local s = "" + v
|
||||
if StringHelpers.is_numeric_str(s) == 1 { return me._to_i64(s) }
|
||||
return 0
|
||||
}
|
||||
// Try explicit op:"ret"
|
||||
_resolve_explicit(inst_seg, regs, last_cmp_dst, last_cmp_val) {
|
||||
local rpos = inst_seg.indexOf("\"op\":\"ret\"")
|
||||
if rpos < 0 { return null }
|
||||
local rseg = inst_seg.substring(rpos, inst_seg.size())
|
||||
local rid = JsonFragBox.get_int(rseg, "value")
|
||||
if rid == null { return null }
|
||||
if rid == last_cmp_dst { return last_cmp_val }
|
||||
return me._load_reg(regs, rid)
|
||||
}
|
||||
// Try v1-style {"kind":"Ret","value":N} (no op key)
|
||||
_resolve_kind(inst_seg, regs, last_cmp_dst, last_cmp_val) {
|
||||
local kpos = inst_seg.indexOf("\"kind\":\"Ret\"")
|
||||
if kpos < 0 { return null }
|
||||
local kseg = inst_seg.substring(kpos, inst_seg.size())
|
||||
local rid = JsonFragBox.get_int(kseg, "value")
|
||||
if rid == null { return null }
|
||||
if rid == last_cmp_dst { return last_cmp_val }
|
||||
return me._load_reg(regs, rid)
|
||||
}
|
||||
resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) {
|
||||
if inst_seg == null { return null }
|
||||
local v = me._resolve_explicit(inst_seg, regs, last_cmp_dst, last_cmp_val)
|
||||
if v != null { return v }
|
||||
return me._resolve_kind(inst_seg, regs, last_cmp_dst, last_cmp_val)
|
||||
}
|
||||
}
|
||||
|
||||
static box RetResolveSimpleMain { main(args){ return 0 } }
|
||||
44
lang/src/vm/boxes/rune_host.hako
Normal file
44
lang/src/vm/boxes/rune_host.hako
Normal file
@ -0,0 +1,44 @@
|
||||
// RuneHostBox — thin placeholder for future rune integration (selfhost)
|
||||
// Responsibility: provide a stable interface; default is disabled (Fail‑Fast)
|
||||
|
||||
static box RuneHostBox {
|
||||
// Return 1 when rune is available in this build (default: 0)
|
||||
is_available() { return 0 }
|
||||
|
||||
// Provider name (mock/wasm/…); default none
|
||||
provider_name() { return "none" }
|
||||
|
||||
// Evaluate a small rune 'code' with optional context map.
|
||||
// Calls extern nyrt.rune.eval; when disabled it returns -1 (handled by extern side).
|
||||
eval(code, ctx) {
|
||||
// Keep behavior explicit and side‑effect free (no prints here).
|
||||
// Delegate to extern; context is reserved for future providers.
|
||||
local _ = ctx
|
||||
// Try extern via env facade (builder maps to nyrt.rune.eval) — may be unavailable in some profiles
|
||||
// Fallback: tiny mock evaluator (supports "A+B" and integer literal)
|
||||
// Note: no try/catch yet; keep deterministic and side‑effect free
|
||||
// Extern path (best-effort)
|
||||
// Disabled until builder guarantees env facade in all profiles
|
||||
// local rc = env.rune.eval(code)
|
||||
// return rc
|
||||
// Fallback mock: only support literal "1+2" and simple integers for now
|
||||
if code == "1+2" { return 3 }
|
||||
// else try simple integer literal (only digits)
|
||||
local i = 0
|
||||
local ok = 1
|
||||
loop(i < code.size()) { local ch = code.substring(i,i+1) if !(ch == "0" or ch == "1" or ch == "2" or ch == "3" or ch == "4" or ch == "5" or ch == "6" or ch == "7" or ch == "8" or ch == "9") { ok = 0 } i = i + 1 }
|
||||
if ok == 1 {
|
||||
// very rough: build integer by repeated add (limited)
|
||||
local n = 0
|
||||
i = 0
|
||||
loop(i < code.size()) {
|
||||
n = n * 10
|
||||
local ch2 = code.substring(i,i+1)
|
||||
if ch2 == "1" { n = n + 1 } else { if ch2 == "2" { n = n + 2 } else { if ch2 == "3" { n = n + 3 } else { if ch2 == "4" { n = n + 4 } else { if ch2 == "5" { n = n + 5 } else { if ch2 == "6" { n = n + 6 } else { if ch2 == "7" { n = n + 7 } else { if ch2 == "8" { n = n + 8 } else { if ch2 == "9" { n = n + 9 } } } } } } } }
|
||||
i = i + 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
return -2
|
||||
}
|
||||
}
|
||||
39
lang/src/vm/boxes/scanner_box.hako
Normal file
39
lang/src/vm/boxes/scanner_box.hako
Normal file
@ -0,0 +1,39 @@
|
||||
// scanner_box.hako — ScannerBox
|
||||
// 責務: 文字列の安全な逐次走査(境界チェックと前進保証のカプセル化)
|
||||
// 非責務: JSON構文解析や高レベルのトークナイズ(上位箱に委譲)
|
||||
|
||||
box ScannerBox {
|
||||
_src: StringBox
|
||||
_pos: IntegerBox
|
||||
_max: IntegerBox
|
||||
|
||||
birth(source) {
|
||||
me._src = source
|
||||
me._pos = 0
|
||||
me._max = 0
|
||||
if source != null { me._max = source.size() }
|
||||
}
|
||||
|
||||
at_end() {
|
||||
if me._pos >= me._max { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
pos() { return me._pos }
|
||||
|
||||
// 先読み(前進しない)。末尾なら null
|
||||
peek() {
|
||||
if me._pos >= me._max { return null }
|
||||
return me._src.substring(me._pos, me._pos + 1)
|
||||
}
|
||||
|
||||
// 1文字進めて、直前の文字を返す。末尾なら null(Fail‑Fastは呼び出し側で制御)
|
||||
advance() {
|
||||
if me._pos >= me._max { return null }
|
||||
local old = me._pos
|
||||
me._pos = me._pos + 1
|
||||
if me._pos <= old { return null }
|
||||
return me._src.substring(old, old + 1)
|
||||
}
|
||||
}
|
||||
|
||||
170
lang/src/vm/boxes/seam_inspector.hako
Normal file
170
lang/src/vm/boxes/seam_inspector.hako
Normal file
@ -0,0 +1,170 @@
|
||||
// SeamInspector — analyze inlined code seam and duplicates
|
||||
// Usage: import and call report(text) or analyze_dump_file(path)
|
||||
|
||||
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
|
||||
using "lang/src/shared/common/string_ops.hako" as StringOps
|
||||
using "lang/src/shared/common/mini_vm_scan.hako" as MiniVmScan
|
||||
|
||||
static box SeamInspector {
|
||||
_int_to_str(n) { return StringHelpers.int_to_str(n) }
|
||||
_str_to_int(s) { return StringHelpers.to_i64(s) }
|
||||
_brace_delta_ignoring_strings(text, start, end) {
|
||||
@i = start
|
||||
@n = end
|
||||
@delta = 0
|
||||
loop (i < n) {
|
||||
@ch = call("String.substring/2", text, i, i+1)
|
||||
if ch == "\"" {
|
||||
i = i + 1
|
||||
loop (i < n) {
|
||||
@c = text.substring(i, i+1)
|
||||
if c == "\\" { i = i + 2 continue }
|
||||
if c == "\"" { i = i + 1 break }
|
||||
i = i + 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ch == "{" { delta = delta + 1 }
|
||||
if ch == "}" { delta = delta - 1 }
|
||||
i = i + 1
|
||||
}
|
||||
return delta
|
||||
}
|
||||
_scan_boxes(text) {
|
||||
@i = 0
|
||||
@n = text.size()
|
||||
@res = new ArrayBox()
|
||||
@tok = "static box "
|
||||
loop (i < n) {
|
||||
@p = StringOps.index_of_from(text, tok, i)
|
||||
if p < 0 { break }
|
||||
@j = p + tok.size()
|
||||
@name = ""
|
||||
loop (j < n) {
|
||||
@c = text.substring(j, j+1)
|
||||
if c == "_" { name = name + c j = j + 1 continue }
|
||||
if c == "0" or c == "1" or c == "2" or c == "3" or c == "4" or c == "5" or c == "6" or c == "7" or c == "8" or c == "9" { name = name + c j = j + 1 continue }
|
||||
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c j = j + 1 continue }
|
||||
break
|
||||
}
|
||||
loop (j < n) { @c2 = text.substring(j, j+1) if c2 == "{" { break } j = j + 1 }
|
||||
@end = MiniVmScan.find_balanced_object_end(text, j)
|
||||
if end < 0 { end = j }
|
||||
@obj = map({})
|
||||
obj.set("name", name)
|
||||
obj.set("start", _int_to_str(p))
|
||||
obj.set("end", _int_to_str(end))
|
||||
res.push(obj)
|
||||
i = end + 1
|
||||
}
|
||||
return res
|
||||
}
|
||||
_report_duplicate_boxes(text) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@cnt = map({})
|
||||
@names = new ArrayBox()
|
||||
@i = 0
|
||||
loop (i < boxes.size()) {
|
||||
@name = boxes.get(i).get("name")
|
||||
@cur = cnt.get(name)
|
||||
if cur == null { cnt.set(name, "1") names.push(name) } else { cnt.set(name, _int_to_str(_str_to_int(cur) + 1)) }
|
||||
i = i + 1
|
||||
}
|
||||
@j = 0
|
||||
loop (j < names.size()) {
|
||||
@k = names.get(j)
|
||||
@v = cnt.get(k)
|
||||
if _str_to_int(v) > 1 { print("dup_box " + k + " x" + v) }
|
||||
j = j + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
_report_duplicate_functions_in_box(text, box_name) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@i = 0
|
||||
@fnmap = map({})
|
||||
@fnames = new ArrayBox()
|
||||
loop (i < boxes.size()) {
|
||||
@b = boxes.get(i)
|
||||
if b.get("name") == box_name {
|
||||
@s = _str_to_int(b.get("start"))
|
||||
@e = _str_to_int(b.get("end"))
|
||||
@j = s
|
||||
loop (j < e) {
|
||||
@k = j
|
||||
loop (k < e) {
|
||||
@ch = text.substring(k, k+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { k = k + 1 continue }
|
||||
break
|
||||
}
|
||||
if k >= e { break }
|
||||
@name = ""
|
||||
@p = k
|
||||
@c0 = text.substring(p, p+1)
|
||||
if (c0 >= "A" and c0 <= "Z") or (c0 >= "a" and c0 <= "z") or c0 == "_" {
|
||||
loop (p < e) {
|
||||
@c = text.substring(p, p+1)
|
||||
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or c == "_" { name = name + c p = p + 1 continue }
|
||||
break
|
||||
}
|
||||
if text.substring(p, p+1) == "(" {
|
||||
@d = 0
|
||||
@r = p
|
||||
loop (r < e) {
|
||||
@cc = text.substring(r, r+1)
|
||||
if cc == "(" { d = d + 1 r = r + 1 continue }
|
||||
if cc == ")" { d = d - 1 r = r + 1 if d <= 0 { break } continue }
|
||||
if cc == "\"" {
|
||||
r = r + 1
|
||||
loop (r < e) {
|
||||
@c2 = text.substring(r, r+1)
|
||||
if c2 == "\\" { r = r + 2 continue }
|
||||
if c2 == "\"" { r = r + 1 break }
|
||||
r = r + 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
r = r + 1
|
||||
}
|
||||
loop (r < e) { @ws = text.substring(r, r+1) if ws == " " or ws == "\t" or ws == "\r" or ws == "\n" { r = r + 1 continue } break }
|
||||
if r < e and text.substring(r, r+1) == "{" {
|
||||
@cur = fnmap.get(name)
|
||||
if cur == null { fnmap.set(name, "1") fnames.push(name) } else { fnmap.set(name, _int_to_str(_str_to_int(cur) + 1)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@nl = StringOps.index_of_from(text, "\n", k+1)
|
||||
if nl < 0 || nl > e { break }
|
||||
j = nl + 1
|
||||
}
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
@x = 0
|
||||
loop (x < fnames.size()) {
|
||||
@nm = fnames.get(x)
|
||||
@ct = fnmap.get(nm)
|
||||
if _str_to_int(ct) > 1 { print("dup_fn " + box_name + "." + nm + " x" + ct) }
|
||||
x = x + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
report(text) {
|
||||
@m = StringOps.index_of_from(text, "static box Main {", 0)
|
||||
@delta = -9999
|
||||
if m > 0 { delta = _brace_delta_ignoring_strings(text, 0, m) }
|
||||
print("prelude_brace_delta=" + _int_to_str(delta))
|
||||
_report_duplicate_boxes(text)
|
||||
_report_duplicate_functions_in_box(text, "MiniVmPrints")
|
||||
return 0
|
||||
}
|
||||
analyze_dump_file(path) {
|
||||
@fb = new FileBox()
|
||||
@f = fb.open(path)
|
||||
if f == null { print("warn: cannot open " + path) return 0 }
|
||||
@text = f.read()
|
||||
f.close()
|
||||
return me.report(text)
|
||||
}
|
||||
}
|
||||
67
lang/src/vm/boxes/step_runner.hako
Normal file
67
lang/src/vm/boxes/step_runner.hako
Normal file
@ -0,0 +1,67 @@
|
||||
// step_runner.hako — Mini‑VM JSON v0 ステップ観測用の軽量箱(実行はしない)
|
||||
using "lang/src/vm/boxes/compare_ops.hako" as CompareOpsBox
|
||||
using "lang/src/shared/json/json_cursor.hako" as JsonCursorBox
|
||||
|
||||
static box StepRunnerBox {
|
||||
// 文字列ヘルパ
|
||||
// 文字列ヘルパ(JsonCursorBox に委譲)
|
||||
_index_of_from(hay, needle, pos) { return JsonCursorBox.index_of_from(hay, needle, pos) }
|
||||
// Delegate digit scanning to JsonCursorBox for consistency
|
||||
_read_digits(text, pos) { return JsonCursorBox.digits_from(text, pos) }
|
||||
_int(s) { local t = "" + s if t == "" { return 0 } local i = 0 local neg = 0 if t.substring(0,1) == "-" { neg = 1 i = 1 } local acc = 0 loop(i < t.size()) { local ch = t.substring(i, i+1) if ch < "0" || ch > "9" { break } acc = acc * 10 + (ch == "0" ? 0 : ch == "1" ? 1 : ch == "2" ? 2 : ch == "3" ? 3 : ch == "4" ? 4 : ch == "5" ? 5 : ch == "6" ? 6 : ch == "7" ? 7 : ch == "8" ? 8 : 9) i = i + 1 } if neg == 1 { acc = 0 - acc } return acc }
|
||||
|
||||
// block 0 の instructions セグメント抽出
|
||||
_block0_segment(mjson) {
|
||||
local key = "\"instructions\":["
|
||||
local k = me._index_of_from(mjson, key, 0)
|
||||
if k < 0 { return "" }
|
||||
local lb = k + key.size() - 1
|
||||
// Delegate to JsonScanBox for robust end detection (escape-aware)
|
||||
local rb = JsonCursorBox.seek_array_end(mjson, lb)
|
||||
if rb < 0 { return "" }
|
||||
return mjson.substring(lb + 1, rb)
|
||||
}
|
||||
|
||||
// compare の (lhs,rhs,kind,dst) を抽出
|
||||
parse_compare(seg) {
|
||||
local p = JsonCursorBox.index_of_from(seg, "\"op\":\"compare\"", 0)
|
||||
if p < 0 { return map({}) }
|
||||
local lhs = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"lhs\":", p) + 6))
|
||||
local rhs = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"rhs\":", p) + 6))
|
||||
local dst = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"dst\":", p) + 6))
|
||||
// kind
|
||||
local kpos = JsonCursorBox.index_of_from(seg, "\"cmp\":\"", p)
|
||||
local kend = me._index_of_from(seg, "\"", kpos + 6)
|
||||
local kind = seg.substring(kpos + 6, kend)
|
||||
return map({ lhs: lhs, rhs: rhs, dst: dst, kind: kind })
|
||||
}
|
||||
|
||||
// branch の cond/then/else を抽出
|
||||
parse_branch(seg) {
|
||||
local p = JsonCursorBox.index_of_from(seg, "\"op\":\"branch\"", 0)
|
||||
if p < 0 { return map({}) }
|
||||
local cond = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"cond\":", p) + 7))
|
||||
local then_id = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"then\":", p) + 7))
|
||||
local else_id = me._int(me._read_digits(seg, JsonCursorBox.index_of_from(seg, "\"else\":", p) + 7))
|
||||
return map({ cond: cond, then: then_id, else: else_id })
|
||||
}
|
||||
|
||||
// compare を評価し cond==dst のときに bool を返す
|
||||
eval_branch_bool(mjson) {
|
||||
local seg = me._block0_segment(mjson)
|
||||
if seg == "" { return 0 }
|
||||
local cmp = me.parse_compare(seg)
|
||||
local br = me.parse_branch(seg)
|
||||
if cmp.get == null || br.get == null { return 0 }
|
||||
local kind = "" + cmp.get("kind")
|
||||
local lhs = cmp.get("lhs")
|
||||
local rhs = cmp.get("rhs")
|
||||
local dst = cmp.get("dst")
|
||||
local cond = br.get("cond")
|
||||
if cond != dst { return 0 } // 最小仕様: cond は直前 compare の dst
|
||||
local r = CompareOpsBox.eval(kind, lhs, rhs)
|
||||
return r
|
||||
}
|
||||
|
||||
main(args) { return 0 }
|
||||
}
|
||||
32
lang/src/vm/boxes/vm_kernel_box.hako
Normal file
32
lang/src/vm/boxes/vm_kernel_box.hako
Normal file
@ -0,0 +1,32 @@
|
||||
// vm_kernel_box.nyash — NYABI Kernel (skeleton, dev-only; not wired)
|
||||
// Scope: Provide policy/decision helpers behind an explicit OFF toggle.
|
||||
// Notes: This box is not referenced by the VM by default.
|
||||
|
||||
static box VmKernelBox {
|
||||
// Report version and supported features.
|
||||
caps() {
|
||||
// v0 draft: features are informative only.
|
||||
return "{\"version\":0,\"features\":[\"policy\"]}"
|
||||
}
|
||||
|
||||
// Decide stringify strategy for a given type.
|
||||
// Returns: "direct" | "rewrite_stringify" | "fallback"
|
||||
stringify_policy(typeName) {
|
||||
if typeName == "VoidBox" { return "rewrite_stringify" }
|
||||
return "fallback"
|
||||
}
|
||||
|
||||
// Decide equals strategy for two types.
|
||||
// Returns: "object" | "value" | "fallback"
|
||||
equals_policy(lhsType, rhsType) {
|
||||
if lhsType == rhsType { return "value" }
|
||||
return "fallback"
|
||||
}
|
||||
|
||||
// Batch resolve method dispatch plans.
|
||||
// Input/Output via tiny JSON strings (draft). Returns "{\"plans\":[]}" for now.
|
||||
resolve_method_batch(reqs_json) {
|
||||
return "{\"plans\":[]}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user