selfhost(pyvm): MiniVmPrints – prefer JSON route early-return (ok==1) to avoid fallback loops; keep default behavior unchanged elsewhere
This commit is contained in:
249
apps/selfhost-vm/boxes/seam_inspector.nyash
Normal file
249
apps/selfhost-vm/boxes/seam_inspector.nyash
Normal file
@ -0,0 +1,249 @@
|
||||
// SeamInspector — analyze inlined code seam and duplicates
|
||||
// Usage: import and call report(text) or analyze_dump_file(path)
|
||||
|
||||
static box SeamInspector {
|
||||
// Count brace delta ignoring simple strings ("...") and escapes
|
||||
_brace_delta_ignoring_strings(text, start, end) {
|
||||
@i = start
|
||||
@n = end
|
||||
@delta = 0
|
||||
loop (i < n) {
|
||||
@ch = text.substring(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
|
||||
}
|
||||
|
||||
// Find index of exact token in plain text from position
|
||||
_index_of_from(h, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
@n = h.length()
|
||||
if pos >= n { return -1 }
|
||||
@m = needle.length()
|
||||
if m <= 0 { return pos }
|
||||
@i = pos
|
||||
@limit = n - m
|
||||
loop (i <= limit) {
|
||||
if h.substring(i, i+m) == needle { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Find matching closing brace starting at '{'
|
||||
_find_balanced_object_end(text, idx) {
|
||||
if text.substring(idx, idx+1) != "{" { return -1 }
|
||||
@i = idx
|
||||
@n = text.length()
|
||||
@depth = 0
|
||||
loop (i < n) {
|
||||
@ch = text.substring(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 == "{" { depth = depth + 1 }
|
||||
if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Scan boxes: return array of {name, start, end}
|
||||
_scan_boxes(text) {
|
||||
@i = 0
|
||||
@n = text.length()
|
||||
@res = new ArrayBox()
|
||||
@tok = "static box "
|
||||
loop (i < n) {
|
||||
@p = _index_of_from(text, tok, i)
|
||||
if p < 0 { break }
|
||||
@j = p + tok.length()
|
||||
// read identifier
|
||||
@name = ""
|
||||
loop (j < n) {
|
||||
@c = text.substring(j, j+1)
|
||||
// ASCII alpha-num or '_'
|
||||
if c == "_" { name = name + c j = j + 1 continue }
|
||||
// digits
|
||||
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 }
|
||||
// letters
|
||||
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c j = j + 1 continue }
|
||||
break
|
||||
}
|
||||
// skip to '{'
|
||||
loop (j < n) {
|
||||
@c2 = text.substring(j, j+1)
|
||||
if c2 == "{" { break }
|
||||
j = j + 1
|
||||
}
|
||||
@end = _find_balanced_object_end(text, j)
|
||||
if end < 0 { end = j }
|
||||
@obj = new MapBox()
|
||||
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
|
||||
}
|
||||
|
||||
// Print duplicate boxes by name
|
||||
_report_duplicate_boxes(text) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@cnt = new MapBox()
|
||||
@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
|
||||
}
|
||||
|
||||
// Inside a given box, count function name duplicates (simple scan: name(...){ )
|
||||
_report_duplicate_functions_in_box(text, box_name) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@i = 0
|
||||
@fnmap = new MapBox()
|
||||
@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) {
|
||||
// find identifier start at line head-ish (naive)
|
||||
// pattern: <spaces> ident '(' ... '{'
|
||||
@k = j
|
||||
// skip spaces/newlines
|
||||
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 }
|
||||
// read ident
|
||||
@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
|
||||
}
|
||||
// must be a function definition: ident '(' ... ')' ws* '{'
|
||||
if text.substring(p, p+1) == "(" {
|
||||
// find matching ')' with a simple counter (strings ignored for simplicity)
|
||||
@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 == "\"" {
|
||||
// skip string inside params
|
||||
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
|
||||
}
|
||||
// skip ws
|
||||
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
|
||||
}
|
||||
// definition only if next is '{'
|
||||
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)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// advance to next line
|
||||
@nl = _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 summary
|
||||
report(text) {
|
||||
// find Main
|
||||
@m = _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))
|
||||
|
||||
// duplicate boxes
|
||||
_report_duplicate_boxes(text)
|
||||
|
||||
// specific hot-spot
|
||||
_report_duplicate_functions_in_box(text, "MiniVmPrints")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Load dump file and report
|
||||
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)
|
||||
}
|
||||
}
|
||||
59
apps/selfhost-vm/collect_empty_args_smoke.nyash
Normal file
59
apps/selfhost-vm/collect_empty_args_smoke.nyash
Normal file
@ -0,0 +1,59 @@
|
||||
// Self-contained dev smoke for FunctionCall empty-args
|
||||
// Goal: echo() -> empty line, itoa() -> 0
|
||||
|
||||
static box MiniVm {
|
||||
// simple substring find from position
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
}
|
||||
|
||||
// collect only FunctionCall with empty arguments [] for echo/itoa
|
||||
collect_prints(json) {
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
local guard = 0
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 16 { break }
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local p = index_of_from(json, k_fc, pos)
|
||||
if p < 0 { break }
|
||||
// name
|
||||
local k_n = "\"name\":\""
|
||||
local np = index_of_from(json, k_n, p)
|
||||
if np < 0 { break }
|
||||
local ni = np + k_n.length()
|
||||
local nj = index_of_from(json, "\"", ni)
|
||||
if nj < 0 { break }
|
||||
local fname = json.substring(ni, nj)
|
||||
// args [] detection
|
||||
local k_a = "\"arguments\":["
|
||||
local ap = index_of_from(json, k_a, nj)
|
||||
if ap < 0 { break }
|
||||
local rb = index_of_from(json, "]", ap)
|
||||
if rb < 0 { break }
|
||||
// no content between '[' and ']'
|
||||
if rb == ap + k_a.length() {
|
||||
if fname == "echo" { out.push("") }
|
||||
if fname == "itoa" { out.push("0") }
|
||||
}
|
||||
pos = rb + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
15
apps/selfhost-vm/collect_empty_args_using_smoke.nyash
Normal file
15
apps/selfhost-vm/collect_empty_args_using_smoke.nyash
Normal file
@ -0,0 +1,15 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":["
|
||||
+ "{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},"
|
||||
+ "{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
30
apps/selfhost-vm/collect_literal_eval.nyash
Normal file
30
apps/selfhost-vm/collect_literal_eval.nyash
Normal file
@ -0,0 +1,30 @@
|
||||
// Minimal self-contained eval (no using): collect the first Print(Literal int) and print it
|
||||
static box Main {
|
||||
// helper: find from position
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
}
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
// naive collect: first Print of Literal int
|
||||
local ki = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local p = json.indexOf(ki)
|
||||
if p >= 0 {
|
||||
local i = p + ki.length()
|
||||
local j = i
|
||||
loop (true) {
|
||||
local ch = json.substring(j, j+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" { j = j + 1 continue }
|
||||
break
|
||||
}
|
||||
local digits = json.substring(i, j)
|
||||
if digits { print(digits) }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
282
apps/selfhost-vm/collect_mixed_smoke.nyash
Normal file
282
apps/selfhost-vm/collect_mixed_smoke.nyash
Normal file
@ -0,0 +1,282 @@
|
||||
static box Main {
|
||||
// --- minimal helpers (self-contained) ---
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 }
|
||||
return pos + rel
|
||||
}
|
||||
_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
|
||||
}
|
||||
read_digits(s, pos) {
|
||||
if pos < 0 { return "" }
|
||||
local n = s.length()
|
||||
if pos >= n { return "" }
|
||||
local i = pos
|
||||
local out_start = -1
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if _is_digit(ch) == 1 { if out_start < 0 { out_start = i } i = i + 1 continue } else { break }
|
||||
}
|
||||
if out_start < 0 { return "" }
|
||||
return s.substring(out_start, i)
|
||||
}
|
||||
find_balanced_object_end(json, idx) {
|
||||
if idx < 0 { return -1 }
|
||||
local n = json.length()
|
||||
if idx >= n { return -1 }
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local i = idx
|
||||
local iter = 0
|
||||
loop (i < n) {
|
||||
iter = iter + 1
|
||||
if iter > 5000 { return -1 }
|
||||
local ch = json.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" { i = i + 2 continue }
|
||||
if ch == "\"" { in_str = 0 }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" { in_str = 1 i = i + 1 continue }
|
||||
if ch == "{" { depth = depth + 1 }
|
||||
if ch == "}" { depth = depth - 1 if depth == 0 { return i + 1 } }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// --- core: collect Print outputs in order ---
|
||||
collect_prints(json) {
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
local guard = 0
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 200 { break }
|
||||
local p = index_of_from(json, k_print, pos)
|
||||
if p < 0 { break }
|
||||
// bound current Print slice as [current_print, next_print)
|
||||
local obj_start = p
|
||||
local next_p = index_of_from(json, k_print, p + k_print.length())
|
||||
local obj_end = json.length()
|
||||
if next_p > 0 { obj_end = next_p }
|
||||
|
||||
// 1) BinaryOp(int '+' int)
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, obj_start)
|
||||
if epos > 0 { if epos < obj_end {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = index_of_from(json, k_bo, epos)
|
||||
if bpos > 0 { if bpos < obj_end {
|
||||
local opok = index_of_from(json, "\"operator\":\"+\"", bpos)
|
||||
if opok > 0 {
|
||||
// typed left/right literal ints
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = index_of_from(json, k_l, bpos)
|
||||
if lp > 0 { if lp < obj_end {
|
||||
local ld = read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = read_digits(json, rp + k_r.length())
|
||||
if rd != "" { out.push(_int_to_str(_str_to_int(ld) + _str_to_int(rd))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
// fallback: scan two successive '"value":' digits within expression bounds
|
||||
local k_v = "\"value\":"
|
||||
local v1 = index_of_from(json, k_v, epos)
|
||||
if v1 > 0 { if v1 < obj_end {
|
||||
local d1 = read_digits(json, v1 + k_v.length())
|
||||
if d1 != "" {
|
||||
local v2 = index_of_from(json, k_v, v1 + k_v.length())
|
||||
if v2 > 0 { if v2 < obj_end {
|
||||
local d2 = read_digits(json, v2 + k_v.length())
|
||||
if d2 != "" { out.push(_int_to_str(_str_to_int(d1) + _str_to_int(d2))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 2) Compare(lhs/rhs ints): prints 1/0
|
||||
{
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = index_of_from(json, k_cp, obj_start)
|
||||
if cpos > 0 { if cpos < obj_end {
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = index_of_from(json, k_op, cpos)
|
||||
if opos > 0 { if opos < obj_end {
|
||||
local oi = opos + k_op.length()
|
||||
local oj = index_of_from(json, "\"", oi)
|
||||
if oj > 0 { if oj <= obj_end {
|
||||
local op = json.substring(oi, oj)
|
||||
local k_v = "\"value\":"
|
||||
local lhs_v = index_of_from(json, k_v, oj)
|
||||
if lhs_v > 0 { if lhs_v < obj_end {
|
||||
local la = read_digits(json, lhs_v + k_v.length())
|
||||
if la != "" {
|
||||
local rhs_v = index_of_from(json, k_v, lhs_v + k_v.length())
|
||||
if rhs_v > 0 { if rhs_v < obj_end {
|
||||
local rb = read_digits(json, rhs_v + k_v.length())
|
||||
if rb != "" {
|
||||
local ai = _str_to_int(la)
|
||||
local bi = _str_to_int(rb)
|
||||
local res = 0
|
||||
if op == "<" { if ai < bi { res = 1 } }
|
||||
if op == "==" { if ai == bi { res = 1 } }
|
||||
if op == "<=" { if ai <= bi { res = 1 } }
|
||||
if op == ">" { if ai > bi { res = 1 } }
|
||||
if op == ">=" { if ai >= bi { res = 1 } }
|
||||
if op == "!=" { if ai != bi { res = 1 } }
|
||||
out.push(_int_to_str(res))
|
||||
pos = p + k_print.length()
|
||||
continue
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 3) FunctionCall echo/itoa (single literal arg)
|
||||
{
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = index_of_from(json, k_fc, obj_start)
|
||||
if fcp > 0 { if fcp < obj_end {
|
||||
local kn = "\"name\":\""
|
||||
local np = index_of_from(json, kn, fcp)
|
||||
if np > 0 { if np < obj_end {
|
||||
local ni = np + kn.length()
|
||||
local nj = index_of_from(json, "\"", ni)
|
||||
if nj > 0 { if nj <= obj_end {
|
||||
local fname = json.substring(ni, nj)
|
||||
local ka = "\"arguments\":["
|
||||
local ap = index_of_from(json, ka, nj)
|
||||
if ap > 0 { if ap < obj_end {
|
||||
// string arg
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, ap)
|
||||
if ps > 0 { if ps < obj_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= obj_end {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { out.push(sval) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}}
|
||||
// int arg
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, ap)
|
||||
if pi > 0 { if pi < obj_end {
|
||||
local ival = read_digits(json, pi + ki.length())
|
||||
if ival != "" { if fname == "itoa" { out.push(ival) pos = p + k_print.length() continue } else { if fname == "echo" { out.push(ival) pos = p + k_print.length() continue } } }
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 4) Literal string
|
||||
{
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, obj_start)
|
||||
if ps > 0 { if ps < obj_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= obj_end { out.push(json.substring(si, sj)) pos = p + k_print.length() continue }}
|
||||
}}
|
||||
}
|
||||
// 5) Literal int
|
||||
{
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, obj_start)
|
||||
if pi > 0 { if pi < obj_end {
|
||||
local digits = read_digits(json, pi + ki.length())
|
||||
if digits != "" { out.push(digits) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
// Unknown: skip ahead
|
||||
pos = p + k_print.length()
|
||||
if pos <= p { pos = p + 1 }
|
||||
}
|
||||
return out
|
||||
}
|
||||
// int<->str (non-negative only)
|
||||
_str_to_int(s) {
|
||||
local n = 0
|
||||
local i = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
n = n * 10
|
||||
if ch == "0" { n = n + 0 }
|
||||
if ch == "1" { n = n + 1 }
|
||||
if ch == "2" { n = n + 2 }
|
||||
if ch == "3" { n = n + 3 }
|
||||
if ch == "4" { n = n + 4 }
|
||||
if ch == "5" { n = n + 5 }
|
||||
if ch == "6" { n = n + 6 }
|
||||
if ch == "7" { n = n + 7 }
|
||||
if ch == "8" { n = n + 8 }
|
||||
if ch == "9" { n = n + 9 }
|
||||
i = i + 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
_int_to_str(n) {
|
||||
if n == 0 { return "0" }
|
||||
local s = new ArrayBox()
|
||||
local x = n
|
||||
loop (x > 0) {
|
||||
local d = x % 10
|
||||
if d == 0 { s.push("0") }
|
||||
if d == 1 { s.push("1") }
|
||||
if d == 2 { s.push("2") }
|
||||
if d == 3 { s.push("3") }
|
||||
if d == 4 { s.push("4") }
|
||||
if d == 5 { s.push("5") }
|
||||
if d == 6 { s.push("6") }
|
||||
if d == 7 { s.push("7") }
|
||||
if d == 8 { s.push("8") }
|
||||
if d == 9 { s.push("9") }
|
||||
x = (x - d) / 10
|
||||
}
|
||||
local out = new ArrayBox()
|
||||
local i = s.size() - 1
|
||||
loop (i >= 0) { out.push(s.get(i)) i = i - 1 }
|
||||
local j = 0
|
||||
local acc = ""
|
||||
loop (j < out.size()) { acc = acc + out.get(j) j = j + 1 }
|
||||
return acc
|
||||
}
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
|
||||
local arr = collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
18
apps/selfhost-vm/collect_prints_loader_smoke.nyash
Normal file
18
apps/selfhost-vm/collect_prints_loader_smoke.nyash
Normal file
@ -0,0 +1,18 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// Mixed shapes (loader-centric)
|
||||
// 1) echo() with empty args -> ""
|
||||
// 2) string literal with quotes inside
|
||||
// 3) itoa() with empty args -> "0"
|
||||
// 4) BinaryOp 8+9
|
||||
// 5) int literal 4
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"C\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":8}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":9}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@ -1,309 +1,12 @@
|
||||
// Mini-VM: function-based entry using library
|
||||
|
||||
using selfhost.vm.json_cur as MiniJsonCur
|
||||
using selfhost.vm.json as MiniJson
|
||||
// Thin entry: delegate to core MiniVm
|
||||
// Using is pre-inlined by runner; keep entry minimal for maintainability.
|
||||
using selfhost.vm.core as MiniVm
|
||||
// Local static box (duplicated from mini_vm_lib for now to avoid include gate issues)
|
||||
// Program entry: prefer argv[0] JSON, fallback to embedded sample
|
||||
|
||||
static box Main {
|
||||
// Small helpers for quick JSON scans (avoid cross-box deps)
|
||||
index_of_from(hay, needle, pos) {
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
}
|
||||
_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
|
||||
}
|
||||
_digit_value(ch) {
|
||||
if ch == "0" { return 0 }
|
||||
if ch == "1" { return 1 }
|
||||
if ch == "2" { return 2 }
|
||||
if ch == "3" { return 3 }
|
||||
if ch == "4" { return 4 }
|
||||
if ch == "5" { return 5 }
|
||||
if ch == "6" { return 6 }
|
||||
if ch == "7" { return 7 }
|
||||
if ch == "8" { return 8 }
|
||||
if ch == "9" { return 9 }
|
||||
return 0
|
||||
}
|
||||
_str_to_int(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
local acc = 0
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "0" { acc = acc * 10 + 0 i = i + 1 continue }
|
||||
if ch == "1" { acc = acc * 10 + 1 i = i + 1 continue }
|
||||
if ch == "2" { acc = acc * 10 + 2 i = i + 1 continue }
|
||||
if ch == "3" { acc = acc * 10 + 3 i = i + 1 continue }
|
||||
if ch == "4" { acc = acc * 10 + 4 i = i + 1 continue }
|
||||
if ch == "5" { acc = acc * 10 + 5 i = i + 1 continue }
|
||||
if ch == "6" { acc = acc * 10 + 6 i = i + 1 continue }
|
||||
if ch == "7" { acc = acc * 10 + 7 i = i + 1 continue }
|
||||
if ch == "8" { acc = acc * 10 + 8 i = i + 1 continue }
|
||||
if ch == "9" { acc = acc * 10 + 9 i = i + 1 continue }
|
||||
break
|
||||
}
|
||||
return acc
|
||||
}
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
// If args provided, use the first as JSON source (guard None)
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
local s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
// Top-level fast paths for simple Print cases (Literal/FunctionCall/Compare/BinaryOp)
|
||||
// 0) If(literal int) then/else branch (string prints)
|
||||
if json.indexOf("\"If\"") >= 0 {
|
||||
local kc = "\"condition\""
|
||||
local pc = json.indexOf(kc)
|
||||
if pc >= 0 {
|
||||
local kv = "\"type\":\"int\",\"value\":"
|
||||
local pv = json.indexOf(kv, pc)
|
||||
if pv >= 0 {
|
||||
local vi = pv + kv.length()
|
||||
local ve = json.indexOf("}", vi)
|
||||
if ve >= 0 {
|
||||
local cond_str = json.substring(vi, ve)
|
||||
// parse int
|
||||
local a = 0
|
||||
local i = 0
|
||||
loop (i < cond_str.length()) {
|
||||
local ch = cond_str.substring(i, i+1)
|
||||
if ch == "0" { a = a*10 i = i + 1 continue }
|
||||
if ch == "1" { a = a*10+1 i = i + 1 continue }
|
||||
if ch == "2" { a = a*10+2 i = i + 1 continue }
|
||||
if ch == "3" { a = a*10+3 i = i + 1 continue }
|
||||
if ch == "4" { a = a*10+4 i = i + 1 continue }
|
||||
if ch == "5" { a = a*10+5 i = i + 1 continue }
|
||||
if ch == "6" { a = a*10+6 i = i + 1 continue }
|
||||
if ch == "7" { a = a*10+7 i = i + 1 continue }
|
||||
if ch == "8" { a = a*10+8 i = i + 1 continue }
|
||||
if ch == "9" { a = a*10+9 i = i + 1 continue }
|
||||
break
|
||||
}
|
||||
local truthy = 0
|
||||
if a != 0 { truthy = 1 }
|
||||
local bkey = "\"then_body\""
|
||||
if truthy == 0 { bkey = "\"else_body\"" }
|
||||
local pb = json.indexOf(bkey, ve)
|
||||
if pb >= 0 {
|
||||
// search first string literal value inside the chosen body
|
||||
local ts = "\"type\":\"string\",\"value\":\""
|
||||
local ps = json.indexOf(ts, pb)
|
||||
if ps >= 0 {
|
||||
local si = ps + ts.length()
|
||||
local sj = json.indexOf("\"", si)
|
||||
if sj >= 0 { print(json.substring(si, sj)) return 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 1) Print(Literal string)
|
||||
if json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\"") >= 0 {
|
||||
local ks = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\""
|
||||
local ps = json.indexOf(ks)
|
||||
if ps >= 0 {
|
||||
local si = ps + ks.length()
|
||||
local sj = json.indexOf("\"", si)
|
||||
if sj >= 0 { print(json.substring(si, sj)) return 0 }
|
||||
}
|
||||
}
|
||||
// 2) Print(Literal int)
|
||||
if json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\"") >= 0 {
|
||||
local ki = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local pi = json.indexOf(ki)
|
||||
if pi >= 0 {
|
||||
local ii = pi + ki.length()
|
||||
// take until next non-digit or closing brace
|
||||
local ie = json.indexOf("}", ii)
|
||||
if ie < 0 { ie = ii }
|
||||
local d = json.substring(ii, ie)
|
||||
print(d)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
// 3) Print(FunctionCall) minimal (echo/itoa)
|
||||
if json.indexOf("\"FunctionCall\"") >= 0 {
|
||||
local pos = 0
|
||||
loop (true) {
|
||||
local kfc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = json.indexOf(kfc, pos)
|
||||
if fcp < 0 { break }
|
||||
local kn = "\"name\":\""
|
||||
local pn = json.indexOf(kn, fcp)
|
||||
if pn < 0 { break }
|
||||
local ni = pn + kn.length()
|
||||
local nj = json.indexOf("\"", ni)
|
||||
if nj < 0 { break }
|
||||
local fname = json.substring(ni, nj)
|
||||
local ka = "\"arguments\":["
|
||||
local pa = json.indexOf(ka, nj)
|
||||
if pa < 0 { pos = nj + 1 continue }
|
||||
// string arg
|
||||
local ts = "\"type\":\"string\",\"value\":\""
|
||||
local ti = json.indexOf(ts, pa)
|
||||
if ti >= 0 {
|
||||
local si = ti + ts.length()
|
||||
local sj = json.indexOf("\"", si)
|
||||
if sj >= 0 {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { print(sval) }
|
||||
pos = sj + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
// int arg
|
||||
local ti2 = json.indexOf("\"type\":\"int\",\"value\":", pa)
|
||||
if ti2 >= 0 {
|
||||
local vi = ti2 + "\"type\":\"int\",\"value\":".length()
|
||||
local ve = json.indexOf("}", vi)
|
||||
if ve < 0 { ve = vi }
|
||||
local ival = json.substring(vi, ve)
|
||||
if fname == "itoa" || fname == "echo" { print(ival) }
|
||||
pos = ve + 1
|
||||
continue
|
||||
}
|
||||
pos = pn + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// 4) Print(Compare) minimal
|
||||
if json.indexOf("\"Compare\"") >= 0 {
|
||||
local ko = "\"operation\":\""
|
||||
local po = json.indexOf(ko)
|
||||
if po >= 0 {
|
||||
local oi = po + ko.length()
|
||||
local oj = json.indexOf("\"", oi)
|
||||
if oj >= 0 {
|
||||
local op = json.substring(oi, oj)
|
||||
local kv = "\"value\":\""
|
||||
// lhs int
|
||||
local kl = "\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local pl = json.indexOf(kl, oj)
|
||||
if pl >= 0 {
|
||||
local li = pl + kl.length()
|
||||
local le = json.indexOf("}", li)
|
||||
if le >= 0 {
|
||||
local la = json.substring(li, le)
|
||||
// rhs int
|
||||
local kr = "\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local pr = json.indexOf(kr, le)
|
||||
if pr >= 0 {
|
||||
local ri = pr + kr.length()
|
||||
local re = json.indexOf("}", ri)
|
||||
if re >= 0 {
|
||||
local rb = json.substring(ri, re)
|
||||
// compute
|
||||
local a = 0
|
||||
local i = 0
|
||||
loop (i < la.length()) { local ch = la.substring(i, i+1) if ch == "0" { a = a*10+0 i=i+1 continue } if ch == "1" { a=a*10+1 i=i+1 continue } if ch == "2" { a=a*10+2 i=i+1 continue } if ch == "3" { a=a*10+3 i=i+1 continue } if ch == "4" { a=a*10+4 i=i+1 continue } if ch == "5" { a=a*10+5 i=i+1 continue } if ch == "6" { a=a*10+6 i=i+1 continue } if ch == "7" { a=a*10+7 i=i+1 continue } if ch == "8" { a=a*10+8 i=i+1 continue } if ch == "9" { a=a*10+9 i=i+1 continue } break }
|
||||
local b = 0
|
||||
local j = 0
|
||||
loop (j < rb.length()) { local ch2 = rb.substring(j, j+1) if ch2 == "0" { b = b*10+0 j=j+1 continue } if ch2 == "1" { b=b*10+1 j=j+1 continue } if ch2 == "2" { b=b*10+2 j=j+1 continue } if ch2 == "3" { b=b*10+3 j=j+1 continue } if ch2 == "4" { b=b*10+4 j=j+1 continue } if ch2 == "5" { b=b*10+5 j=j+1 continue } if ch2 == "6" { b=b*10+6 j=j+1 continue } if ch2 == "7" { b=b*10+7 j=j+1 continue } if ch2 == "8" { b=b*10+8 j=j+1 continue } if ch2 == "9" { b=b*10+9 j=j+1 continue } break }
|
||||
local res = 0
|
||||
if op == "<" { if a < b { res = 1 } }
|
||||
if op == "==" { if a == b { res = 1 } }
|
||||
if op == "<=" { if a <= b { res = 1 } }
|
||||
if op == ">" { if a > b { res = 1 } }
|
||||
if op == ">=" { if a >= b { res = 1 } }
|
||||
if op == "!=" { if a != b { res = 1 } }
|
||||
print(res)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 5) BinaryOp(int+int) typed pattern twice and add(保険)
|
||||
if json.indexOf("\"BinaryOp\"") >= 0 && json.indexOf("\"operator\":\"+\"") >= 0 {
|
||||
local pat = "\"type\":\"int\",\"value\":"
|
||||
local p1 = json.indexOf(pat)
|
||||
if p1 >= 0 {
|
||||
// parse first integer by taking until next closing brace
|
||||
local i = p1 + pat.length()
|
||||
local end1 = json.indexOf("}", i)
|
||||
if end1 < 0 { end1 = i }
|
||||
local a_str = json.substring(i, end1)
|
||||
// convert a_str to int
|
||||
local a = 0
|
||||
local k = 0
|
||||
loop (k < a_str.length()) {
|
||||
local ch = a_str.substring(k, k+1)
|
||||
if ch == "0" { a = a * 10 + 0 k = k + 1 continue }
|
||||
if ch == "1" { a = a * 10 + 1 k = k + 1 continue }
|
||||
if ch == "2" { a = a * 10 + 2 k = k + 1 continue }
|
||||
if ch == "3" { a = a * 10 + 3 k = k + 1 continue }
|
||||
if ch == "4" { a = a * 10 + 4 k = k + 1 continue }
|
||||
if ch == "5" { a = a * 10 + 5 k = k + 1 continue }
|
||||
if ch == "6" { a = a * 10 + 6 k = k + 1 continue }
|
||||
if ch == "7" { a = a * 10 + 7 k = k + 1 continue }
|
||||
if ch == "8" { a = a * 10 + 8 k = k + 1 continue }
|
||||
if ch == "9" { a = a * 10 + 9 k = k + 1 continue }
|
||||
break
|
||||
}
|
||||
// parse second integer
|
||||
local p2 = json.lastIndexOf(pat)
|
||||
if p2 >= 0 && p2 != p1 {
|
||||
local j = p2 + pat.length()
|
||||
local end2 = json.indexOf("}", j)
|
||||
if end2 < 0 { end2 = j }
|
||||
local b_str = json.substring(j, end2)
|
||||
local b = 0
|
||||
local m = 0
|
||||
loop (m < b_str.length()) {
|
||||
local ch2 = b_str.substring(m, m+1)
|
||||
if ch2 == "0" { b = b * 10 + 0 m = m + 1 continue }
|
||||
if ch2 == "1" { b = b * 10 + 1 m = m + 1 continue }
|
||||
if ch2 == "2" { b = b * 10 + 2 m = m + 1 continue }
|
||||
if ch2 == "3" { b = b * 10 + 3 m = m + 1 continue }
|
||||
if ch2 == "4" { b = b * 10 + 4 m = m + 1 continue }
|
||||
if ch2 == "5" { b = b * 10 + 5 m = m + 1 continue }
|
||||
if ch2 == "6" { b = b * 10 + 6 m = m + 1 continue }
|
||||
if ch2 == "7" { b = b * 10 + 7 m = m + 1 continue }
|
||||
if ch2 == "8" { b = b * 10 + 8 m = m + 1 continue }
|
||||
if ch2 == "9" { b = b * 10 + 9 m = m + 1 continue }
|
||||
break
|
||||
}
|
||||
print(a + b)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to full MiniVm runner
|
||||
local vm = new MiniVm()
|
||||
return vm.run(json)
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
if args { if args.size() > 0 { @s = args.get(0) if s { json = s } } }
|
||||
return new MiniVm().run(json)
|
||||
}
|
||||
}
|
||||
|
||||
// Top-level fallback entry for current runner
|
||||
function main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
local s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
local vm = new MiniVm()
|
||||
return vm.run(json)
|
||||
}
|
||||
|
||||
@ -3,23 +3,23 @@
|
||||
static box MiniVm {
|
||||
_is_digit(ch) { return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" }
|
||||
read_digits(json, pos) {
|
||||
local out = ""
|
||||
@out = ""
|
||||
loop (true) {
|
||||
local s = json.substring(pos, pos+1)
|
||||
@s = json.substring(pos, pos+1)
|
||||
if s == "" { break }
|
||||
if _is_digit(s) { out = out + s pos = pos + 1 } else { break }
|
||||
}
|
||||
return out
|
||||
}
|
||||
parse_first_int(json) {
|
||||
local key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
local idx = json.lastIndexOf(key)
|
||||
@key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
@idx = json.lastIndexOf(key)
|
||||
if idx < 0 { return "0" }
|
||||
local start = idx + key.length()
|
||||
@start = idx + key.length()
|
||||
return read_digits(json, start)
|
||||
}
|
||||
run_branch(json) {
|
||||
local n = parse_first_int(json)
|
||||
@n = parse_first_int(json)
|
||||
if n == "0" || n == "1" || n == "2" || n == "3" || n == "4" { print("10") return 0 }
|
||||
print("20")
|
||||
return 0
|
||||
@ -29,12 +29,12 @@ static box MiniVm {
|
||||
// Program entry: embedded JSON (value=1 → print 10; else → 20)
|
||||
static box Main {
|
||||
main(args) {
|
||||
local vm = new MiniVm()
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
@vm = new MiniVm()
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
// If provided, override by argv[0]
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
local s = args.get(0)
|
||||
@s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
@ -44,15 +44,15 @@ static box Main {
|
||||
|
||||
// Top-level fallback entry for current runner
|
||||
function main(args) {
|
||||
local vm = new MiniVm()
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
@vm = new MiniVm()
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
local s = args.get(0)
|
||||
@s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
local n = vm.parse_first_int(json)
|
||||
@n = vm.parse_first_int(json)
|
||||
if n == "0" || n == "1" || n == "2" || n == "3" || n == "4" { print("10") return 0 }
|
||||
print("20")
|
||||
return 0
|
||||
|
||||
@ -6,9 +6,9 @@ static box MiniVm {
|
||||
|
||||
// Read consecutive digits starting at pos
|
||||
read_digits(json, pos) {
|
||||
local out = ""
|
||||
@out = ""
|
||||
loop (true) {
|
||||
local s = json.substring(pos, pos+1)
|
||||
@s = json.substring(pos, pos+1)
|
||||
if s == "" { break }
|
||||
if _is_digit(s) { out = out + s pos = pos + 1 } else { break }
|
||||
}
|
||||
@ -17,16 +17,16 @@ static box MiniVm {
|
||||
|
||||
// Extract the first integer literal from our AST JSON v0 subset
|
||||
parse_first_int(json) {
|
||||
local key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
local idx = json.lastIndexOf(key)
|
||||
@key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
@idx = json.lastIndexOf(key)
|
||||
if idx < 0 { return "0" }
|
||||
local start = idx + key.length()
|
||||
@start = idx + key.length()
|
||||
return read_digits(json, start)
|
||||
}
|
||||
|
||||
// Execute a minimal program: print the extracted integer and exit code 0
|
||||
run(json) {
|
||||
local n = parse_first_int(json)
|
||||
@n = parse_first_int(json)
|
||||
print(n)
|
||||
return 0
|
||||
}
|
||||
|
||||
16
apps/selfhost-vm/run_core_wrapper.nyash
Normal file
16
apps/selfhost-vm/run_core_wrapper.nyash
Normal file
@ -0,0 +1,16 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
if args { if args.size() > 0 { @s = args.get(0) if s { json = s } } }
|
||||
print("pre")
|
||||
print(json.length())
|
||||
print(json.indexOf("\"kind\":\"Program\""))
|
||||
print(json.indexOf("\"kind\":\"Print\""))
|
||||
print(json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\""))
|
||||
@code = new MiniVm().run(json)
|
||||
print("post")
|
||||
return code
|
||||
}
|
||||
}
|
||||
@ -79,17 +79,93 @@ static box MiniVmBinOp {
|
||||
local scan = new MiniVmScan()
|
||||
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 }
|
||||
// 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.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.length())
|
||||
if rp > 0 { if rp < slice_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.length())
|
||||
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 { return -1 }
|
||||
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.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.length())
|
||||
if rp > 0 { if rp < obj_end2 {
|
||||
local rd = scan.read_digits(json, rp + k_rint.length())
|
||||
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 { return -1 }
|
||||
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.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.length())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.length())
|
||||
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 { return -1 }
|
||||
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.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.length())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.length())
|
||||
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) {
|
||||
@ -2,19 +2,41 @@
|
||||
static box MiniVmScan {
|
||||
// helper: find needle from position pos
|
||||
index_of_from(hay, needle, pos) {
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
// Guard position; clamp to valid range to avoid negative substring
|
||||
if pos < 0 { pos = 0 }
|
||||
local n = hay.length()
|
||||
if pos >= n { return -1 }
|
||||
local m = needle.length()
|
||||
if m <= 0 { return pos }
|
||||
local i = pos
|
||||
local limit = n - m
|
||||
loop (i <= limit) {
|
||||
local seg = hay.substring(i, i + m)
|
||||
if seg == needle { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// helper: find balanced bracket range [ ... ] starting at idx (points to '[')
|
||||
find_balanced_array_end(json, idx) {
|
||||
local n = json.length()
|
||||
@n = json.length()
|
||||
if json.substring(idx, idx+1) != "[" { return -1 }
|
||||
local depth = 0
|
||||
local i = idx
|
||||
@depth = 0
|
||||
@i = idx
|
||||
@in_str = 0
|
||||
@guard = 0
|
||||
loop (i < n) {
|
||||
local ch = json.substring(i, i+1)
|
||||
guard = guard + 1
|
||||
if guard > 50000 { return -1 }
|
||||
@ch = json.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" { i = i + 2 continue }
|
||||
if ch == "\"" { in_str = 0 i = i + 1 continue }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" { in_str = 1 i = i + 1 continue }
|
||||
if ch == "[" { depth = depth + 1 }
|
||||
if ch == "]" {
|
||||
depth = depth - 1
|
||||
@ -27,12 +49,23 @@ static box MiniVmScan {
|
||||
|
||||
// helper: find balanced object range { ... } starting at idx (points to '{')
|
||||
find_balanced_object_end(json, idx) {
|
||||
local n = json.length()
|
||||
@n = json.length()
|
||||
if json.substring(idx, idx+1) != "{" { return -1 }
|
||||
local depth = 0
|
||||
local i = idx
|
||||
@depth = 0
|
||||
@i = idx
|
||||
@in_str = 0
|
||||
@guard = 0
|
||||
loop (i < n) {
|
||||
local ch = json.substring(i, i+1)
|
||||
guard = guard + 1
|
||||
if guard > 50000 { return -1 }
|
||||
@ch = json.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" { i = i + 2 continue }
|
||||
if ch == "\"" { in_str = 0 i = i + 1 continue }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" { in_str = 1 i = i + 1 continue }
|
||||
if ch == "{" { depth = depth + 1 }
|
||||
if ch == "}" {
|
||||
depth = depth - 1
|
||||
@ -44,11 +77,11 @@ static box MiniVmScan {
|
||||
}
|
||||
|
||||
_str_to_int(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
local acc = 0
|
||||
@i = 0
|
||||
@n = s.length()
|
||||
@acc = 0
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
@ch = s.substring(i, i+1)
|
||||
if ch == "0" { acc = acc * 10 + 0 i = i + 1 continue }
|
||||
if ch == "1" { acc = acc * 10 + 1 i = i + 1 continue }
|
||||
if ch == "2" { acc = acc * 10 + 2 i = i + 1 continue }
|
||||
@ -78,11 +111,11 @@ static box MiniVmScan {
|
||||
}
|
||||
_int_to_str(n) {
|
||||
if n == 0 { return "0" }
|
||||
local v = n
|
||||
local out = ""
|
||||
@v = n
|
||||
@out = ""
|
||||
loop (v > 0) {
|
||||
local d = v % 10
|
||||
local ch = _digit_char(d)
|
||||
@d = v % 10
|
||||
@ch = _digit_char(d)
|
||||
out = ch + out
|
||||
v = v / 10
|
||||
}
|
||||
@ -91,9 +124,9 @@ static box MiniVmScan {
|
||||
|
||||
// Read digit runs starting at pos
|
||||
read_digits(json, pos) {
|
||||
local out = ""
|
||||
@out = ""
|
||||
loop (true) {
|
||||
local s = json.substring(pos, pos+1)
|
||||
@s = json.substring(pos, pos+1)
|
||||
if s == "" { break }
|
||||
if s == "0" { out = out + s pos = pos + 1 continue }
|
||||
if s == "1" { out = out + s pos = pos + 1 continue }
|
||||
@ -112,18 +145,18 @@ static box MiniVmScan {
|
||||
|
||||
// Linear pass: sum all numbers outside of quotes
|
||||
sum_numbers_no_quotes(json) {
|
||||
local i = 0
|
||||
local n = json.length()
|
||||
local total = 0
|
||||
@i = 0
|
||||
@n = json.length()
|
||||
@total = 0
|
||||
loop (i < n) {
|
||||
local ch = json.substring(i, i+1)
|
||||
@ch = json.substring(i, i+1)
|
||||
if ch == "\"" {
|
||||
local j = index_of_from(json, "\"", i+1)
|
||||
@j = index_of_from(json, "\"", i+1)
|
||||
if j < 0 { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
local d = read_digits(json, i)
|
||||
@d = read_digits(json, i)
|
||||
if d { total = total + _str_to_int(d) i = i + d.length() continue }
|
||||
i = i + 1
|
||||
}
|
||||
@ -132,11 +165,11 @@ static box MiniVmScan {
|
||||
|
||||
// Naive: sum all digit runs anywhere
|
||||
sum_all_digits_naive(json) {
|
||||
local i = 0
|
||||
local n = json.length()
|
||||
local total = 0
|
||||
@i = 0
|
||||
@n = json.length()
|
||||
@total = 0
|
||||
loop (i < n) {
|
||||
local d = read_digits(json, i)
|
||||
@d = read_digits(json, i)
|
||||
if d { total = total + _str_to_int(d) i = i + d.length() continue }
|
||||
i = i + 1
|
||||
}
|
||||
@ -145,19 +178,19 @@ static box MiniVmScan {
|
||||
|
||||
// Sum first two integers outside quotes; returns string or empty
|
||||
sum_first_two_numbers(json) {
|
||||
local i = 0
|
||||
local n = json.length()
|
||||
local total = 0
|
||||
@i = 0
|
||||
@n = json.length()
|
||||
@total = 0
|
||||
local found = 0
|
||||
loop (i < n) {
|
||||
local ch = json.substring(i, i+1)
|
||||
@ch = json.substring(i, i+1)
|
||||
if ch == "\"" {
|
||||
local j = index_of_from(json, "\"", i+1)
|
||||
@j = index_of_from(json, "\"", i+1)
|
||||
if j < 0 { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
local d = read_digits(json, i)
|
||||
@d = read_digits(json, i)
|
||||
if d {
|
||||
total = total + _str_to_int(d)
|
||||
found = found + 1
|
||||
@ -170,4 +203,3 @@ static box MiniVmScan {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
19
apps/selfhost/compiler/README.md
Normal file
19
apps/selfhost/compiler/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Nyash Selfhost Compiler (MVP scaffold)
|
||||
|
||||
This is the Phase 15.3 work-in-progress Nyash compiler implemented in Ny.
|
||||
|
||||
Layout
|
||||
- `compiler.nyash`: entry (CompilerBox). Reads `tmp/ny_parser_input.ny`, prints JSON v0.
|
||||
- `parser/`: lexer/parser/ast (scaffolds; to be filled as we extend Stage‑2)
|
||||
- `mir/`: builder/optimizer stubs (future; current target is JSON v0 emit)
|
||||
- `tests/`: Stage‑1/2 samples (TBD)
|
||||
|
||||
Run (behind flag)
|
||||
- `NYASH_USE_NY_COMPILER=1 target/release/nyash --backend vm <program.nyash>`
|
||||
- The runner writes the input to `tmp/ny_parser_input.ny` and invokes this program.
|
||||
- It captures a JSON v0 line from stdout and executes it via the JSON bridge.
|
||||
- Stage‑3 syntax gate: set `NYASH_NY_COMPILER_STAGE3=1` to pass `--stage3` to the parser (accepts Break/Continue/Throw/Try in JSON v0).
|
||||
|
||||
Notes
|
||||
- Early MVP emits a minimal JSON v0 (currently a placeholder: return 0). We will gradually wire lexer/parser/emitter.
|
||||
- Keep JSON v0 spec in `docs/reference/ir/json_v0.md`.
|
||||
38
apps/selfhost/compiler/boxes/debug_box.nyash
Normal file
38
apps/selfhost/compiler/boxes/debug_box.nyash
Normal file
@ -0,0 +1,38 @@
|
||||
// DebugBox — conditional debug output aggregator (extracted)
|
||||
box DebugBox {
|
||||
enabled
|
||||
birth() {
|
||||
me.enabled = 0
|
||||
return 0
|
||||
}
|
||||
set_enabled(v) {
|
||||
me.enabled = v
|
||||
return 0
|
||||
}
|
||||
log(msg) {
|
||||
if me.enabled {
|
||||
local c = new ConsoleBox()
|
||||
c.println("[DEBUG] " + msg)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
info(msg) {
|
||||
if me.enabled {
|
||||
local c = new ConsoleBox()
|
||||
c.println("[INFO] " + msg)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
error(msg) {
|
||||
if me.enabled {
|
||||
local c = new ConsoleBox()
|
||||
c.println("[ERROR] " + msg)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Include stub to satisfy current include lowering (expects a static box)
|
||||
static box DebugStub {
|
||||
main(args) { return 0 }
|
||||
}
|
||||
26
apps/selfhost/compiler/boxes/emitter_box.nyash
Normal file
26
apps/selfhost/compiler/boxes/emitter_box.nyash
Normal file
@ -0,0 +1,26 @@
|
||||
// EmitterBox — thin wrapper to emit JSON v0 (extracted)
|
||||
box EmitterBox {
|
||||
emit_program(json, usings_json) {
|
||||
if json == null { return json }
|
||||
// Normalize usings payload to at least an empty array so meta.usings is always present
|
||||
local payload = usings_json
|
||||
if payload == null { payload = "[]" }
|
||||
if payload.length() == 0 { payload = "[]" }
|
||||
// Inject meta.usings before closing brace of top-level object
|
||||
local n = json.lastIndexOf("}")
|
||||
if n < 0 { return json }
|
||||
local head = json.substring(0, n)
|
||||
local tail = json.substring(n, json.length())
|
||||
local needs_comma = 1
|
||||
if head.length() == 0 { needs_comma = 0 } else {
|
||||
local last = head.substring(head.length() - 1, head.length())
|
||||
if last == "{" || last == "," { needs_comma = 0 }
|
||||
}
|
||||
if needs_comma == 1 { head = head + "," }
|
||||
return head + "\"meta\":{\"usings\":" + payload + "}" + tail
|
||||
}
|
||||
}
|
||||
|
||||
static box EmitterStub {
|
||||
main(args) { return 0 }
|
||||
}
|
||||
52
apps/selfhost/compiler/boxes/mir_emitter_box.nyash
Normal file
52
apps/selfhost/compiler/boxes/mir_emitter_box.nyash
Normal file
@ -0,0 +1,52 @@
|
||||
// MirEmitterBox — Minimal MIR JSON v0 emitter (M2 MVP)
|
||||
// Scope: Return(Int) only (const + ret). Safe default to 0 when not found.
|
||||
// Future: add Binary/Compare/ExternCall/BoxCall lowering incrementally.
|
||||
|
||||
box MirEmitterBox {
|
||||
// Extract first Return(Int) value from Stage-1 JSON (very small string scan)
|
||||
_extract_return_int(ast_json) {
|
||||
if ast_json == null { return 0 }
|
||||
// Look for '"type":"Return"'
|
||||
local p = ast_json.lastIndexOf("\"type\":\"Return\"")
|
||||
if p < 0 { p = ast_json.indexOf("\"type\":\"Return\"") }
|
||||
if p < 0 { return 0 }
|
||||
// From there, search for '"type":"Int","value":'
|
||||
local q = ast_json.indexOf("\"type\":\"Int\",\"value\":", p)
|
||||
if q < 0 { return 0 }
|
||||
q = q + 23 // length of the marker
|
||||
// Read consecutive digits (optional minus not handled in Stage-1 yet)
|
||||
local n = ast_json.length()
|
||||
local i = q
|
||||
local s = ""
|
||||
loop(i < n) {
|
||||
local ch = ast_json.substring(i, i+1)
|
||||
if ch >= "0" && ch <= "9" { s = s + ch i = i + 1 } else { break }
|
||||
}
|
||||
if s.length() == 0 { return 0 }
|
||||
// String to int via addition loop
|
||||
local val = 0
|
||||
local k = 0
|
||||
local m = s.length()
|
||||
loop(k < m) {
|
||||
local d = s.substring(k, k+1)
|
||||
local dv = 0
|
||||
if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } }
|
||||
val = val * 10 + dv
|
||||
k = k + 1
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Build minimal MIR JSON v0: main with const -> ret
|
||||
emit_mir_min(ast_json) {
|
||||
local retv = me._extract_return_int(ast_json)
|
||||
local s = "{\"functions\":[{\"name\":\"main\",\"params\":[],\"blocks\":[{\"id\":0,\"instructions\":["
|
||||
s = s + "{\\\"op\\\":\\\"const\\\",\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + retv + "},\\\"dst\\\":1},"
|
||||
s = s + "{\\\"op\\\":\\\"ret\\\",\\\"value\\\":1}"
|
||||
s = s + "]}]}]}"
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
static box MirEmitterStub { main(args) { return 0 } }
|
||||
|
||||
917
apps/selfhost/compiler/boxes/parser_box.nyash
Normal file
917
apps/selfhost/compiler/boxes/parser_box.nyash
Normal file
@ -0,0 +1,917 @@
|
||||
// ParserBox — Stage‑1 JSON v0 generator(extracted, simplified for Rust parser)
|
||||
box ParserBox {
|
||||
gpos
|
||||
usings_json
|
||||
stage3
|
||||
|
||||
birth() { me.gpos = 0 me.usings_json = "[]" me.stage3 = 0 return 0 }
|
||||
|
||||
stage3_enable(flag) {
|
||||
if flag == null { flag = 0 }
|
||||
if flag == 0 { me.stage3 = 0 } else { me.stage3 = 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
stage3_enabled() {
|
||||
if me.stage3 == 1 { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
esc_json(s) {
|
||||
local out = ""
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "\\" { out = out + "\\\\" } else { if ch == "\"" { out = out + "\\\"" } else { out = out + ch } }
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
is_digit(ch) { return ch >= "0" && ch <= "9" }
|
||||
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
|
||||
// simple alpha/underscore check for identifiers
|
||||
is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
|
||||
|
||||
gpos_set(i) { me.gpos = i return 0 }
|
||||
gpos_get() { return me.gpos }
|
||||
|
||||
// lightweight string helpers
|
||||
starts_with(src, i, pat) {
|
||||
local n = src.length()
|
||||
local m = pat.length()
|
||||
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
|
||||
}
|
||||
index_of(src, i, pat) {
|
||||
local n = src.length()
|
||||
local m = pat.length()
|
||||
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(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
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)
|
||||
}
|
||||
|
||||
// Enhanced whitespace skipper (inline lines): used by line-based using extractor
|
||||
trim_ws_and_line_comments(s) {
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
// leading spaces/tabs
|
||||
loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 }
|
||||
// strip line comments
|
||||
if i + 1 < n && s.substring(i, i+2) == "//" { return "" }
|
||||
return s.substring(i, n)
|
||||
}
|
||||
|
||||
// keyword match at position i 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.length()
|
||||
local j = i + kw.length()
|
||||
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
|
||||
}
|
||||
|
||||
// integer to string (uses string concat coercion)
|
||||
i2s(v) { return "" + v }
|
||||
|
||||
// Read identifier starting at i: [A-Za-z_][A-Za-z0-9_]*; returns "name@pos"
|
||||
read_ident2(src, i) {
|
||||
local n = src.length()
|
||||
local j = i
|
||||
if j >= n { return "@" + me.i2s(i) }
|
||||
local ch = src.substring(j, j+1)
|
||||
if me.is_alpha(ch) == 0 { return "@" + me.i2s(i) }
|
||||
j = j + 1
|
||||
loop(j < n) {
|
||||
local c = src.substring(j, j+1)
|
||||
if me.is_alpha(c) || me.is_digit(c) { j = j + 1 } else { break }
|
||||
}
|
||||
local name = src.substring(i, j)
|
||||
return name + "@" + me.i2s(j)
|
||||
}
|
||||
|
||||
// Read string literal at i (i points to '"'); returns raw content (no quotes), updates gpos
|
||||
read_string_lit(src, i) {
|
||||
local n = src.length()
|
||||
local j = i
|
||||
if j >= n || src.substring(j, j+1) != "\"" { me.gpos_set(i) return "" }
|
||||
j = j + 1
|
||||
local out = ""
|
||||
local guard = 0
|
||||
local max = 200000
|
||||
loop(j < n) {
|
||||
if guard > max { break } else { guard = guard + 1 }
|
||||
local ch = src.substring(j, j+1)
|
||||
if ch == "\"" { j = j + 1 me.gpos_set(j) return out }
|
||||
if ch == "\\" && j + 1 < n {
|
||||
local nx = src.substring(j+1, j+2)
|
||||
if nx == "\"" { out = out + "\"" j = j + 2 } else { if nx == "\\" { out = out + "\\" j = j + 2 } else { if nx == "n" { out = out + "\n" j = j + 2 } else { if nx == "r" { out = out + "\r" j = j + 2 } else { if nx == "t" { out = out + "\t" j = j + 2 } else { if nx == "u" && j + 5 < n { out = out + src.substring(j, j+6) j = j + 6 } else { out = out + nx j = j + 2 } } } } } }
|
||||
} else { out = out + ch j = j + 1 }
|
||||
}
|
||||
me.gpos_set(j)
|
||||
return out
|
||||
}
|
||||
|
||||
// Append a using entry into usings_json (no-op acceptance path)
|
||||
add_using(kind, target, alias) {
|
||||
// kind: "path" or "ns"; target: path or namespace; alias: nullable
|
||||
local cur = me.usings_json
|
||||
if cur == null || cur.length() == 0 { cur = "[]" }
|
||||
// Build entry
|
||||
local name = ""
|
||||
local path = null
|
||||
if kind == "path" {
|
||||
path = target
|
||||
if alias != null { name = alias } else {
|
||||
local p = target
|
||||
// basename
|
||||
local idx = -1
|
||||
local t = 0
|
||||
loop(t < p.length()) { if p.substring(t,t+1) == "/" { idx = t } t = t + 1 }
|
||||
if idx >= 0 { p = p.substring(idx+1, p.length()) }
|
||||
if p.length() > 6 && me.starts_with(p, p.length()-6, ".nyash") == 1 { p = p.substring(0, p.length()-6) }
|
||||
name = p
|
||||
}
|
||||
} else {
|
||||
name = target
|
||||
if alias != null { name = alias }
|
||||
}
|
||||
local entry = "{\"name\":\"" + me.esc_json(name) + "\""
|
||||
if path != null { entry = entry + ",\"path\":\"" + me.esc_json(path) + "\"" }
|
||||
entry = entry + "}"
|
||||
// Insert before closing ']' of array
|
||||
if cur == "[]" { me.usings_json = "[" + entry + "]" return 0 }
|
||||
// naive append
|
||||
local pos = cur.lastIndexOf("]")
|
||||
if pos < 0 { me.usings_json = "[" + entry + "]" return 0 }
|
||||
me.usings_json = cur.substring(0, pos) + "," + entry + "]"
|
||||
return 0
|
||||
}
|
||||
|
||||
// Collect `using` lines into JSON array stored in me.usings_json (no-op acceptance)
|
||||
extract_usings(src) {
|
||||
if src == null { me.usings_json = "[]" return 0 }
|
||||
local n = src.length()
|
||||
local i = 0
|
||||
local first = 1
|
||||
local out = "["
|
||||
loop(i < n) {
|
||||
// read a line
|
||||
local j = i
|
||||
loop(j < n && src.substring(j, j+1) != "\n") { j = j + 1 }
|
||||
local line = src.substring(i, j)
|
||||
// process
|
||||
local k = 0
|
||||
loop(k < line.length() && (line.substring(k,k+1) == " " || line.substring(k,k+1) == "\t")) { k = k + 1 }
|
||||
if me.starts_with(line, k, "using ") == 1 {
|
||||
local rest = me.trim(line.substring(k + 6, line.length()))
|
||||
// split on ' as '
|
||||
local as_pos = me.index_of(rest, 0, " as ")
|
||||
local target = rest
|
||||
local alias = null
|
||||
if as_pos >= 0 {
|
||||
target = me.trim(rest.substring(0, as_pos))
|
||||
alias = me.trim(rest.substring(as_pos + 4, rest.length()))
|
||||
}
|
||||
// path or namespace
|
||||
local is_path = 0
|
||||
if target.length() > 0 {
|
||||
if me.starts_with(target, 0, "\"") == 1 { is_path = 1 }
|
||||
if me.starts_with(target, 0, "./") == 1 { is_path = 1 }
|
||||
if me.starts_with(target, 0, "/") == 1 { is_path = 1 }
|
||||
if target.length() >= 6 && me.starts_with(target, target.length()-6, ".nyash") == 1 { is_path = 1 }
|
||||
}
|
||||
local name = ""
|
||||
local path = null
|
||||
if is_path == 1 {
|
||||
// trim quotes
|
||||
if me.starts_with(target, 0, "\"") == 1 {
|
||||
target = target.substring(1, target.length())
|
||||
if target.length() > 0 && target.substring(target.length()-1, target.length()) == "\"" {
|
||||
target = target.substring(0, target.length()-1)
|
||||
}
|
||||
}
|
||||
path = target
|
||||
if alias != null { name = alias } else {
|
||||
// derive from basename
|
||||
local p = target
|
||||
// find last '/'
|
||||
local idx = -1
|
||||
local t = 0
|
||||
loop(t < p.length()) { if p.substring(t,t+1) == "/" { idx = t } t = t + 1 }
|
||||
if idx >= 0 { p = p.substring(idx+1, p.length()) }
|
||||
// strip .nyash
|
||||
if p.length() > 6 && me.starts_with(p, p.length()-6, ".nyash") == 1 { p = p.substring(0, p.length()-6) }
|
||||
name = p
|
||||
}
|
||||
} else {
|
||||
name = target
|
||||
}
|
||||
// append JSON entry
|
||||
if first == 0 { out = out + "," } else { first = 0 }
|
||||
out = out + "{\"name\":\"" + me.esc_json(name) + "\""
|
||||
if path != null { out = out + ",\"path\":\"" + me.esc_json(path) + "\"" }
|
||||
out = out + "}"
|
||||
}
|
||||
i = j + 1
|
||||
}
|
||||
out = out + "]"
|
||||
me.usings_json = out
|
||||
return 0
|
||||
}
|
||||
get_usings_json() { return me.usings_json }
|
||||
|
||||
to_int(s) { local n = s.length() if n == 0 { return 0 } local i = 0 local acc = 0 loop(i < n) { local d = s.substring(i, i+1) local dv = 0 if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } } acc = acc * 10 + dv i = i + 1 } return acc }
|
||||
|
||||
skip_ws(src, i) { if src == null { return i } local n = src.length() local cont = 1 local guard = 0 local max = 100000 loop(cont == 1) { if guard > max { return i } 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 }
|
||||
// identifiers/strings not required for Stage‑1 beyond string literal parse above
|
||||
|
||||
// using metadata omitted in Stage‑1
|
||||
|
||||
parse_number2(src, i) { local n = src.length() local j = i local cont = 1 local guard = 0 local max = 100000 loop(cont == 1) { if guard > max { cont = 0 } else { guard = guard + 1 if j < n { if me.is_digit(src.substring(j, j+1)) { j = j + 1 } else { cont = 0 } } else { cont = 0 } } } local s = src.substring(i, j) me.gpos_set(j) return "{\"type\":\"Int\",\"value\":" + s + "}" }
|
||||
parse_string2(src, i) { local n = src.length() local j = i + 1 local out = "" local guard = 0 local max = 200000 loop(j < n) { if guard > max { break } guard = guard + 1 local ch = src.substring(j, j+1) if ch == "\"" { j = j + 1 me.gpos_set(j) return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" } if ch == "\\" && j + 1 < n { local nx = src.substring(j+1, j+2) if nx == "\"" { out = out + "\"" j = j + 2 } else { if nx == "\\" { out = out + "\\" j = j + 2 } else { if nx == "n" { out = out + "\n" j = j + 2 } else { if nx == "r" { out = out + "\r" j = j + 2 } else { if nx == "t" { out = out + "\t" j = j + 2 } else { if nx == "u" && j + 5 < n { out = out + src.substring(j, j+6) j = j + 6 } else { out = out + nx j = j + 2 } } } } } } } else { out = out + ch j = j + 1 } } me.gpos_set(j) return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" }
|
||||
|
||||
parse_factor2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
if j >= src.length() { me.gpos_set(j) return "{\"type\":\"Int\",\"value\":0}" }
|
||||
if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" }
|
||||
if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" }
|
||||
if me.starts_with_kw(src, j, "null") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Null\"}" }
|
||||
// Peek expression: peek <expr> { "label" => <expr>, ..., else => <expr> }
|
||||
if me.starts_with_kw(src, j, "peek") == 1 {
|
||||
j = j + 4
|
||||
j = me.skip_ws(src, j)
|
||||
// scrutinee expression
|
||||
local scr = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "{" { j = j + 1 } // enter arms block
|
||||
j = me.skip_ws(src, j)
|
||||
local arms_json = "["
|
||||
local first_arm = 1
|
||||
local else_json = null
|
||||
local n = src.length()
|
||||
local contp = 1
|
||||
local guardp = 0
|
||||
local maxp = 400000
|
||||
loop(contp == 1) {
|
||||
if guardp > maxp { contp = 0 } else { guardp = guardp + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= n { contp = 0 } else {
|
||||
if src.substring(j, j+1) == "}" {
|
||||
j = j + 1
|
||||
contp = 0
|
||||
} else {
|
||||
// else arm or labeled arm
|
||||
if me.starts_with_kw(src, j, "else") == 1 {
|
||||
j = j + 4
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+2) == "=>" { j = j + 2 }
|
||||
j = me.skip_ws(src, j)
|
||||
// else body may be a block or bare expr
|
||||
if src.substring(j, j+1) == "{" {
|
||||
j = j + 1
|
||||
j = me.skip_ws(src, j)
|
||||
else_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "}" { j = j + 1 }
|
||||
} else {
|
||||
else_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
}
|
||||
// optional separator/newline tolerated; continue until '}'
|
||||
} else {
|
||||
// labeled arm: string literal label
|
||||
if src.substring(j, j+1) != "\"" {
|
||||
// degrade safely to avoid infinite loop
|
||||
j = j + 1
|
||||
continue
|
||||
}
|
||||
local label_raw = me.read_string_lit(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+2) == "=>" { j = j + 2 }
|
||||
j = me.skip_ws(src, j)
|
||||
// arm expr: block or bare expr
|
||||
local expr_json = "{\"type\":\"Int\",\"value\":0}"
|
||||
if src.substring(j, j+1) == "{" {
|
||||
j = j + 1
|
||||
j = me.skip_ws(src, j)
|
||||
expr_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "}" { j = j + 1 }
|
||||
} else {
|
||||
expr_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
}
|
||||
local arm_json = "{\"label\":\"" + me.esc_json(label_raw) + "\",\"expr\":" + expr_json + "}"
|
||||
if first_arm == 1 { arms_json = arms_json + arm_json first_arm = 0 } else { arms_json = arms_json + "," + arm_json }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
arms_json = arms_json + "]"
|
||||
if else_json == null { else_json = "{\"type\":\"Null\"}" }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Peek\",\"scrutinee\":" + scr + ",\"arms\":" + arms_json + ",\"else\":" + else_json + "}"
|
||||
}
|
||||
local ch = src.substring(j, j+1)
|
||||
// Parenthesized
|
||||
if ch == "(" {
|
||||
local inner = me.parse_expr2(src, j + 1)
|
||||
local k = me.gpos_get()
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
me.gpos_set(k)
|
||||
return inner
|
||||
}
|
||||
// String literal
|
||||
if ch == "\"" { return me.parse_string2(src, j) }
|
||||
// Map literal: {"k": v, ...} (string keys only) → Call{name:"map.of", args:[Str(k1), v1, Str(k2), v2, ...]}
|
||||
if ch == "{" {
|
||||
local n = src.length()
|
||||
j = j + 1
|
||||
local out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= n { cont = 0 } else {
|
||||
if src.substring(j, j+1) == "}" { j = j + 1 cont = 0 } else {
|
||||
// key (string only for Stage-2)
|
||||
if src.substring(j, j+1) != "\"" {
|
||||
// degrade by skipping one char to avoid infinite loop
|
||||
j = j + 1
|
||||
continue
|
||||
}
|
||||
local key_raw = me.read_string_lit(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == ":" { j = j + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
local val_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
local key_json = "{\"type\":\"Str\",\"value\":\"" + me.esc_json(key_raw) + "\"}"
|
||||
if first == 1 { out = out + key_json + "," + val_json first = 0 } else { out = out + "," + key_json + "," + val_json }
|
||||
// optional comma
|
||||
local before2 = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
// progress guard (in case of malformed input)
|
||||
if j <= before2 { if j < n { j = j + 1 } else { j = n } }
|
||||
}
|
||||
}
|
||||
}
|
||||
out = out + "]"
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Call\",\"name\":\"map.of\",\"args\":" + out + "}"
|
||||
}
|
||||
// Array literal: [e1, e2, ...] → Call{name:"array.of", args:[...]}
|
||||
if ch == "[" {
|
||||
local n = src.length()
|
||||
j = j + 1
|
||||
local out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= n { cont = 0 } else {
|
||||
if src.substring(j, j+1) == "]" { j = j + 1 cont = 0 } else {
|
||||
local before = j
|
||||
local ej = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if first == 1 { out = out + ej first = 0 } else { out = out + "," + ej }
|
||||
// optional comma+whitespace
|
||||
local before2 = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
// progress guard
|
||||
if j <= before { if j < n { j = j + 1 } else { j = n } }
|
||||
}
|
||||
}
|
||||
}
|
||||
out = out + "]"
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Call\",\"name\":\"array.of\",\"args\":" + out + "}"
|
||||
}
|
||||
// true/false
|
||||
if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" }
|
||||
if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" }
|
||||
// new Class(args)
|
||||
if me.starts_with_kw(src, j, "new") == 1 {
|
||||
local p = me.skip_ws(src, j + 3)
|
||||
local idp = me.read_ident2(src, p)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local cls = idp.substring(0, at)
|
||||
local k = me.to_int(idp.substring(at+1, idp.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == "(" { k = k + 1 }
|
||||
local args_and_pos = me.parse_args2(src, k)
|
||||
local at2 = args_and_pos.lastIndexOf("@")
|
||||
local args_json = args_and_pos.substring(0, at2)
|
||||
k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
me.gpos_set(k)
|
||||
return "{\"type\":\"New\",\"class\":\"" + cls + "\",\"args\":" + args_json + "}"
|
||||
}
|
||||
// Identifier / Call / Method chain
|
||||
if me.is_alpha(ch) {
|
||||
local idp = me.read_ident2(src, j)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local name = idp.substring(0, at)
|
||||
local k = me.to_int(idp.substring(at+1, idp.length()))
|
||||
local node = "{\"type\":\"Var\",\"name\":\"" + name + "\"}"
|
||||
local cont2 = 1
|
||||
loop(cont2 == 1) {
|
||||
k = me.skip_ws(src, k)
|
||||
local tch = src.substring(k, k+1)
|
||||
if tch == "(" {
|
||||
k = k + 1
|
||||
local args_and_pos = me.parse_args2(src, k)
|
||||
local at2 = args_and_pos.lastIndexOf("@")
|
||||
local args_json = args_and_pos.substring(0, at2)
|
||||
k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
node = "{\"type\":\"Call\",\"name\":\"" + name + "\",\"args\":" + args_json + "}"
|
||||
} else {
|
||||
if tch == "." {
|
||||
k = k + 1
|
||||
k = me.skip_ws(src, k)
|
||||
local midp = me.read_ident2(src, k)
|
||||
local at3 = midp.lastIndexOf("@")
|
||||
local mname = midp.substring(0, at3)
|
||||
k = me.to_int(midp.substring(at3+1, midp.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == "(" { k = k + 1 }
|
||||
local args2 = me.parse_args2(src, k)
|
||||
local at4 = args2.lastIndexOf("@")
|
||||
local args_json2 = args2.substring(0, at4)
|
||||
k = me.to_int(args2.substring(at4+1, args2.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
node = "{\"type\":\"Method\",\"recv\":" + node + ",\"method\":\"" + mname + "\",\"args\":" + args_json2 + "}"
|
||||
} else { cont2 = 0 }
|
||||
}
|
||||
}
|
||||
me.gpos_set(k)
|
||||
return node
|
||||
}
|
||||
// Fallback: number
|
||||
return me.parse_number2(src, j)
|
||||
}
|
||||
// unary minus binds tighter than * /
|
||||
parse_unary2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
if src.substring(j, j+1) == "-" {
|
||||
local rhs = me.parse_factor2(src, j + 1)
|
||||
j = me.gpos_get()
|
||||
local zero = "{\"type\":\"Int\",\"value\":0}"
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Binary\",\"op\":\"-\",\"lhs\":" + zero + ",\"rhs\":" + rhs + "}"
|
||||
}
|
||||
return me.parse_factor2(src, j)
|
||||
}
|
||||
parse_term2(src, i) { local lhs = me.parse_unary2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "*" && op != "/" { cont = 0 } else { local rhs = me.parse_unary2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs }
|
||||
parse_sum2(src, i) { local lhs = me.parse_term2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "+" && op != "-" { cont = 0 } else { local rhs = me.parse_term2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs }
|
||||
parse_compare2(src, i) { local lhs = me.parse_sum2(src, i) local j = me.gpos_get() j = me.skip_ws(src, j) local two = src.substring(j, j+2) local one = src.substring(j, j+1) local op = "" if two == "==" || two == "!=" || two == "<=" || two == ">=" { op = two j = j + 2 } else { if one == "<" || one == ">" { op = one j = j + 1 } } if op == "" { me.gpos_set(j) return lhs } local rhs = me.parse_sum2(src, j) j = me.gpos_get() me.gpos_set(j) return "{\"type\":\"Compare\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" }
|
||||
parse_expr2(src, i) {
|
||||
local lhs = me.parse_compare2(src, i)
|
||||
local j = me.gpos_get()
|
||||
local cont = 1
|
||||
loop(cont == 1) {
|
||||
j = me.skip_ws(src, j)
|
||||
local two = src.substring(j, j+2)
|
||||
if two != "&&" && two != "||" { cont = 0 } else {
|
||||
local rhs = me.parse_compare2(src, j+2)
|
||||
j = me.gpos_get()
|
||||
lhs = "{\"type\":\"Logical\",\"op\":\"" + two + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
|
||||
}
|
||||
}
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "?" {
|
||||
j = j + 1
|
||||
j = me.skip_ws(src, j)
|
||||
local then_expr = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == ":" { j = j + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
local else_expr = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if else_expr.length() == 0 { else_expr = "{\"type\":\"Int\",\"value\":0}" }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Ternary\",\"cond\":" + lhs + ",\"then\":" + then_expr + ",\"else\":" + else_expr + "}"
|
||||
}
|
||||
me.gpos_set(j)
|
||||
return lhs
|
||||
}
|
||||
parse_args2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
local n = src.length()
|
||||
local out = "["
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == ")" { return "[]@" + me.i2s(j) }
|
||||
// first argument
|
||||
local e = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
out = out + e
|
||||
// subsequent arguments with guard
|
||||
local cont_args = 1
|
||||
local guard = 0
|
||||
local max = 100000
|
||||
loop(cont_args == 1) {
|
||||
if guard > max { cont_args = 0 } else { guard = guard + 1 }
|
||||
local before = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," {
|
||||
j = j + 1
|
||||
j = me.skip_ws(src, j)
|
||||
e = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
out = out + "," + e
|
||||
} else { cont_args = 0 }
|
||||
if j == before { cont_args = 0 }
|
||||
}
|
||||
out = out + "]"
|
||||
return out + "@" + me.i2s(j)
|
||||
}
|
||||
parse_stmt2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
local stmt_start = j
|
||||
if me.starts_with_kw(src, j, "using") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "\"" {
|
||||
local p = me.read_string_lit(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
local alias = null
|
||||
if me.starts_with_kw(src, j, "as") == 1 { j = j + 2 j = me.skip_ws(src, j) local idp = me.read_ident2(src, j) local at = idp.lastIndexOf("@") alias = idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) }
|
||||
me.add_using("path", p, alias)
|
||||
} else {
|
||||
if me.is_alpha(src.substring(j, j+1)) {
|
||||
local idp = me.read_ident2(src, j)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local name = idp.substring(0, at)
|
||||
j = me.to_int(idp.substring(at+1, idp.length()))
|
||||
local cont = 1
|
||||
loop(cont == 1) {
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "." { j = j + 1 j = me.skip_ws(src, j) idp = me.read_ident2(src, j) at = idp.lastIndexOf("@") name = name + "." + idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) } else { cont = 0 }
|
||||
}
|
||||
j = me.skip_ws(src, j)
|
||||
local alias2 = null
|
||||
if me.starts_with_kw(src, j, "as") == 1 { j = j + 2 j = me.skip_ws(src, j) idp = me.read_ident2(src, j) at = idp.lastIndexOf("@") alias2 = idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) }
|
||||
me.add_using("ns", name, alias2)
|
||||
}
|
||||
}
|
||||
// ensure progress
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return ""
|
||||
}
|
||||
// simple assignment: IDENT '=' expr ; → JSON v0 Local{name, expr} (Stage‑2 uses Local for updates)
|
||||
if j < src.length() && me.is_alpha(src.substring(j, j+1)) {
|
||||
local idp0 = me.read_ident2(src, j)
|
||||
local at0 = idp0.lastIndexOf("@")
|
||||
if at0 > 0 {
|
||||
local name0 = idp0.substring(0, at0)
|
||||
local k0 = me.to_int(idp0.substring(at0+1, idp0.length()))
|
||||
k0 = me.skip_ws(src, k0)
|
||||
if k0 < src.length() && src.substring(k0, k0+1) == "=" {
|
||||
local eq_two = "="
|
||||
if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) }
|
||||
if eq_two != "==" {
|
||||
k0 = k0 + 1
|
||||
k0 = me.skip_ws(src, k0)
|
||||
local default_local = "{\"type\":\"Int\",\"value\":0}"
|
||||
local expr_json0 = default_local
|
||||
local end_pos0 = k0
|
||||
if k0 < src.length() {
|
||||
local ahead = src.substring(k0, k0+1)
|
||||
if ahead != "}" && ahead != ";" {
|
||||
expr_json0 = me.parse_expr2(src, k0)
|
||||
end_pos0 = me.gpos_get()
|
||||
}
|
||||
}
|
||||
k0 = end_pos0
|
||||
if k0 <= stmt_start { if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } }
|
||||
me.gpos_set(k0)
|
||||
return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if me.starts_with_kw(src, j, "return") == 1 {
|
||||
j = j + 6
|
||||
j = me.skip_ws(src, j)
|
||||
local default_ret = "{\"type\":\"Int\",\"value\":0}"
|
||||
local expr_json_ret = default_ret
|
||||
local end_pos_ret = j
|
||||
if j < src.length() {
|
||||
local ahead_ret = src.substring(j, j+1)
|
||||
if ahead_ret != "}" && ahead_ret != ";" {
|
||||
expr_json_ret = me.parse_expr2(src, j)
|
||||
end_pos_ret = me.gpos_get()
|
||||
}
|
||||
}
|
||||
j = end_pos_ret
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Return\",\"expr\":" + expr_json_ret + "}"
|
||||
}
|
||||
if me.starts_with_kw(src, j, "local") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
local idp = me.read_ident2(src, j)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local name = idp.substring(0, at)
|
||||
j = me.to_int(idp.substring(at+1, idp.length()))
|
||||
j = me.skip_ws(src, j)
|
||||
if j < src.length() && src.substring(j, j+1) == "=" { j = j + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
local default_local = "{\"type\":\"Int\",\"value\":0}"
|
||||
local expr_json_local = default_local
|
||||
local end_pos_local = j
|
||||
if j < src.length() {
|
||||
local ahead_local = src.substring(j, j+1)
|
||||
if ahead_local != "}" && ahead_local != ";" {
|
||||
expr_json_local = me.parse_expr2(src, j)
|
||||
end_pos_local = me.gpos_get()
|
||||
}
|
||||
}
|
||||
j = end_pos_local
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Local\",\"name\":\"" + name + "\",\"expr\":" + expr_json_local + "}"
|
||||
}
|
||||
if me.starts_with_kw(src, j, "if") == 1 {
|
||||
j = j + 2
|
||||
j = me.skip_ws(src, j)
|
||||
local paren = 0
|
||||
if src.substring(j, j+1) == "(" { paren = 1 j = j + 1 }
|
||||
local cond = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if paren == 1 { j = me.skip_ws(src, j) if src.substring(j, j+1) == ")" { j = j + 1 } }
|
||||
j = me.skip_ws(src, j)
|
||||
local then_res = me.parse_block2(src, j)
|
||||
local at1 = then_res.lastIndexOf("@")
|
||||
local then_json = then_res.substring(0, at1)
|
||||
j = me.to_int(then_res.substring(at1+1, then_res.length()))
|
||||
j = me.skip_ws(src, j)
|
||||
local else_json = null
|
||||
if me.starts_with_kw(src, j, "else") == 1 { j = j + 4 j = me.skip_ws(src, j) local else_res = me.parse_block2(src, j) local at2 = else_res.lastIndexOf("@") else_json = else_res.substring(0, at2) j = me.to_int(else_res.substring(at2+1, else_res.length())) }
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
if else_json == null { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}" } else { return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + ",\"else\":" + else_json + "}" }
|
||||
}
|
||||
if me.starts_with_kw(src, j, "loop") == 1 {
|
||||
j = j + 4
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "(" { j = j + 1 }
|
||||
local cond = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == ")" { j = j + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
local body_res = me.parse_block2(src, j)
|
||||
local at3 = body_res.lastIndexOf("@")
|
||||
local body_json = body_res.substring(0, at3)
|
||||
j = me.to_int(body_res.substring(at3+1, body_res.length()))
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}"
|
||||
}
|
||||
// Stage-3 acceptance (syntax only): break / continue → no-op expression
|
||||
if me.starts_with_kw(src, j, "break") == 1 {
|
||||
j = j + 5
|
||||
if me.stage3_enabled() == 1 {
|
||||
j = me.skip_ws(src, j)
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Break\"}"
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
if me.starts_with_kw(src, j, "continue") == 1 {
|
||||
j = j + 8
|
||||
if me.stage3_enabled() == 1 {
|
||||
j = me.skip_ws(src, j)
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Continue\"}"
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
// Stage-3 acceptance: throw expr → degrade to Expr(expr)
|
||||
if me.starts_with_kw(src, j, "throw") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
local e_throw = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if me.stage3_enabled() == 1 {
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Throw\",\"expr\":" + e_throw + "}"
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":" + e_throw + "}"
|
||||
}
|
||||
// Stage-3 acceptance: try { ... } (catch ...)* (cleanup { ... })? → degrade to no-op (syntax only)
|
||||
if me.starts_with_kw(src, j, "try") == 1 {
|
||||
j = j + 3
|
||||
j = me.skip_ws(src, j)
|
||||
// parse try block
|
||||
local try_res = me.parse_block2(src, j)
|
||||
local at_t = try_res.lastIndexOf("@")
|
||||
local try_json = try_res.substring(0, at_t)
|
||||
j = me.to_int(try_res.substring(at_t+1, try_res.length()))
|
||||
local catches_json = "["
|
||||
local catch_first = 1
|
||||
// zero or more catch
|
||||
local guard_ct = 0
|
||||
local max_ct = 100
|
||||
local cont_ct = 1
|
||||
loop(cont_ct == 1) {
|
||||
if guard_ct > max_ct { cont_ct = 0 } else { guard_ct = guard_ct + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if me.starts_with_kw(src, j, "catch") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
local catch_type = null
|
||||
local catch_param = null
|
||||
if src.substring(j, j+1) == "(" { j = j + 1 j = me.skip_ws(src, j)
|
||||
// optional type + name
|
||||
if me.is_alpha(src.substring(j, j+1)) {
|
||||
local id1 = me.read_ident2(src, j)
|
||||
local at1 = id1.lastIndexOf("@")
|
||||
catch_type = id1.substring(0, at1)
|
||||
j = me.to_int(id1.substring(at1+1, id1.length()))
|
||||
j = me.skip_ws(src, j)
|
||||
}
|
||||
if me.is_alpha(src.substring(j, j+1)) {
|
||||
local id2 = me.read_ident2(src, j)
|
||||
local at2 = id2.lastIndexOf("@")
|
||||
catch_param = id2.substring(0, at2)
|
||||
j = me.to_int(id2.substring(at2+1, id2.length()))
|
||||
j = me.skip_ws(src, j)
|
||||
}
|
||||
if src.substring(j, j+1) == ")" { j = j + 1 }
|
||||
}
|
||||
j = me.skip_ws(src, j)
|
||||
// catch body
|
||||
local c_res = me.parse_block2(src, j)
|
||||
local atc = c_res.lastIndexOf("@")
|
||||
j = me.to_int(c_res.substring(atc+1, c_res.length()))
|
||||
if me.stage3_enabled() == 1 {
|
||||
local entry = "{"
|
||||
local wrote = 0
|
||||
if catch_param != null && catch_param.length() > 0 { entry = entry + "\"param\":\"" + me.esc_json(catch_param) + "\"" wrote = 1 }
|
||||
if catch_type != null && catch_type.length() > 0 { if wrote == 1 { entry = entry + "," } entry = entry + "\"typeHint\":\"" + me.esc_json(catch_type) + "\"" wrote = 1 }
|
||||
local body_json = c_res.substring(0, atc)
|
||||
if wrote == 1 { entry = entry + "," }
|
||||
entry = entry + "\"body\":" + body_json + "}"
|
||||
if catch_first == 0 { catches_json = catches_json + "," + entry } else { catches_json = catches_json + entry catch_first = 0 }
|
||||
}
|
||||
} else { cont_ct = 0 }
|
||||
}
|
||||
catches_json = catches_json + "]"
|
||||
// optional cleanup
|
||||
j = me.skip_ws(src, j)
|
||||
local finally_json = null
|
||||
if me.starts_with_kw(src, j, "cleanup") == 1 {
|
||||
j = j + 7
|
||||
j = me.skip_ws(src, j)
|
||||
local f_res = me.parse_block2(src, j)
|
||||
local atf = f_res.lastIndexOf("@")
|
||||
j = me.to_int(f_res.substring(atf+1, f_res.length()))
|
||||
finally_json = f_res.substring(0, atf)
|
||||
}
|
||||
if me.stage3_enabled() == 1 {
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
local node = "{\"type\":\"Try\",\"try\":" + try_json + ",\"catches\":" + catches_json
|
||||
if finally_json != null { node = node + ",\"finally\":" + finally_json }
|
||||
node = node + "}"
|
||||
return node
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
// Fallback: expression or unknown token — ensure progress even on malformed input
|
||||
local expr_start = j
|
||||
local e = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if j <= expr_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":" + e + "}"
|
||||
}
|
||||
parse_block2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
if src.substring(j, j+1) != "{" { return "[]@" + me.i2s(j) }
|
||||
j = j + 1
|
||||
local body = "["
|
||||
local first = 1
|
||||
local cont_block = 1
|
||||
loop(cont_block == 1) {
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= src.length() { cont_block = 0 } else {
|
||||
if src.substring(j, j+1) == "}" { j = j + 1 cont_block = 0 } else {
|
||||
local start_j = j
|
||||
local s = me.parse_stmt2(src, j)
|
||||
j = me.gpos_get()
|
||||
// Progress guard: ensure forward movement to avoid infinite loop on malformed input
|
||||
if j <= start_j {
|
||||
if j < src.length() { j = j + 1 } else { j = src.length() }
|
||||
me.gpos_set(j)
|
||||
}
|
||||
// consume optional semicolons (ASI minimal)
|
||||
local done = 0
|
||||
local guard = 0
|
||||
local max = 100000
|
||||
loop(done == 0) {
|
||||
if guard > max { done = 1 } else { guard = guard + 1 }
|
||||
local before = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < src.length() && src.substring(j, j+1) == ";" { j = j + 1 } else { done = 1 }
|
||||
if j == before { done = 1 }
|
||||
}
|
||||
if s.length() > 0 { if first == 1 { body = body + s first = 0 } else { body = body + "," + s } }
|
||||
}
|
||||
}
|
||||
}
|
||||
body = body + "]"
|
||||
return body + "@" + me.i2s(j)
|
||||
}
|
||||
parse_program2(src) {
|
||||
local i = me.skip_ws(src, 0)
|
||||
local body = "["
|
||||
local first = 1
|
||||
local cont_prog = 1
|
||||
loop(cont_prog == 1) {
|
||||
i = me.skip_ws(src, i)
|
||||
if i >= src.length() { cont_prog = 0 } else {
|
||||
local start_i = i
|
||||
local s = me.parse_stmt2(src, i)
|
||||
i = me.gpos_get()
|
||||
// Progress guard: ensure forward movement to avoid infinite loop on malformed input
|
||||
if i <= start_i {
|
||||
if i < src.length() { i = i + 1 } else { i = src.length() }
|
||||
me.gpos_set(i)
|
||||
}
|
||||
// consume optional semicolons between top-level statements
|
||||
local done2 = 0
|
||||
local guard2 = 0
|
||||
local max2 = 100000
|
||||
loop(done2 == 0) {
|
||||
if guard2 > max2 { done2 = 1 } else { guard2 = guard2 + 1 }
|
||||
local before2 = i
|
||||
i = me.skip_ws(src, i)
|
||||
if i < src.length() && src.substring(i, i+1) == ";" { i = i + 1 } else { done2 = 1 }
|
||||
if i == before2 { done2 = 1 }
|
||||
}
|
||||
if s.length() > 0 { if first == 1 { body = body + s first = 0 } else { body = body + "," + s } }
|
||||
}
|
||||
}
|
||||
body = body + "]"
|
||||
return "{\"version\":0,\"kind\":\"Program\",\"body\":" + body + "}"
|
||||
}
|
||||
}
|
||||
|
||||
static box ParserStub { main(args) { return 0 } }
|
||||
135
apps/selfhost/compiler/compiler.nyash
Normal file
135
apps/selfhost/compiler/compiler.nyash
Normal file
@ -0,0 +1,135 @@
|
||||
// Selfhost Compiler MVP (Phase 15.3)
|
||||
// Reads tmp/ny_parser_input.ny and prints a minimal JSON v0 program.
|
||||
// Components are split under boxes/ and included here.
|
||||
|
||||
// Prefer using for module declaration (Runner strips and registers)
|
||||
using "apps/selfhost-compiler/boxes/debug_box.nyash" as DebugBoxMod
|
||||
using "apps/selfhost-compiler/boxes/parser_box.nyash" as ParserBoxMod
|
||||
using "apps/selfhost-compiler/boxes/emitter_box.nyash" as EmitterBoxMod
|
||||
using "apps/selfhost-compiler/boxes/mir_emitter_box.nyash" as MirEmitterBoxMod
|
||||
|
||||
// Transitional: keep include for Phase-15 compatibility
|
||||
include "apps/selfhost-compiler/boxes/debug_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/parser_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/emitter_box.nyash"
|
||||
include "apps/selfhost-compiler/boxes/mir_emitter_box.nyash"
|
||||
|
||||
static box Main {
|
||||
// ---- IO helper ----
|
||||
read_all(path) {
|
||||
local fb = new FileBox()
|
||||
fb.open(path, "r")
|
||||
local s = fb.read()
|
||||
fb.close()
|
||||
if s == null { return "return 1+2*3" }
|
||||
return s
|
||||
}
|
||||
|
||||
// ---- JSON helpers ----
|
||||
esc_json(s) {
|
||||
local out = ""
|
||||
local i = 0
|
||||
local n = s.length()
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch == "\\" { out = out + "\\\\" } else {
|
||||
if ch == "\"" { out = out + "\\\"" } else { out = out + ch }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Parser delegation
|
||||
parse_program(src, stage3_flag) {
|
||||
local parser = new ParserBoxMod.ParserBox()
|
||||
if stage3_flag == 1 { parser.stage3_enable(1) }
|
||||
// Collect using metadata (no-op acceptance in Stage‑15)
|
||||
parser.extract_usings(src)
|
||||
me._usings = parser.get_usings_json()
|
||||
return parser.parse_program2(src)
|
||||
}
|
||||
|
||||
main(args) {
|
||||
// Debug setup
|
||||
me.dbg = new DebugBoxMod.DebugBox()
|
||||
me.dbg.set_enabled(0)
|
||||
|
||||
// Source selection (EXE-first friendly)
|
||||
// - default: safe constant
|
||||
// - positional arg: treat as input file path
|
||||
// - --read-tmp: use tmp/ny_parser_input.ny (requires FileBox plugin)
|
||||
local src = "return 1+2*3"
|
||||
local read_tmp = 0
|
||||
local input_path = null
|
||||
local stage3_mode = 0
|
||||
if args != null {
|
||||
local alen = args.length()
|
||||
local i = 0
|
||||
loop(i < alen) {
|
||||
local a = args.get(i)
|
||||
if a == "--read-tmp" {
|
||||
read_tmp = 1
|
||||
} else {
|
||||
if a == "--min-json" {
|
||||
/* handled later */
|
||||
} else {
|
||||
if a == "--stage3" {
|
||||
stage3_mode = 1
|
||||
} else {
|
||||
if input_path == null { input_path = a }
|
||||
}
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if input_path != null {
|
||||
// Prefer explicit file when provided (requires FileBox plugin)
|
||||
local s1 = me.read_all(input_path)
|
||||
if s1 != null { src = s1 }
|
||||
} else {
|
||||
if read_tmp == 1 {
|
||||
// Optional: read tmp/ny_parser_input.ny (requires FileBox plugin; do not use in CI)
|
||||
local s2 = me.read_all("tmp/ny_parser_input.ny")
|
||||
if s2 != null { src = s2 }
|
||||
}
|
||||
}
|
||||
|
||||
// Gate: minimal JSON when requested via script arg
|
||||
local min_mode = 0
|
||||
local emit_mir = 0
|
||||
if args != null {
|
||||
local alen = args.length()
|
||||
local i = 0
|
||||
loop(i < alen) {
|
||||
local arg = args.get(i)
|
||||
if arg == "--min-json" { min_mode = 1 }
|
||||
if arg == "--emit-mir" { emit_mir = 1 }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
local json = null
|
||||
local ast_json = null
|
||||
if min_mode == 1 {
|
||||
ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"
|
||||
} else {
|
||||
ast_json = me.parse_program(src, stage3_mode)
|
||||
}
|
||||
|
||||
if emit_mir == 1 {
|
||||
// Lower minimal AST to MIR JSON (Return(Int) only for MVP)
|
||||
local mir = new MirEmitterBoxMod.MirEmitterBox()
|
||||
json = mir.emit_mir_min(ast_json)
|
||||
} else {
|
||||
// Emit Stage‑1 JSON with metadata
|
||||
local emitter = new EmitterBoxMod.EmitterBox()
|
||||
json = emitter.emit_program(ast_json, me._usings)
|
||||
}
|
||||
|
||||
// Output JSON
|
||||
local console = new ConsoleBox()
|
||||
console.println(json)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
8
apps/selfhost/compiler/emitter/json_v0.nyash
Normal file
8
apps/selfhost/compiler/emitter/json_v0.nyash
Normal file
@ -0,0 +1,8 @@
|
||||
// JSON v0 emitter (MVP placeholder)
|
||||
static box JsonV0Emitter {
|
||||
// Emit a minimal Program{return 0}
|
||||
program_return0() {
|
||||
return "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/selfhost/compiler/mir/builder.nyash
Normal file
5
apps/selfhost/compiler/mir/builder.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
static box MirBuilder {
|
||||
// placeholder
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
5
apps/selfhost/compiler/mir/optimizer.nyash
Normal file
5
apps/selfhost/compiler/mir/optimizer.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
static box Optimizer {
|
||||
// placeholder
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
5
apps/selfhost/compiler/parser/ast.nyash
Normal file
5
apps/selfhost/compiler/parser/ast.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
static box AST {
|
||||
// scaffold for future AST node constructors
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
5
apps/selfhost/compiler/parser/lexer.nyash
Normal file
5
apps/selfhost/compiler/parser/lexer.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
static box Lexer {
|
||||
// scaffold for future implementation
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
5
apps/selfhost/compiler/parser/parser.nyash
Normal file
5
apps/selfhost/compiler/parser/parser.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
static box Parser {
|
||||
// scaffold for future implementation
|
||||
main(args) { return 0 }
|
||||
}
|
||||
|
||||
4
apps/selfhost/compiler/tests/stage1/README.md
Normal file
4
apps/selfhost/compiler/tests/stage1/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
Stage‑1 tests (scaffold)
|
||||
|
||||
Add minimal Ny source samples here. Harness TBD.
|
||||
|
||||
61
apps/selfhost/vm/boxes/json_cur.nyash
Normal file
61
apps/selfhost/vm/boxes/json_cur.nyash
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.length()
|
||||
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.length()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
749
apps/selfhost/vm/boxes/mini_vm_core.nyash
Normal file
749
apps/selfhost/vm/boxes/mini_vm_core.nyash
Normal file
@ -0,0 +1,749 @@
|
||||
using selfhost.vm.json as MiniJson
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
using selfhost.vm.binop as MiniVmBinOp
|
||||
using selfhost.vm.compare as MiniVmCompare
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box MiniVm {
|
||||
_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
|
||||
}
|
||||
_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 MiniJson().read_digits_from(json, pos) }
|
||||
// Read a JSON string starting at position pos (at opening quote); returns the decoded string
|
||||
read_json_string(json, pos) { return new MiniJson().read_quoted_from(json, pos) }
|
||||
// helper: find needle from position pos
|
||||
index_of_from(hay, needle, pos) { return new MiniVmScan().index_of_from(hay, needle, pos) }
|
||||
// helper: next non-whitespace character index from pos
|
||||
next_non_ws(json, pos) { return new MiniJson().next_non_ws(json, pos) }
|
||||
// ——— Helpers (as box methods) ———
|
||||
|
||||
// Minimal: Print(BinaryOp) with operator "+"; supports string+string and int+int
|
||||
// try_print_binop_at moved to MiniVmBinOp
|
||||
|
||||
// Greedy fallback: detect BinaryOp int+int by pattern regardless of field order nuances
|
||||
// try_print_binop_int_greedy moved to MiniVmBinOp
|
||||
|
||||
// Fallback: within the current Print's expression BinaryOp object, scan for two numeric values and sum
|
||||
// try_print_binop_sum_any moved to MiniVmBinOp
|
||||
|
||||
// Deterministic: within the first Print.expression BinaryOp('+'),
|
||||
// find exactly two numeric values from successive '"value":' fields and sum.
|
||||
// Stops after collecting two ints; bounded strictly by the expression object.
|
||||
// try_print_binop_sum_expr_values moved to MiniVmBinOp
|
||||
|
||||
// Simpler deterministic fallback: after the first BinaryOp '+',
|
||||
// scan forward for two successive 'value' fields and sum their integer digits.
|
||||
// This avoids brace matching and remains bounded by two finds.
|
||||
// try_print_binop_sum_after_bop moved to MiniVmBinOp
|
||||
|
||||
// Direct typed BinaryOp(int+int) matcher using explicit left/right literal paths
|
||||
try_print_binop_typed_direct(json) {
|
||||
local k_left = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_right = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = json.indexOf(k_left)
|
||||
if lp < 0 { return -1 }
|
||||
local ld = read_digits(json, lp + k_left.length())
|
||||
if ld == "" { return -1 }
|
||||
local rp = index_of_from(json, k_right, lp + k_left.length())
|
||||
if rp < 0 { return -1 }
|
||||
local rd = read_digits(json, rp + k_right.length())
|
||||
if rd == "" { return -1 }
|
||||
print(_int_to_str(_str_to_int(ld) + _str_to_int(rd)))
|
||||
return rp + k_right.length()
|
||||
}
|
||||
|
||||
// Tokenized typed extractor: search left/right blocks then type/value pairs
|
||||
try_print_binop_typed_tokens(json) {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos < 0 { return -1 }
|
||||
local lp = index_of_from(json, "\"left\":", bpos)
|
||||
if lp < 0 { return -1 }
|
||||
local kt = "\"type\":\"int\""
|
||||
local kv = "\"value\":"
|
||||
local tp1 = index_of_from(json, kt, lp)
|
||||
if tp1 < 0 { return -1 }
|
||||
local vp1 = index_of_from(json, kv, tp1)
|
||||
if vp1 < 0 { return -1 }
|
||||
local ld = read_digits(json, vp1 + kv.length())
|
||||
if ld == "" { return -1 }
|
||||
local rp = index_of_from(json, "\"right\":", lp)
|
||||
if rp < 0 { return -1 }
|
||||
local tp2 = index_of_from(json, kt, rp)
|
||||
if tp2 < 0 { return -1 }
|
||||
local vp2 = index_of_from(json, kv, tp2)
|
||||
if vp2 < 0 { return -1 }
|
||||
local rd = read_digits(json, vp2 + kv.length())
|
||||
if rd == "" { return -1 }
|
||||
print(_int_to_str(_str_to_int(ld) + _str_to_int(rd)))
|
||||
return rp
|
||||
}
|
||||
|
||||
// Fast value-pair extractor: find left/right then first value digits after each
|
||||
try_print_binop_value_pairs(json) {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos < 0 { return -1 }
|
||||
local kl = "\"left\":"
|
||||
local kv = "\"value\":"
|
||||
local lp = index_of_from(json, kl, bpos)
|
||||
if lp < 0 { return -1 }
|
||||
local v1 = index_of_from(json, kv, lp)
|
||||
if v1 < 0 { return -1 }
|
||||
local ld = read_digits(json, v1 + kv.length())
|
||||
if ld == "" { return -1 }
|
||||
local rp = index_of_from(json, "\"right\":", lp)
|
||||
if rp < 0 { return -1 }
|
||||
local v2 = index_of_from(json, kv, rp)
|
||||
if v2 < 0 { return -1 }
|
||||
local rd = read_digits(json, v2 + kv.length())
|
||||
if rd == "" { return -1 }
|
||||
print(_int_to_str(_str_to_int(ld) + _str_to_int(rd)))
|
||||
return v2 + kv.length()
|
||||
}
|
||||
|
||||
// Minimal: Print(Compare) for integers. Prints 1/0 for true/false.
|
||||
// try_print_compare_at moved to MiniVmCompare
|
||||
// Extract first Print literal from JSON v0 Program and return its string representation
|
||||
parse_first_print_literal(json) {
|
||||
// Find a Print statement
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = json.indexOf(k_print)
|
||||
if p < 0 { return null }
|
||||
// Find value type in the expression following Print
|
||||
local k_type = "\"type\":\""
|
||||
local tpos = json.indexOf(k_type)
|
||||
if tpos < 0 { return null }
|
||||
tpos = tpos + k_type.length()
|
||||
// Read type name until next quote
|
||||
local t_end = index_of_from(json, "\"", tpos)
|
||||
if t_end < 0 { return null }
|
||||
local ty = json.substring(tpos, t_end)
|
||||
// Find value field
|
||||
local k_val = "\"value\":"
|
||||
local vpos = index_of_from(json, k_val, t_end)
|
||||
if vpos < 0 { return null }
|
||||
vpos = vpos + k_val.length()
|
||||
if ty == "int" || ty == "i64" || ty == "integer" {
|
||||
// read digits via MiniJson
|
||||
local digits = new MiniJson().read_digits_from(json, vpos)
|
||||
return digits
|
||||
}
|
||||
if ty == "string" {
|
||||
// read quoted via MiniJson
|
||||
local i = index_of_from(json, "\"", vpos)
|
||||
if i < 0 { return null }
|
||||
return new MiniJson().read_quoted_from(json, i)
|
||||
}
|
||||
// Other types not supported yet
|
||||
return null
|
||||
}
|
||||
// helper: find balanced bracket range [ ... ] starting at idx (points to '[')
|
||||
find_balanced_array_end(json, idx) { return new MiniVmScan().find_balanced_array_end(json, idx) }
|
||||
// helper: find balanced object range { ... } starting at idx (points to '{')
|
||||
find_balanced_object_end(json, idx) { return new MiniVmScan().find_balanced_object_end(json, idx) }
|
||||
// Print all Print-Literal values within [start,end] (inclusive slice indices)
|
||||
print_prints_in_slice(json, start, end) { return new MiniVmPrints().print_prints_in_slice(json, start, end) }
|
||||
// Process top-level If with literal condition; print branch prints. Returns printed count.
|
||||
process_if_once(json) { return new MiniVmPrints().process_if_once(json) }
|
||||
print_all_print_literals(json) { return new MiniVmPrints().print_all_print_literals(json) }
|
||||
parse_first_int(json) {
|
||||
local key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
local idx = json.lastIndexOf(key)
|
||||
if idx < 0 { return "0" }
|
||||
local start = idx + key.length()
|
||||
return read_digits(json, start)
|
||||
}
|
||||
// Fallback: find first BinaryOp and return sum of two numeric values as string; empty if not found
|
||||
parse_first_binop_sum(json) {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos < 0 { return "" }
|
||||
// typed pattern inside left/right.literal.value: {"type":"int","value":<digits>}
|
||||
local k_typed = "\"type\":\"int\",\"value\":"
|
||||
// first number
|
||||
local p1 = index_of_from(json, k_typed, bpos)
|
||||
if p1 < 0 { return "" }
|
||||
local d1 = read_digits(json, p1 + k_typed.length())
|
||||
if d1 == "" { return "" }
|
||||
// second number
|
||||
local p2 = index_of_from(json, k_typed, p1 + k_typed.length())
|
||||
if p2 < 0 { return "" }
|
||||
local d2 = read_digits(json, p2 + k_typed.length())
|
||||
if d2 == "" { return "" }
|
||||
return _int_to_str(_str_to_int(d1) + _str_to_int(d2))
|
||||
}
|
||||
// Linear pass: sum all numbers outside of quotes (fast, finite)
|
||||
sum_numbers_no_quotes(json) { return new MiniVmScan().sum_numbers_no_quotes(json) }
|
||||
// Naive: sum all digit runs anywhere (for simple BinaryOp JSON)
|
||||
sum_all_digits_naive(json) { return new MiniVmScan().sum_all_digits_naive(json) }
|
||||
// Sum first two integers outside quotes; returns string or empty if not found
|
||||
sum_first_two_numbers(json) { return new MiniVmScan().sum_first_two_numbers(json) }
|
||||
|
||||
// Sum two integers near a BinaryOp '+' token; bounded window to keep steps low
|
||||
sum_two_numbers_near_plus(json) {
|
||||
local k_plus = "\"operator\":\"+\""
|
||||
local op = json.indexOf(k_plus)
|
||||
if op < 0 { return "" }
|
||||
local n = json.length()
|
||||
local start = op - 120
|
||||
if start < 0 { start = 0 }
|
||||
local limit = op + 240
|
||||
if limit > n { limit = n }
|
||||
local i = start
|
||||
local found = 0
|
||||
local a = 0
|
||||
loop (i < limit) {
|
||||
local ch = json.substring(i, i+1)
|
||||
if ch == "\"" {
|
||||
// skip to next quote within window
|
||||
local j = index_of_from(json, "\"", i+1)
|
||||
if j < 0 || j > limit { break }
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
local d = read_digits(json, i)
|
||||
if d {
|
||||
if found == 0 {
|
||||
a = _str_to_int(d)
|
||||
found = 1
|
||||
} else {
|
||||
local b = _str_to_int(d)
|
||||
return _int_to_str(a + b)
|
||||
}
|
||||
i = i + d.length()
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// Fallback: sum all bare numbers (not inside quotes) in the JSON; return string or empty if none
|
||||
sum_all_numbers(json) {
|
||||
local cur = new MiniJson()
|
||||
local i = 0
|
||||
local n = json.length()
|
||||
local sum = 0
|
||||
loop (i < n) {
|
||||
local ch = json.substring(i, i+1)
|
||||
if ch == "\"" {
|
||||
// skip quoted string
|
||||
local s = cur.read_quoted_from(json, i)
|
||||
i = i + s.length() + 2
|
||||
continue
|
||||
}
|
||||
// try digits
|
||||
local d = cur.read_digits_from(json, i)
|
||||
if d != "" { sum = sum + _str_to_int(d) i = i + d.length() continue }
|
||||
i = i + 1
|
||||
}
|
||||
if sum == 0 { return "" }
|
||||
return _int_to_str(sum)
|
||||
}
|
||||
// (reserved) helper for future robust binop scan
|
||||
run(json) {
|
||||
// entry: attempt minimal quick shapes first, then broader routes
|
||||
// Quick path: Program-level Print of a single Literal string/int
|
||||
if json.indexOf("\"kind\":\"Program\"") >= 0 && json.indexOf("\"kind\":\"Print\"") >= 0 {
|
||||
// Literal string
|
||||
if json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\"") >= 0 {
|
||||
local ks = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\""
|
||||
local ps = json.indexOf(ks)
|
||||
if ps >= 0 {
|
||||
local si = ps + ks.length()
|
||||
local sj = json.indexOf("\"", si)
|
||||
if sj >= 0 { print(json.substring(si, sj)) return 0 }
|
||||
}
|
||||
}
|
||||
// Literal int
|
||||
if json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\"") >= 0 {
|
||||
local ki = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local pi = json.indexOf(ki)
|
||||
if pi >= 0 {
|
||||
local ii = pi + ki.length()
|
||||
// digits until closing brace
|
||||
local ie = json.indexOf("}", ii)
|
||||
if ie < 0 { ie = ii }
|
||||
local d = json.substring(ii, ie)
|
||||
if d { print(d) return 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Single-purpose fast path for smoke: if BinaryOp '+' exists, try expression-bounded extractor first.
|
||||
if json.indexOf("\"BinaryOp\"") >= 0 && json.indexOf("\"operator\":\"+\"") >= 0 {
|
||||
// Bind to first Print and extract value×2 within expression bounds
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = index_of_from(json, k_print, 0)
|
||||
if p >= 0 {
|
||||
local np0 = new MiniVmBinOp().try_print_binop_sum_expr_values(json, json.length(), p)
|
||||
if np0 > 0 { return 0 }
|
||||
}
|
||||
// Typed direct inside BinaryOp object (fast and finite)
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos >= 0 {
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local li = index_of_from(json, k_lint, bpos)
|
||||
if li >= 0 {
|
||||
local ld = read_digits(json, li + k_lint.length())
|
||||
if ld != "" {
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local ri = index_of_from(json, k_rint, li + k_lint.length())
|
||||
if ri >= 0 {
|
||||
local rd = read_digits(json, ri + k_rint.length())
|
||||
if rd != "" { print(_int_to_str(_str_to_int(ld) + _str_to_int(rd))) return 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// As a final bounded fallback under BinaryOp '+', sum first two numbers outside quotes
|
||||
{
|
||||
local s2 = sum_first_two_numbers(json)
|
||||
if s2 { print(s2) return 0 }
|
||||
}
|
||||
// (skip near-operator windowed scan to avoid high step counts under PyVM)
|
||||
}
|
||||
// Prefer If(literal) branch handling first
|
||||
local ifc = process_if_once(json)
|
||||
if ifc > 0 { return 0 }
|
||||
// Quick conservative path: if BinaryOp exists, sum bare numbers outside quotes
|
||||
// (limited to simple BinaryOp(int,int) JSON)
|
||||
if json.indexOf("\"BinaryOp\"") >= 0 {
|
||||
// Prefer expression-bounded scan first
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = index_of_from(json, k_print, 0)
|
||||
if p >= 0 {
|
||||
// Deterministic: sum the first two numbers from successive 'value' fields
|
||||
local np0 = new MiniVmBinOp().try_print_binop_sum_expr_values(json, json.length(), p)
|
||||
if np0 > 0 { return 0 }
|
||||
}
|
||||
// Brace-free deterministic fallback tied to the first BinaryOp
|
||||
{
|
||||
local np1 = new MiniVmBinOp().try_print_binop_sum_after_bop(json)
|
||||
if np1 > 0 { return 0 }
|
||||
}
|
||||
// avoid global number-sum fallback to keep steps bounded
|
||||
}
|
||||
// 0) direct typed BinaryOp '+' fast-path (explicit left/right literal ints)
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local k_plus = "\"operator\":\"+\""
|
||||
if json.indexOf(k_bo) >= 0 && json.indexOf(k_plus) >= 0 {
|
||||
local np = try_print_binop_typed_direct(json)
|
||||
if np > 0 { return 0 }
|
||||
np = try_print_binop_typed_tokens(json)
|
||||
if np > 0 { return 0 }
|
||||
np = try_print_binop_value_pairs(json)
|
||||
if np > 0 { return 0 }
|
||||
// (skip bounded-window fallback around '+')
|
||||
}
|
||||
// 0) quick path: BinaryOp(int+int) typed fast-path
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = json.indexOf(k_bo)
|
||||
if bpos >= 0 {
|
||||
// typed left/right ints inside BinaryOp
|
||||
local k_lint = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local li = index_of_from(json, k_lint, bpos)
|
||||
if li >= 0 {
|
||||
local ld = read_digits(json, li + k_lint.length())
|
||||
if ld != "" {
|
||||
local k_rint = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local ri = index_of_from(json, k_rint, li + k_lint.length())
|
||||
if ri >= 0 {
|
||||
local rd = read_digits(json, ri + k_rint.length())
|
||||
if rd != "" {
|
||||
print(_int_to_str(_str_to_int(ld) + _str_to_int(rd)))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback: sum two numeric values within the first Print.expression BinaryOp object
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = index_of_from(json, k_print, 0)
|
||||
if p >= 0 {
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, p)
|
||||
if epos > 0 {
|
||||
local obj_start = index_of_from(json, "{", epos)
|
||||
local obj_end = find_balanced_object_end(json, obj_start)
|
||||
if obj_start > 0 && obj_end > 0 {
|
||||
local k_bo2 = "\"kind\":\"BinaryOp\""
|
||||
local b2 = index_of_from(json, k_bo2, obj_start)
|
||||
if b2 > 0 && b2 < obj_end {
|
||||
local k_v = "\"value\":"
|
||||
local p1 = index_of_from(json, k_v, obj_start)
|
||||
local d1 = ""
|
||||
loop (p1 > 0 && p1 < obj_end) {
|
||||
d1 = new MiniJson().read_digits_from(json, p1 + k_v.length())
|
||||
if d1 != "" { break }
|
||||
p1 = index_of_from(json, k_v, p1 + k_v.length())
|
||||
}
|
||||
if d1 != "" {
|
||||
local p2 = index_of_from(json, k_v, p1 + k_v.length())
|
||||
local d2 = ""
|
||||
loop (p2 > 0 && p2 < obj_end) {
|
||||
d2 = new MiniJson().read_digits_from(json, p2 + k_v.length())
|
||||
if d2 != "" { break }
|
||||
p2 = index_of_from(json, k_v, p2 + k_v.length())
|
||||
}
|
||||
if d2 != "" {
|
||||
local ai = _str_to_int(d1)
|
||||
local bi = _str_to_int(d2)
|
||||
print(_int_to_str(ai + bi))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback: parse-first within BinaryOp scope by scanning two numeric values
|
||||
local ssum = new MiniVmBinOp().parse_first_binop_sum(json)
|
||||
if ssum { print(ssum) return 0 }
|
||||
}
|
||||
// Attempt expression-local BinaryOp sum via existing helper on first Print
|
||||
{
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = index_of_from(json, k_print, 0)
|
||||
if p >= 0 {
|
||||
local np = new MiniVmBinOp().try_print_binop_sum_any(json, json.length(), p)
|
||||
if np > 0 { return 0 }
|
||||
}
|
||||
}
|
||||
// 0-c) quick path: Compare(lhs int, rhs int)
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = json.indexOf(k_cp)
|
||||
if cpos >= 0 {
|
||||
// operation
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = index_of_from(json, k_op, cpos)
|
||||
if opos > 0 {
|
||||
local oi = opos + k_op.length()
|
||||
local oj = index_of_from(json, "\"", oi)
|
||||
if oj > 0 {
|
||||
local op = json.substring(oi, oj)
|
||||
// lhs value
|
||||
local k_lhs = "\"lhs\":{\"kind\":\"Literal\""
|
||||
local hl = index_of_from(json, k_lhs, oj)
|
||||
if hl > 0 {
|
||||
local k_v = "\"value\":"
|
||||
local hv = index_of_from(json, k_v, hl)
|
||||
if hv > 0 {
|
||||
local a = read_digits(json, hv + k_v.length())
|
||||
// rhs value
|
||||
local k_rhs = "\"rhs\":{\"kind\":\"Literal\""
|
||||
local hr = index_of_from(json, k_rhs, hl)
|
||||
if hr > 0 {
|
||||
local rv = index_of_from(json, k_v, hr)
|
||||
if rv > 0 {
|
||||
local b = read_digits(json, rv + k_v.length())
|
||||
if a && b {
|
||||
local ai = _str_to_int(a)
|
||||
local bi = _str_to_int(b)
|
||||
local res = 0
|
||||
if op == "<" { if ai < bi { res = 1 } }
|
||||
if op == "==" { if ai == bi { res = 1 } }
|
||||
if op == "<=" { if ai <= bi { res = 1 } }
|
||||
if op == ">" { if ai > bi { res = 1 } }
|
||||
if op == ">=" { if ai >= bi { res = 1 } }
|
||||
if op == "!=" { if ai != bi { res = 1 } }
|
||||
print(res)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Scan global prints (flat programs)
|
||||
local pc = print_all_print_literals(json)
|
||||
// 2) as a robustness fallback, handle first BinaryOp sum within first Print.expression
|
||||
if pc == 0 {
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = index_of_from(json, k_print, 0)
|
||||
if p >= 0 {
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, p)
|
||||
if epos > 0 {
|
||||
local obj_start = index_of_from(json, "{", epos)
|
||||
local obj_end = find_balanced_object_end(json, obj_start)
|
||||
if obj_start > 0 && obj_end > 0 {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = index_of_from(json, k_bo, obj_start)
|
||||
if bpos > 0 && bpos < obj_end {
|
||||
// sum two numeric values inside this expression object
|
||||
local cur = new MiniJson()
|
||||
local k_v = "\"value\":"
|
||||
local p1 = index_of_from(json, k_v, obj_start)
|
||||
local d1 = ""
|
||||
loop (p1 > 0 && p1 < obj_end) {
|
||||
d1 = cur.read_digits_from(json, p1 + k_v.length())
|
||||
if d1 != "" { break }
|
||||
p1 = index_of_from(json, k_v, p1 + k_v.length())
|
||||
}
|
||||
if d1 != "" {
|
||||
local p2 = index_of_from(json, k_v, p1 + k_v.length())
|
||||
local d2 = ""
|
||||
loop (p2 > 0 && p2 < obj_end) {
|
||||
d2 = cur.read_digits_from(json, p2 + k_v.length())
|
||||
if d2 != "" { break }
|
||||
p2 = index_of_from(json, k_v, p2 + k_v.length())
|
||||
}
|
||||
if d2 != "" {
|
||||
local ai = _str_to_int(d1)
|
||||
local bi = _str_to_int(d2)
|
||||
print(_int_to_str(ai + bi))
|
||||
pc = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if pc == 0 {
|
||||
// last resort: typed pattern-wide sum, then safe number sum outside quotes, else single int literal
|
||||
local s = new MiniVmBinOp().parse_first_binop_sum(json)
|
||||
if s { print(s) } else {
|
||||
local ts = sum_numbers_no_quotes(json)
|
||||
if ts { print(ts) } else {
|
||||
local n = parse_first_int(json)
|
||||
print(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Pure helper: collect minimal print outputs (literals only) into an array
|
||||
collect_prints(json) {
|
||||
// Ported from self-contained smoke (Hardened minimal scanner)
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
local guard = 0
|
||||
// DEV trace: flip to 1 for one-run diagnosis; keep 0 for normal
|
||||
local trace = 0
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 200 { break }
|
||||
local p = index_of_from(json, k_print, pos)
|
||||
if p < 0 { break }
|
||||
// bound current Print slice to [this, next)
|
||||
local obj_start = p
|
||||
local next_p = index_of_from(json, k_print, p + k_print.length())
|
||||
local obj_end = json.length()
|
||||
if next_p > 0 { obj_end = next_p }
|
||||
if trace == 1 { print("[collect][p] "+p) print("[collect][slice_end] "+obj_end) }
|
||||
if trace == 1 {
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos_dbg = index_of_from(json, k_expr, obj_start)
|
||||
print("[scan][expr] "+epos_dbg)
|
||||
local fc_dbg = index_of_from(json, "\"kind\":\"FunctionCall\"", obj_start)
|
||||
print("[scan][fc] "+fc_dbg)
|
||||
local bo_dbg = index_of_from(json, "\"kind\":\"BinaryOp\"", obj_start)
|
||||
print("[scan][bo] "+bo_dbg)
|
||||
local cp_dbg = index_of_from(json, "\"kind\":\"Compare\"", obj_start)
|
||||
print("[scan][cp] "+cp_dbg)
|
||||
local ts_dbg = index_of_from(json, "\"type\":\"string\"", obj_start)
|
||||
print("[scan][ts] "+ts_dbg)
|
||||
local ti_dbg = index_of_from(json, "\"type\":\"int\"", obj_start)
|
||||
print("[scan][ti] "+ti_dbg)
|
||||
// positions for tight patterns used by branches
|
||||
local ks_pat = "\"type\":\"string\",\"value\":\""
|
||||
print("[scan][ks] "+index_of_from(json, ks_pat, obj_start))
|
||||
local ki_pat = "\"type\":\"int\",\"value\":"
|
||||
print("[scan][ki] "+index_of_from(json, ki_pat, obj_start))
|
||||
}
|
||||
|
||||
// 1) FunctionCall echo/itoa (single literal or empty args)
|
||||
{
|
||||
// Limit search within Print.expression object for stability
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, obj_start)
|
||||
if epos > 0 { if epos < obj_end {
|
||||
local expr_start = index_of_from(json, "{", epos)
|
||||
if expr_start > 0 { if expr_start < obj_end {
|
||||
local expr_end = new MiniVmScan().find_balanced_object_end(json, expr_start)
|
||||
if expr_end > 0 { if expr_end <= obj_end {
|
||||
if trace == 1 { print("[collect][expr] "+expr_start+","+expr_end) }
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = index_of_from(json, k_fc, expr_start)
|
||||
if fcp > 0 { if fcp < expr_end {
|
||||
local kn = "\"name\":\""
|
||||
local np = index_of_from(json, kn, fcp)
|
||||
if np > 0 { if np < obj_end {
|
||||
local ni = np + kn.length()
|
||||
local nj = index_of_from(json, "\"", ni)
|
||||
if nj > 0 { if nj <= expr_end {
|
||||
local fname = json.substring(ni, nj)
|
||||
local ka = "\"arguments\":["
|
||||
local ap = index_of_from(json, ka, nj)
|
||||
if ap > 0 { if ap < expr_end {
|
||||
// detect empty args [] quickly: no type token inside balanced array
|
||||
local arr_start = index_of_from(json, "[", ap)
|
||||
local arr_end = new MiniVmScan().find_balanced_array_end(json, arr_start)
|
||||
if arr_start >= 0 { if arr_end >= 0 { if arr_end <= expr_end {
|
||||
local kt = "\"type\":\""
|
||||
local atpos = index_of_from(json, kt, arr_start)
|
||||
if atpos < 0 || atpos >= arr_end {
|
||||
if fname == "echo" { out.push("") pos = obj_end + 1 continue }
|
||||
if fname == "itoa" { out.push("0") pos = obj_end + 1 continue }
|
||||
}
|
||||
}}}
|
||||
// string arg
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, ap)
|
||||
if ps > 0 { if ps < expr_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= expr_end {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { out.push(sval) pos = obj_end + 1 continue }
|
||||
}}
|
||||
}}
|
||||
// int arg
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, ap)
|
||||
if pi > 0 { if pi < expr_end {
|
||||
local ival = read_digits(json, pi + ki.length())
|
||||
if ival != "" { if fname == "itoa" { out.push(ival) pos = obj_end + 1 continue } else { if fname == "echo" { out.push(ival) pos = obj_end + 1 continue } } }
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 2) BinaryOp(int '+' int)
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, obj_start)
|
||||
if epos > 0 { if epos < obj_end {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = index_of_from(json, k_bo, epos)
|
||||
if bpos > 0 { if bpos < obj_end {
|
||||
if index_of_from(json, "\"operator\":\"+\"", bpos) > 0 {
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = index_of_from(json, k_l, bpos)
|
||||
if lp > 0 { if lp < obj_end {
|
||||
local ld = read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = read_digits(json, rp + k_r.length())
|
||||
if rd != "" { if trace == 1 { print("[hit][bo-typed] "+ld+"+"+rd) } out.push(_int_to_str(_str_to_int(ld) + _str_to_int(rd))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
// fallback: two successive 'value' digits within expression bounds
|
||||
local k_v = "\"value\":"
|
||||
local v1 = index_of_from(json, k_v, epos)
|
||||
if v1 > 0 { if v1 < obj_end {
|
||||
local d1 = new MiniJson().read_digits_from(json, v1 + k_v.length())
|
||||
if d1 != "" {
|
||||
local v2 = index_of_from(json, k_v, v1 + k_v.length())
|
||||
if v2 > 0 { if v2 < obj_end {
|
||||
local d2 = new MiniJson().read_digits_from(json, v2 + k_v.length())
|
||||
if d2 != "" { if trace == 1 { print("[hit][bo-fallback] "+d1+"+"+d2) } out.push(_int_to_str(_str_to_int(d1) + _str_to_int(d2))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 3) Compare(lhs/rhs ints)
|
||||
{
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = index_of_from(json, k_cp, obj_start)
|
||||
if cpos > 0 { if cpos < obj_end {
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = index_of_from(json, k_op, cpos)
|
||||
if opos > 0 { if opos < obj_end {
|
||||
local oi = opos + k_op.length()
|
||||
local oj = index_of_from(json, "\"", oi)
|
||||
if oj > 0 { if oj <= obj_end {
|
||||
local op = json.substring(oi, oj)
|
||||
local k_v = "\"value\":"
|
||||
local lhs_v = index_of_from(json, k_v, oj)
|
||||
if lhs_v > 0 { if lhs_v < obj_end {
|
||||
local la = read_digits(json, lhs_v + k_v.length())
|
||||
if la != "" {
|
||||
local rhs_v = index_of_from(json, k_v, lhs_v + k_v.length())
|
||||
if rhs_v > 0 { if rhs_v < obj_end {
|
||||
local rb = read_digits(json, rhs_v + k_v.length())
|
||||
if rb != "" {
|
||||
local ai = _str_to_int(la)
|
||||
local bi = _str_to_int(rb)
|
||||
local res = 0
|
||||
if op == "<" { if ai < bi { res = 1 } }
|
||||
if op == "==" { if ai == bi { res = 1 } }
|
||||
if op == "<=" { if ai <= bi { res = 1 } }
|
||||
if op == ">" { if ai > bi { res = 1 } }
|
||||
if op == ">=" { if ai >= bi { res = 1 } }
|
||||
if op == "!=" { if ai != bi { res = 1 } }
|
||||
out.push(_int_to_str(res))
|
||||
pos = p + k_print.length()
|
||||
continue
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// (FunctionCall branch moved earlier)
|
||||
|
||||
// 4) Literal string
|
||||
{
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, obj_start)
|
||||
if ps > 0 { if ps < obj_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= obj_end {
|
||||
if trace == 1 { print("[hit][str]") }
|
||||
out.push(json.substring(si, sj)) pos = p + k_print.length() continue
|
||||
}}
|
||||
}}
|
||||
}
|
||||
// 5) Literal int
|
||||
{
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, obj_start)
|
||||
if pi > 0 { if pi < obj_end {
|
||||
local digits = read_digits(json, pi + ki.length())
|
||||
if digits != "" { if trace == 1 { print("[hit][i-lit] "+digits) } out.push(digits) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
// Unknown: skip this Print object entirely to avoid stalls and mis-detection
|
||||
// Use coarse slice end (next Print position) when available; fallback to k_print-length step
|
||||
pos = obj_end + 1
|
||||
if pos <= p { pos = p + k_print.length() }
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
// Program entry: prefer argv[0] JSON, fallback to embedded sample
|
||||
501
apps/selfhost/vm/boxes/mini_vm_prints.nyash
Normal file
501
apps/selfhost/vm/boxes/mini_vm_prints.nyash
Normal file
@ -0,0 +1,501 @@
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
using selfhost.vm.binop as MiniVmBinOp
|
||||
using selfhost.vm.compare as MiniVmCompare
|
||||
// Use the JSON adapter facade for cursor ops (next_non_ws, digits)
|
||||
using selfhost.vm.json as MiniJsonLoader
|
||||
|
||||
static box MiniVmPrints {
|
||||
// dev trace flag (0=OFF)
|
||||
_trace_enabled() { return 0 }
|
||||
// fallback toggle for legacy heuristics (0=OFF, 1=ON)
|
||||
_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.length()
|
||||
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 }
|
||||
// robust: look for explicit int type within expression object
|
||||
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.length())
|
||||
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.length()
|
||||
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.length()
|
||||
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.length()
|
||||
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.length())
|
||||
if fname == "itoa" || fname == "echo" { print(digits) return ivalp + k_ival.length() }
|
||||
return -1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// Print all Print-Literal values within [start,end]
|
||||
print_prints_in_slice(json, start, end) {
|
||||
// Preferred route: JSON Box (plugin) – robust and structure-aware
|
||||
// If plugin is available, parse and traverse Program.statements and print.
|
||||
// Fall back to text scanner when plugin is unavailable or parse fails.
|
||||
{
|
||||
// Attempt plugin route in a guarded block
|
||||
@printed = 0
|
||||
@ok = 0
|
||||
// new JsonDocBox()/JsonNodeBox are provided by the JSON plugin
|
||||
@doc = new JsonDocBox()
|
||||
doc.parse(json)
|
||||
@root = doc.root()
|
||||
if root {
|
||||
@stmts = root.get("statements")
|
||||
if stmts {
|
||||
@n = stmts.size()
|
||||
@i = 0
|
||||
loop (i < n) {
|
||||
@node = stmts.at(i)
|
||||
if !node { i = i + 1 continue }
|
||||
@expr = node.get("expression")
|
||||
if !expr { i = i + 1 continue }
|
||||
@k = expr.get("kind").str()
|
||||
if k == "Literal" {
|
||||
@val = expr.get("value")
|
||||
if val {
|
||||
@ty = val.get("type").str()
|
||||
if ty == "string" { print(val.get("value").str()) } else { print(val.get("value").int()) }
|
||||
printed = printed + 1
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if k == "FunctionCall" {
|
||||
@name = expr.get("name").str()
|
||||
@args = expr.get("arguments")
|
||||
if !args { i = i + 1 continue }
|
||||
@asz = args.size()
|
||||
if asz <= 0 {
|
||||
if name == "echo" { print("") printed = printed + 1 }
|
||||
if name == "itoa" { print("0") printed = printed + 1 }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@arg0v = args.at(0).get("value")
|
||||
if name == "echo" {
|
||||
if arg0v {
|
||||
@t = arg0v.get("type").str()
|
||||
if t == "string" { print(arg0v.get("value").str()) } else { print(arg0v.get("value").int()) }
|
||||
printed = printed + 1
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if name == "itoa" {
|
||||
if arg0v { print(arg0v.get("value").int()) printed = printed + 1 }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if k == "Compare" {
|
||||
@op = expr.get("operation").str()
|
||||
@lhs = expr.get("lhs").get("value").get("value").int()
|
||||
@rhs = expr.get("rhs").get("value").get("value").int()
|
||||
@res = 0
|
||||
if op == "<" { if lhs < rhs { res = 1 } }
|
||||
if op == "==" { if lhs == rhs { res = 1 } }
|
||||
if op == "<=" { if lhs <= rhs { res = 1 } }
|
||||
if op == ">" { if lhs > rhs { res = 1 } }
|
||||
if op == ">=" { if lhs >= rhs { res = 1 } }
|
||||
if op == "!=" { if lhs != rhs { res = 1 } }
|
||||
print(res)
|
||||
printed = printed + 1
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if k == "BinaryOp" {
|
||||
@op = expr.get("operator").str()
|
||||
if op == "+" {
|
||||
@left = expr.get("left").get("value").get("value").int()
|
||||
@right = expr.get("right").get("value").get("value").int()
|
||||
print(left + right)
|
||||
printed = printed + 1
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
ok = 1
|
||||
}
|
||||
}
|
||||
// Prefer plugin result whenever JSON route ran (ok==1). Even if printed==0,
|
||||
// return early to avoid falling back to the heuristic scanner which can loop
|
||||
// on malformed inputs or seam-edge cases.
|
||||
if ok == 1 { return printed }
|
||||
}
|
||||
|
||||
// Fallback: text scanner(開発用)
|
||||
local scan = new MiniVmScan()
|
||||
local bin = new MiniVmBinOp()
|
||||
local cmp = new MiniVmCompare()
|
||||
local pos = start
|
||||
local printed = 0
|
||||
local guard = 0
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 200 { break }
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
local p = scan.index_of_from(json, k_print, pos)
|
||||
if p < 0 || p > end { break }
|
||||
// bound current Print object (coarse): use next Print marker as slice end to avoid deep brace scan
|
||||
local p_obj_start = scan.index_of_from(json, "{", p)
|
||||
// coarse slice end by next Print marker
|
||||
local next_p = scan.index_of_from(json, k_print, p + k_print.length())
|
||||
local p_slice_end = end
|
||||
if next_p > 0 { p_slice_end = next_p }
|
||||
// avoid heavy find_balanced_object_end; use coarse p_slice_end-1 as object end
|
||||
local p_obj_end = p_slice_end - 1
|
||||
if p_obj_start <= 0 { p_obj_end = p + k_print.length() }
|
||||
// Fast path: handle within [p, p_slice_end) without deep brace scans
|
||||
{
|
||||
local did = 0
|
||||
// FunctionCall echo/itoa
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = scan.index_of_from(json, k_fc, p)
|
||||
if fcp > 0 { if fcp < p_slice_end {
|
||||
local k_name = "\"name\":\""
|
||||
local npos = scan.index_of_from(json, k_name, fcp)
|
||||
if npos > 0 { if npos < p_slice_end {
|
||||
local ni = npos + k_name.length()
|
||||
local nj = scan.index_of_from(json, "\"", ni)
|
||||
if nj > 0 { if nj <= p_slice_end {
|
||||
local fname = json.substring(ni, nj)
|
||||
local k_args = "\"arguments\":["
|
||||
local apos = scan.index_of_from(json, k_args, nj)
|
||||
if apos > 0 { if apos < p_slice_end {
|
||||
// quick value-based parse first (avoid type walk)
|
||||
{
|
||||
local k_sval = "\"value\":\""
|
||||
local vs = scan.index_of_from(json, k_sval, apos)
|
||||
if vs > 0 { if vs < p_slice_end {
|
||||
local si = vs + k_sval.length()
|
||||
local sj = scan.index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= p_slice_end {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { print(sval) printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}}
|
||||
local k_ival = "\"value\":"
|
||||
local vi = scan.index_of_from(json, k_ival, apos)
|
||||
if vi > 0 { if vi < p_slice_end {
|
||||
local digits = scan.read_digits(json, vi + k_ival.length())
|
||||
if digits != "" { if fname == "itoa" || fname == "echo" { print(digits) printed = printed + 1 did = 1 pos = p_slice_end continue } }
|
||||
}}
|
||||
}
|
||||
// empty args
|
||||
local nwn = new MiniJsonLoader().next_non_ws(json, apos + k_args.length())
|
||||
if nwn == apos + k_args.length() {
|
||||
if fname == "echo" { print("") printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
if fname == "itoa" { print("0") printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}
|
||||
local k_t = "\"type\":\""
|
||||
local atpos = scan.index_of_from(json, k_t, apos)
|
||||
if atpos > 0 { if atpos < p_slice_end {
|
||||
local ati = atpos + k_t.length()
|
||||
local atj = scan.index_of_from(json, "\"", ati)
|
||||
if atj > 0 { if atj <= p_slice_end {
|
||||
local aty = json.substring(ati, atj)
|
||||
if aty == "string" {
|
||||
local k_sval = "\"value\":\""
|
||||
local svalp = scan.index_of_from(json, k_sval, atj)
|
||||
if svalp > 0 { if svalp < p_slice_end {
|
||||
local si = svalp + k_sval.length()
|
||||
local sj = scan.index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= p_slice_end {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { print(sval) printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}}
|
||||
}
|
||||
if aty == "int" || aty == "i64" || aty == "integer" {
|
||||
local k_ival = "\"value\":"
|
||||
local ivalp = scan.index_of_from(json, k_ival, atj)
|
||||
if ivalp > 0 { if ivalp < p_slice_end {
|
||||
local digits = scan.read_digits(json, ivalp + k_ival.length())
|
||||
if fname == "itoa" || fname == "echo" { print(digits) printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
// Compare within slice
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = scan.index_of_from(json, k_cp, p)
|
||||
if cpos > 0 { if cpos < p_slice_end {
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = scan.index_of_from(json, k_op, cpos)
|
||||
if opos > 0 { if opos < p_slice_end {
|
||||
local oi = opos + k_op.length()
|
||||
local oj = scan.index_of_from(json, "\"", oi)
|
||||
if oj > 0 { if oj <= p_slice_end {
|
||||
local op = json.substring(oi, oj)
|
||||
local k_lhs = "\"lhs\":{\"kind\":\"Literal\""
|
||||
local hl = scan.index_of_from(json, k_lhs, oj)
|
||||
if hl > 0 { if hl < p_slice_end {
|
||||
local k_v = "\"value\":"
|
||||
local hv = scan.index_of_from(json, k_v, hl)
|
||||
if hv > 0 { if hv < p_slice_end {
|
||||
local a = scan.read_digits(json, hv + k_v.length())
|
||||
local k_rhs = "\"rhs\":{\"kind\":\"Literal\""
|
||||
local hr = scan.index_of_from(json, k_rhs, hl)
|
||||
if hr > 0 { if hr < p_slice_end {
|
||||
local rv = scan.index_of_from(json, k_v, hr)
|
||||
if rv > 0 { if rv < p_slice_end {
|
||||
local b = scan.read_digits(json, rv + k_v.length())
|
||||
if a && b {
|
||||
local ai = scan._str_to_int(a)
|
||||
local bi = scan._str_to_int(b)
|
||||
local res = 0
|
||||
if op == "<" { if ai < bi { res = 1 } }
|
||||
if op == "==" { if ai == bi { res = 1 } }
|
||||
if op == "<=" { if ai <= bi { res = 1 } }
|
||||
if op == ">" { if ai > bi { res = 1 } }
|
||||
if op == ">=" { if ai >= bi { res = 1 } }
|
||||
if op == "!=" { if ai != bi { res = 1 } }
|
||||
print(res)
|
||||
printed = printed + 1
|
||||
did = 1
|
||||
pos = p_slice_end
|
||||
continue
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
// BinaryOp '+' (typed ints) within slice
|
||||
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", p) > 0 { if scan.index_of_from(json, "\"operator\":\"+\"", p) > 0 {
|
||||
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, p)
|
||||
if lp > 0 { if lp < p_slice_end {
|
||||
local ld = scan.read_digits(json, lp + k_lint.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_rint, lp + k_lint.length())
|
||||
if rp > 0 { if rp < p_slice_end {
|
||||
local rd = scan.read_digits(json, rp + k_rint.length())
|
||||
if rd != "" { print(new MiniVmScan()._int_to_str(new MiniVmScan()._str_to_int(ld) + new MiniVmScan()._str_to_int(rd))) printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
// Literal string within slice
|
||||
{
|
||||
local k_val = "\"value\":\""
|
||||
local s = scan.index_of_from(json, k_val, p)
|
||||
if s > 0 { if s < p_slice_end {
|
||||
local i = s + k_val.length()
|
||||
local j = scan.index_of_from(json, "\"", i)
|
||||
if j > 0 { if j <= p_slice_end {
|
||||
print(json.substring(i, j))
|
||||
printed = printed + 1
|
||||
did = 1
|
||||
pos = p_slice_end
|
||||
continue
|
||||
}}
|
||||
}}
|
||||
}
|
||||
// Literal int within slice
|
||||
{
|
||||
local k_tint = "\"type\":\"int\""
|
||||
local tpos = scan.index_of_from(json, k_tint, p)
|
||||
if tpos > 0 { if tpos < p_slice_end {
|
||||
local k_val2 = "\"value\":"
|
||||
local v2 = scan.index_of_from(json, k_val2, tpos)
|
||||
if v2 > 0 { if v2 < p_slice_end {
|
||||
local digits = scan.read_digits(json, v2 + k_val2.length())
|
||||
if digits != "" { print(digits) printed = printed + 1 did = 1 pos = p_slice_end continue }
|
||||
}}
|
||||
}}
|
||||
}
|
||||
if did == 1 { pos = p_slice_end + 1 continue }
|
||||
}
|
||||
// 0) BinaryOp typed/expr(重スキャン回避のため無効化。必要なら下の軽量パスを使用)
|
||||
// 1) BinaryOp fallbacks(開発用トグル。既定OFF)
|
||||
if (new MiniVmPrints()._fallback_enabled() == 1) {
|
||||
local nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_int_greedy(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
nextp = bin.try_print_binop_sum_any(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// Inline typed sum within this Print BinaryOp('+')
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = scan.index_of_from(json, k_expr, p)
|
||||
if epos > 0 { if epos < p_obj_end {
|
||||
if scan.index_of_from(json, "\"kind\":\"BinaryOp\"", epos) > 0 {
|
||||
if scan.index_of_from(json, "\"operator\":\"+\"", epos) > 0 {
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = scan.index_of_from(json, k_l, epos)
|
||||
if lp > 0 { if lp < p_obj_end {
|
||||
local ld = scan.read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = scan.index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < p_obj_end {
|
||||
local rd = scan.read_digits(json, rp + k_r.length())
|
||||
if rd != "" { print(new MiniVmScan()._int_to_str(new MiniVmScan()._str_to_int(ld) + new MiniVmScan()._str_to_int(rd))) printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
// 2) Compare
|
||||
nextp = cmp.try_print_compare_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// 3) FunctionCall(既定: 軽量スライス限定パスで処理済み。重い版は呼ばない)
|
||||
// nextp = new MiniVmPrints().try_print_functioncall_at(json, end, p)
|
||||
// if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// 4) literal string
|
||||
nextp = new MiniVmPrints().try_print_string_value_at(json, end, p)
|
||||
if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// 5) literal int via type(重走査を避けるため無効化。スライス限定の軽量パスで処理済み)
|
||||
// nextp = new MiniVmPrints().try_print_int_value_at(json, end, p)
|
||||
// if nextp > 0 { printed = printed + 1 pos = p_obj_end + 1 continue }
|
||||
// 5b) literal int(簡易フォールバック;既定OFF)
|
||||
if (new MiniVmPrints()._fallback_enabled() == 1) {
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = scan.index_of_from(json, ki, p)
|
||||
if pi > 0 { if pi < p_slice_end {
|
||||
local digits = scan.read_digits(json, pi + ki.length())
|
||||
if digits != "" { print(digits) printed = printed + 1 pos = p_slice_end continue }
|
||||
}}
|
||||
// Unknown shape: skip this Print object entirely to avoid stalls
|
||||
pos = p_obj_end + 1
|
||||
if pos <= p { pos = p + k_print.length() }
|
||||
} else {
|
||||
// 既定は最小前進(次の探索へ)
|
||||
pos = p + k_print.length()
|
||||
}
|
||||
}
|
||||
return printed
|
||||
}
|
||||
|
||||
// Process top-level If with literal condition; print branch prints. Returns printed count.
|
||||
process_if_once(json) {
|
||||
local scan = new MiniVmScan()
|
||||
local k_if = "\"kind\":\"If\""
|
||||
local p = scan.index_of_from(json, k_if, 0)
|
||||
if p < 0 { return 0 }
|
||||
local k_cond = "\"condition\""
|
||||
local cpos = scan.index_of_from(json, k_cond, p)
|
||||
if cpos < 0 { return 0 }
|
||||
local k_val = "\"value\":"
|
||||
local vpos = scan.index_of_from(json, k_val, cpos)
|
||||
if vpos < 0 { return 0 }
|
||||
local val_digits = scan.read_digits(json, vpos + k_val.length())
|
||||
local truthy = 0
|
||||
if val_digits { if val_digits != "0" { truthy = 1 } }
|
||||
local k_then = "\"then_body\""
|
||||
local k_else = "\"else_body\""
|
||||
local bkey = k_then
|
||||
if truthy == 0 { bkey = k_else }
|
||||
local bpos = scan.index_of_from(json, bkey, cpos)
|
||||
if bpos < 0 { return 0 }
|
||||
local arr_start = scan.index_of_from(json, "[", bpos)
|
||||
if arr_start < 0 { return 0 }
|
||||
local arr_end = new MiniVmScan().find_balanced_array_end(json, arr_start)
|
||||
if arr_end < 0 { return 0 }
|
||||
return new MiniVmPrints().print_prints_in_slice(json, arr_start, arr_end)
|
||||
}
|
||||
|
||||
// Print all Print-Literal values in Program.statements (string/int only; MVP)
|
||||
print_all_print_literals(json) {
|
||||
return new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
}
|
||||
}
|
||||
249
apps/selfhost/vm/boxes/seam_inspector.nyash
Normal file
249
apps/selfhost/vm/boxes/seam_inspector.nyash
Normal file
@ -0,0 +1,249 @@
|
||||
// SeamInspector — analyze inlined code seam and duplicates
|
||||
// Usage: import and call report(text) or analyze_dump_file(path)
|
||||
|
||||
static box SeamInspector {
|
||||
// Count brace delta ignoring simple strings ("...") and escapes
|
||||
_brace_delta_ignoring_strings(text, start, end) {
|
||||
@i = start
|
||||
@n = end
|
||||
@delta = 0
|
||||
loop (i < n) {
|
||||
@ch = text.substring(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
|
||||
}
|
||||
|
||||
// Find index of exact token in plain text from position
|
||||
_index_of_from(h, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
@n = h.length()
|
||||
if pos >= n { return -1 }
|
||||
@m = needle.length()
|
||||
if m <= 0 { return pos }
|
||||
@i = pos
|
||||
@limit = n - m
|
||||
loop (i <= limit) {
|
||||
if h.substring(i, i+m) == needle { return i }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Find matching closing brace starting at '{'
|
||||
_find_balanced_object_end(text, idx) {
|
||||
if text.substring(idx, idx+1) != "{" { return -1 }
|
||||
@i = idx
|
||||
@n = text.length()
|
||||
@depth = 0
|
||||
loop (i < n) {
|
||||
@ch = text.substring(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 == "{" { depth = depth + 1 }
|
||||
if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Scan boxes: return array of {name, start, end}
|
||||
_scan_boxes(text) {
|
||||
@i = 0
|
||||
@n = text.length()
|
||||
@res = new ArrayBox()
|
||||
@tok = "static box "
|
||||
loop (i < n) {
|
||||
@p = _index_of_from(text, tok, i)
|
||||
if p < 0 { break }
|
||||
@j = p + tok.length()
|
||||
// read identifier
|
||||
@name = ""
|
||||
loop (j < n) {
|
||||
@c = text.substring(j, j+1)
|
||||
// ASCII alpha-num or '_'
|
||||
if c == "_" { name = name + c j = j + 1 continue }
|
||||
// digits
|
||||
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 }
|
||||
// letters
|
||||
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c j = j + 1 continue }
|
||||
break
|
||||
}
|
||||
// skip to '{'
|
||||
loop (j < n) {
|
||||
@c2 = text.substring(j, j+1)
|
||||
if c2 == "{" { break }
|
||||
j = j + 1
|
||||
}
|
||||
@end = _find_balanced_object_end(text, j)
|
||||
if end < 0 { end = j }
|
||||
@obj = new MapBox()
|
||||
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
|
||||
}
|
||||
|
||||
// Print duplicate boxes by name
|
||||
_report_duplicate_boxes(text) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@cnt = new MapBox()
|
||||
@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
|
||||
}
|
||||
|
||||
// Inside a given box, count function name duplicates (simple scan: name(...){ )
|
||||
_report_duplicate_functions_in_box(text, box_name) {
|
||||
@boxes = _scan_boxes(text)
|
||||
@i = 0
|
||||
@fnmap = new MapBox()
|
||||
@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) {
|
||||
// find identifier start at line head-ish (naive)
|
||||
// pattern: <spaces> ident '(' ... '{'
|
||||
@k = j
|
||||
// skip spaces/newlines
|
||||
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 }
|
||||
// read ident
|
||||
@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
|
||||
}
|
||||
// must be a function definition: ident '(' ... ')' ws* '{'
|
||||
if text.substring(p, p+1) == "(" {
|
||||
// find matching ')' with a simple counter (strings ignored for simplicity)
|
||||
@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 == "\"" {
|
||||
// skip string inside params
|
||||
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
|
||||
}
|
||||
// skip ws
|
||||
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
|
||||
}
|
||||
// definition only if next is '{'
|
||||
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)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// advance to next line
|
||||
@nl = _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 summary
|
||||
report(text) {
|
||||
// find Main
|
||||
@m = _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))
|
||||
|
||||
// duplicate boxes
|
||||
_report_duplicate_boxes(text)
|
||||
|
||||
// specific hot-spot
|
||||
_report_duplicate_functions_in_box(text, "MiniVmPrints")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Load dump file and report
|
||||
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)
|
||||
}
|
||||
}
|
||||
59
apps/selfhost/vm/collect_empty_args_smoke.nyash
Normal file
59
apps/selfhost/vm/collect_empty_args_smoke.nyash
Normal file
@ -0,0 +1,59 @@
|
||||
// Self-contained dev smoke for FunctionCall empty-args
|
||||
// Goal: echo() -> empty line, itoa() -> 0
|
||||
|
||||
static box MiniVm {
|
||||
// simple substring find from position
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
}
|
||||
|
||||
// collect only FunctionCall with empty arguments [] for echo/itoa
|
||||
collect_prints(json) {
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
local guard = 0
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 16 { break }
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local p = index_of_from(json, k_fc, pos)
|
||||
if p < 0 { break }
|
||||
// name
|
||||
local k_n = "\"name\":\""
|
||||
local np = index_of_from(json, k_n, p)
|
||||
if np < 0 { break }
|
||||
local ni = np + k_n.length()
|
||||
local nj = index_of_from(json, "\"", ni)
|
||||
if nj < 0 { break }
|
||||
local fname = json.substring(ni, nj)
|
||||
// args [] detection
|
||||
local k_a = "\"arguments\":["
|
||||
local ap = index_of_from(json, k_a, nj)
|
||||
if ap < 0 { break }
|
||||
local rb = index_of_from(json, "]", ap)
|
||||
if rb < 0 { break }
|
||||
// no content between '[' and ']'
|
||||
if rb == ap + k_a.length() {
|
||||
if fname == "echo" { out.push("") }
|
||||
if fname == "itoa" { out.push("0") }
|
||||
}
|
||||
pos = rb + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
15
apps/selfhost/vm/collect_empty_args_using_smoke.nyash
Normal file
15
apps/selfhost/vm/collect_empty_args_using_smoke.nyash
Normal file
@ -0,0 +1,15 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":["
|
||||
+ "{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},"
|
||||
+ "{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
30
apps/selfhost/vm/collect_literal_eval.nyash
Normal file
30
apps/selfhost/vm/collect_literal_eval.nyash
Normal file
@ -0,0 +1,30 @@
|
||||
// Minimal self-contained eval (no using): collect the first Print(Literal int) and print it
|
||||
static box Main {
|
||||
// helper: find from position
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 } else { return pos + rel }
|
||||
}
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
// naive collect: first Print of Literal int
|
||||
local ki = "\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local p = json.indexOf(ki)
|
||||
if p >= 0 {
|
||||
local i = p + ki.length()
|
||||
local j = i
|
||||
loop (true) {
|
||||
local ch = json.substring(j, j+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" { j = j + 1 continue }
|
||||
break
|
||||
}
|
||||
local digits = json.substring(i, j)
|
||||
if digits { print(digits) }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
282
apps/selfhost/vm/collect_mixed_smoke.nyash
Normal file
282
apps/selfhost/vm/collect_mixed_smoke.nyash
Normal file
@ -0,0 +1,282 @@
|
||||
static box Main {
|
||||
// --- minimal helpers (self-contained) ---
|
||||
index_of_from(hay, needle, pos) {
|
||||
if pos < 0 { pos = 0 }
|
||||
if pos >= hay.length() { return -1 }
|
||||
local tail = hay.substring(pos, hay.length())
|
||||
local rel = tail.indexOf(needle)
|
||||
if rel < 0 { return -1 }
|
||||
return pos + rel
|
||||
}
|
||||
_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
|
||||
}
|
||||
read_digits(s, pos) {
|
||||
if pos < 0 { return "" }
|
||||
local n = s.length()
|
||||
if pos >= n { return "" }
|
||||
local i = pos
|
||||
local out_start = -1
|
||||
loop (i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if _is_digit(ch) == 1 { if out_start < 0 { out_start = i } i = i + 1 continue } else { break }
|
||||
}
|
||||
if out_start < 0 { return "" }
|
||||
return s.substring(out_start, i)
|
||||
}
|
||||
find_balanced_object_end(json, idx) {
|
||||
if idx < 0 { return -1 }
|
||||
local n = json.length()
|
||||
if idx >= n { return -1 }
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local i = idx
|
||||
local iter = 0
|
||||
loop (i < n) {
|
||||
iter = iter + 1
|
||||
if iter > 5000 { return -1 }
|
||||
local ch = json.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if ch == "\\" { i = i + 2 continue }
|
||||
if ch == "\"" { in_str = 0 }
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if ch == "\"" { in_str = 1 i = i + 1 continue }
|
||||
if ch == "{" { depth = depth + 1 }
|
||||
if ch == "}" { depth = depth - 1 if depth == 0 { return i + 1 } }
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// --- core: collect Print outputs in order ---
|
||||
collect_prints(json) {
|
||||
local out = new ArrayBox()
|
||||
local pos = 0
|
||||
local guard = 0
|
||||
local k_print = "\"kind\":\"Print\""
|
||||
loop (true) {
|
||||
guard = guard + 1
|
||||
if guard > 200 { break }
|
||||
local p = index_of_from(json, k_print, pos)
|
||||
if p < 0 { break }
|
||||
// bound current Print slice as [current_print, next_print)
|
||||
local obj_start = p
|
||||
local next_p = index_of_from(json, k_print, p + k_print.length())
|
||||
local obj_end = json.length()
|
||||
if next_p > 0 { obj_end = next_p }
|
||||
|
||||
// 1) BinaryOp(int '+' int)
|
||||
{
|
||||
local k_expr = "\"expression\":{"
|
||||
local epos = index_of_from(json, k_expr, obj_start)
|
||||
if epos > 0 { if epos < obj_end {
|
||||
local k_bo = "\"kind\":\"BinaryOp\""
|
||||
local bpos = index_of_from(json, k_bo, epos)
|
||||
if bpos > 0 { if bpos < obj_end {
|
||||
local opok = index_of_from(json, "\"operator\":\"+\"", bpos)
|
||||
if opok > 0 {
|
||||
// typed left/right literal ints
|
||||
local k_l = "\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local k_r = "\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":"
|
||||
local lp = index_of_from(json, k_l, bpos)
|
||||
if lp > 0 { if lp < obj_end {
|
||||
local ld = read_digits(json, lp + k_l.length())
|
||||
if ld != "" {
|
||||
local rp = index_of_from(json, k_r, lp + k_l.length())
|
||||
if rp > 0 { if rp < obj_end {
|
||||
local rd = read_digits(json, rp + k_r.length())
|
||||
if rd != "" { out.push(_int_to_str(_str_to_int(ld) + _str_to_int(rd))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
// fallback: scan two successive '"value":' digits within expression bounds
|
||||
local k_v = "\"value\":"
|
||||
local v1 = index_of_from(json, k_v, epos)
|
||||
if v1 > 0 { if v1 < obj_end {
|
||||
local d1 = read_digits(json, v1 + k_v.length())
|
||||
if d1 != "" {
|
||||
local v2 = index_of_from(json, k_v, v1 + k_v.length())
|
||||
if v2 > 0 { if v2 < obj_end {
|
||||
local d2 = read_digits(json, v2 + k_v.length())
|
||||
if d2 != "" { out.push(_int_to_str(_str_to_int(d1) + _str_to_int(d2))) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 2) Compare(lhs/rhs ints): prints 1/0
|
||||
{
|
||||
local k_cp = "\"kind\":\"Compare\""
|
||||
local cpos = index_of_from(json, k_cp, obj_start)
|
||||
if cpos > 0 { if cpos < obj_end {
|
||||
local k_op = "\"operation\":\""
|
||||
local opos = index_of_from(json, k_op, cpos)
|
||||
if opos > 0 { if opos < obj_end {
|
||||
local oi = opos + k_op.length()
|
||||
local oj = index_of_from(json, "\"", oi)
|
||||
if oj > 0 { if oj <= obj_end {
|
||||
local op = json.substring(oi, oj)
|
||||
local k_v = "\"value\":"
|
||||
local lhs_v = index_of_from(json, k_v, oj)
|
||||
if lhs_v > 0 { if lhs_v < obj_end {
|
||||
local la = read_digits(json, lhs_v + k_v.length())
|
||||
if la != "" {
|
||||
local rhs_v = index_of_from(json, k_v, lhs_v + k_v.length())
|
||||
if rhs_v > 0 { if rhs_v < obj_end {
|
||||
local rb = read_digits(json, rhs_v + k_v.length())
|
||||
if rb != "" {
|
||||
local ai = _str_to_int(la)
|
||||
local bi = _str_to_int(rb)
|
||||
local res = 0
|
||||
if op == "<" { if ai < bi { res = 1 } }
|
||||
if op == "==" { if ai == bi { res = 1 } }
|
||||
if op == "<=" { if ai <= bi { res = 1 } }
|
||||
if op == ">" { if ai > bi { res = 1 } }
|
||||
if op == ">=" { if ai >= bi { res = 1 } }
|
||||
if op == "!=" { if ai != bi { res = 1 } }
|
||||
out.push(_int_to_str(res))
|
||||
pos = p + k_print.length()
|
||||
continue
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 3) FunctionCall echo/itoa (single literal arg)
|
||||
{
|
||||
local k_fc = "\"kind\":\"FunctionCall\""
|
||||
local fcp = index_of_from(json, k_fc, obj_start)
|
||||
if fcp > 0 { if fcp < obj_end {
|
||||
local kn = "\"name\":\""
|
||||
local np = index_of_from(json, kn, fcp)
|
||||
if np > 0 { if np < obj_end {
|
||||
local ni = np + kn.length()
|
||||
local nj = index_of_from(json, "\"", ni)
|
||||
if nj > 0 { if nj <= obj_end {
|
||||
local fname = json.substring(ni, nj)
|
||||
local ka = "\"arguments\":["
|
||||
local ap = index_of_from(json, ka, nj)
|
||||
if ap > 0 { if ap < obj_end {
|
||||
// string arg
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, ap)
|
||||
if ps > 0 { if ps < obj_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= obj_end {
|
||||
local sval = json.substring(si, sj)
|
||||
if fname == "echo" { out.push(sval) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}}
|
||||
// int arg
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, ap)
|
||||
if pi > 0 { if pi < obj_end {
|
||||
local ival = read_digits(json, pi + ki.length())
|
||||
if ival != "" { if fname == "itoa" { out.push(ival) pos = p + k_print.length() continue } else { if fname == "echo" { out.push(ival) pos = p + k_print.length() continue } } }
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}
|
||||
|
||||
// 4) Literal string
|
||||
{
|
||||
local ks = "\"type\":\"string\",\"value\":\""
|
||||
local ps = index_of_from(json, ks, obj_start)
|
||||
if ps > 0 { if ps < obj_end {
|
||||
local si = ps + ks.length()
|
||||
local sj = index_of_from(json, "\"", si)
|
||||
if sj > 0 { if sj <= obj_end { out.push(json.substring(si, sj)) pos = p + k_print.length() continue }}
|
||||
}}
|
||||
}
|
||||
// 5) Literal int
|
||||
{
|
||||
local ki = "\"type\":\"int\",\"value\":"
|
||||
local pi = index_of_from(json, ki, obj_start)
|
||||
if pi > 0 { if pi < obj_end {
|
||||
local digits = read_digits(json, pi + ki.length())
|
||||
if digits != "" { out.push(digits) pos = p + k_print.length() continue }
|
||||
}}
|
||||
}
|
||||
// Unknown: skip ahead
|
||||
pos = p + k_print.length()
|
||||
if pos <= p { pos = p + 1 }
|
||||
}
|
||||
return out
|
||||
}
|
||||
// int<->str (non-negative only)
|
||||
_str_to_int(s) {
|
||||
local n = 0
|
||||
local i = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
n = n * 10
|
||||
if ch == "0" { n = n + 0 }
|
||||
if ch == "1" { n = n + 1 }
|
||||
if ch == "2" { n = n + 2 }
|
||||
if ch == "3" { n = n + 3 }
|
||||
if ch == "4" { n = n + 4 }
|
||||
if ch == "5" { n = n + 5 }
|
||||
if ch == "6" { n = n + 6 }
|
||||
if ch == "7" { n = n + 7 }
|
||||
if ch == "8" { n = n + 8 }
|
||||
if ch == "9" { n = n + 9 }
|
||||
i = i + 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
_int_to_str(n) {
|
||||
if n == 0 { return "0" }
|
||||
local s = new ArrayBox()
|
||||
local x = n
|
||||
loop (x > 0) {
|
||||
local d = x % 10
|
||||
if d == 0 { s.push("0") }
|
||||
if d == 1 { s.push("1") }
|
||||
if d == 2 { s.push("2") }
|
||||
if d == 3 { s.push("3") }
|
||||
if d == 4 { s.push("4") }
|
||||
if d == 5 { s.push("5") }
|
||||
if d == 6 { s.push("6") }
|
||||
if d == 7 { s.push("7") }
|
||||
if d == 8 { s.push("8") }
|
||||
if d == 9 { s.push("9") }
|
||||
x = (x - d) / 10
|
||||
}
|
||||
local out = new ArrayBox()
|
||||
local i = s.size() - 1
|
||||
loop (i >= 0) { out.push(s.get(i)) i = i - 1 }
|
||||
local j = 0
|
||||
local acc = ""
|
||||
loop (j < out.size()) { acc = acc + out.get(j) j = j + 1 }
|
||||
return acc
|
||||
}
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
|
||||
local arr = collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
11
apps/selfhost/vm/collect_mixed_using_smoke.nyash
Normal file
11
apps/selfhost/vm/collect_mixed_using_smoke.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
|
||||
new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
18
apps/selfhost/vm/collect_prints_loader_smoke.nyash
Normal file
18
apps/selfhost/vm/collect_prints_loader_smoke.nyash
Normal file
@ -0,0 +1,18 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// Mixed shapes (loader-centric)
|
||||
// 1) echo() with empty args -> ""
|
||||
// 2) string literal with quotes inside
|
||||
// 3) itoa() with empty args -> "0"
|
||||
// 4) BinaryOp 8+9
|
||||
// 5) int literal 4
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"C\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":8}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":9}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
51
apps/selfhost/vm/json_loader.nyash
Normal file
51
apps/selfhost/vm/json_loader.nyash
Normal file
@ -0,0 +1,51 @@
|
||||
// MiniJsonLoader (Stage-B scaffold)
|
||||
// Purpose: centralize minimal JSON cursor ops for Mini-VM.
|
||||
// Implementation note: For now we delegate to local MiniJsonCur-compatible
|
||||
// helpers. In a later step, this can be swapped to use `apps/libs/json_cur.nyash`
|
||||
// (JsonCursorBox) without touching Mini-VM call sites.
|
||||
|
||||
static box MiniJsonLoader {
|
||||
read_quoted_from(s, pos) {
|
||||
// Local fallback (same behavior as MiniJsonCur.read_quoted_from)
|
||||
// Keep in sync with Mini-VM until libs adoption gate is enabled.
|
||||
local i = pos
|
||||
if s.substring(i, i+1) != "\"" { return "" }
|
||||
i = i + 1
|
||||
local out = ""
|
||||
local n = s.length()
|
||||
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_digits_from(s, pos) {
|
||||
local out = ""
|
||||
local i = pos
|
||||
if i == null { return out }
|
||||
if i < 0 { return out }
|
||||
loop (true) {
|
||||
local ch = s.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 }
|
||||
}
|
||||
return out
|
||||
}
|
||||
next_non_ws(s, pos) {
|
||||
local i = pos
|
||||
local n = s.length()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/selfhost/vm/mini_vm.nyash
Normal file
12
apps/selfhost/vm/mini_vm.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
// Thin entry: delegate to core MiniVm
|
||||
// Using is pre-inlined by runner; keep entry minimal for maintainability.
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
if args { if args.size() > 0 { @s = args.get(0) if s { json = s } } }
|
||||
return new MiniVm().run(json)
|
||||
}
|
||||
}
|
||||
|
||||
59
apps/selfhost/vm/mini_vm_if_branch.nyash
Normal file
59
apps/selfhost/vm/mini_vm_if_branch.nyash
Normal file
@ -0,0 +1,59 @@
|
||||
// Mini-VM: function-based entry for branching
|
||||
// Local static box (duplicated from mini_vm_lib for now to avoid include gate issues)
|
||||
static box MiniVm {
|
||||
_is_digit(ch) { return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" }
|
||||
read_digits(json, pos) {
|
||||
@out = ""
|
||||
loop (true) {
|
||||
@s = json.substring(pos, pos+1)
|
||||
if s == "" { break }
|
||||
if _is_digit(s) { out = out + s pos = pos + 1 } else { break }
|
||||
}
|
||||
return out
|
||||
}
|
||||
parse_first_int(json) {
|
||||
@key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
@idx = json.lastIndexOf(key)
|
||||
if idx < 0 { return "0" }
|
||||
@start = idx + key.length()
|
||||
return read_digits(json, start)
|
||||
}
|
||||
run_branch(json) {
|
||||
@n = parse_first_int(json)
|
||||
if n == "0" || n == "1" || n == "2" || n == "3" || n == "4" { print("10") return 0 }
|
||||
print("20")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Program entry: embedded JSON (value=1 → print 10; else → 20)
|
||||
static box Main {
|
||||
main(args) {
|
||||
@vm = new MiniVm()
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
// If provided, override by argv[0]
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
@s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
return vm.run_branch(json)
|
||||
}
|
||||
}
|
||||
|
||||
// Top-level fallback entry for current runner
|
||||
function main(args) {
|
||||
@vm = new MiniVm()
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}}}]}"
|
||||
if args {
|
||||
if args.size() > 0 {
|
||||
@s = args.get(0)
|
||||
if s { json = s }
|
||||
}
|
||||
}
|
||||
@n = vm.parse_first_int(json)
|
||||
if n == "0" || n == "1" || n == "2" || n == "3" || n == "4" { print("10") return 0 }
|
||||
print("20")
|
||||
return 0
|
||||
}
|
||||
33
apps/selfhost/vm/mini_vm_lib.nyash
Normal file
33
apps/selfhost/vm/mini_vm_lib.nyash
Normal file
@ -0,0 +1,33 @@
|
||||
// Mini-VM library (function-based) with a tiny JSON extractor
|
||||
// Safe MVP: no real JSON parsing; string scan for first int literal only
|
||||
|
||||
static box MiniVm {
|
||||
_is_digit(ch) { return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" }
|
||||
|
||||
// Read consecutive digits starting at pos
|
||||
read_digits(json, pos) {
|
||||
@out = ""
|
||||
loop (true) {
|
||||
@s = json.substring(pos, pos+1)
|
||||
if s == "" { break }
|
||||
if _is_digit(s) { out = out + s pos = pos + 1 } else { break }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Extract the first integer literal from our AST JSON v0 subset
|
||||
parse_first_int(json) {
|
||||
@key = "\"value\":{\"type\":\"int\",\"value\":"
|
||||
@idx = json.lastIndexOf(key)
|
||||
if idx < 0 { return "0" }
|
||||
@start = idx + key.length()
|
||||
return read_digits(json, start)
|
||||
}
|
||||
|
||||
// Execute a minimal program: print the extracted integer and exit code 0
|
||||
run(json) {
|
||||
@n = parse_first_int(json)
|
||||
print(n)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
16
apps/selfhost/vm/run_core_wrapper.nyash
Normal file
16
apps/selfhost/vm/run_core_wrapper.nyash
Normal file
@ -0,0 +1,16 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
@json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
if args { if args.size() > 0 { @s = args.get(0) if s { json = s } } }
|
||||
print("pre")
|
||||
print(json.length())
|
||||
print(json.indexOf("\"kind\":\"Program\""))
|
||||
print(json.indexOf("\"kind\":\"Print\""))
|
||||
print(json.indexOf("\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\""))
|
||||
@code = new MiniVm().run(json)
|
||||
print("post")
|
||||
return code
|
||||
}
|
||||
}
|
||||
11
apps/tests/dev_inline_binop_probe.nyash
Normal file
11
apps/tests/dev_inline_binop_probe.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// Single Print(BinaryOp '+')
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}}]}"
|
||||
new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/tests/dev_json_digits_from.nyash
Normal file
12
apps/tests/dev_json_digits_from.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
using selfhost.vm.json as MiniJson
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
local k = "\"value\":"
|
||||
local p = json.indexOf(k)
|
||||
print(new MiniJson().read_digits_from(json, p + k.length()))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
9
apps/tests/dev_parse_include_prints.nyash
Normal file
9
apps/tests/dev_parse_include_prints.nyash
Normal file
@ -0,0 +1,9 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// do nothing; this just forces including MiniVmPrints
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/tests/dev_prints_count_probe.nyash
Normal file
11
apps/tests/dev_prints_count_probe.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
local n = new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
print(n)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/tests/dev_prints_probe.nyash
Normal file
10
apps/tests/dev_prints_probe.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
new MiniVmPrints().print_all_print_literals(json)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/tests/dev_scan_digits.nyash
Normal file
10
apps/tests/dev_scan_digits.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local s = "xx42yy"
|
||||
print(new MiniVmScan().read_digits(s, 2))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
13
apps/tests/dev_scan_prints.nyash
Normal file
13
apps/tests/dev_scan_prints.nyash
Normal file
@ -0,0 +1,13 @@
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"B\"}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":7}}]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Compare\",\"operation\":\"<\",\"lhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":1}},\"rhs\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":2}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"BinaryOp\",\"operator\":\"+\",\"left\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":3}},\"right\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":4}}}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":5}}}]}"
|
||||
local scan = new MiniVmScan()
|
||||
local p = scan.index_of_from(json, "\"kind\":\"Print\"", 0)
|
||||
// Directly invoke string literal printer on the first Print
|
||||
new MiniVmPrints().try_print_string_value_at(json, json.length(), p)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
10
apps/tests/dev_scan_prints_int_slice.nyash
Normal file
10
apps/tests/dev_scan_prints_int_slice.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/tests/dev_scan_prints_slice.nyash
Normal file
10
apps/tests/dev_scan_prints_slice.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}}]}"
|
||||
new MiniVmPrints().print_prints_in_slice(json, 0, json.length())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/tests/dev_scan_probe.nyash
Normal file
11
apps/tests/dev_scan_probe.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"string\",\"value\":\"A\"}}}]}"
|
||||
local p = new MiniVmScan().index_of_from(json, "\"kind\":\"Print\"", 0)
|
||||
print(p)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/tests/dev_seam_inspect_dump.nyash
Normal file
10
apps/tests/dev_seam_inspect_dump.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.seam as SeamInspector
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// Analyze PyVM dumped code (requires NYASH_PYVM_DUMP_CODE=1 to have produced tmp/pyvm_dump.txt)
|
||||
new SeamInspector().analyze_dump_file("tmp/pyvm_dump.txt")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
8
apps/tests/dev_sugar/at_local_basic.nyash
Normal file
8
apps/tests/dev_sugar/at_local_basic.nyash
Normal file
@ -0,0 +1,8 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
@x = 1
|
||||
print(x)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/tests/dev_sugar/compound_and_inc.nyash
Normal file
12
apps/tests/dev_sugar/compound_and_inc.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
static box Main {
|
||||
main(args) {
|
||||
@i = 0
|
||||
i++
|
||||
i += 2
|
||||
i *= 3
|
||||
i -= 1
|
||||
i /= 2
|
||||
print(i)
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/tests/dev_sugar/print_when_fn.nyash
Normal file
11
apps/tests/dev_sugar/print_when_fn.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
// print! and when sugar test
|
||||
static box Main {
|
||||
main(args) {
|
||||
@v = 41
|
||||
when v + 1 == 42 {
|
||||
print! 42
|
||||
} else {
|
||||
print!(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
apps/tests/dev_try_int_value.nyash
Normal file
10
apps/tests/dev_try_int_value.nyash
Normal file
@ -0,0 +1,10 @@
|
||||
using selfhost.vm.prints as MiniVmPrints
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"Literal\",\"value\":{\"type\":\"int\",\"value\":42}}}]}"
|
||||
new MiniVmPrints().try_print_int_value_at(json, json.length(), 0)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
13
apps/tests/mini_vm_functioncall_empty_args.nyash
Normal file
13
apps/tests/mini_vm_functioncall_empty_args.nyash
Normal file
@ -0,0 +1,13 @@
|
||||
using selfhost.vm.core as MiniVm
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
// Program with empty-args echo() and itoa() to exercise loader-path fallback
|
||||
local json = "{\"kind\":\"Program\",\"statements\":[{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"echo\",\"arguments\":[]}},{\"kind\":\"Print\",\"expression\":{\"kind\":\"FunctionCall\",\"name\":\"itoa\",\"arguments\":[]}}]}"
|
||||
|
||||
local arr = new MiniVm().collect_prints(json)
|
||||
local i = 0
|
||||
loop (i < arr.size()) { print(arr.get(i)) i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
11
apps/tests/using_edge_mix.nyash
Normal file
11
apps/tests/using_edge_mix.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
using selfhost.vm.scan as MiniVmScan
|
||||
|
||||
static box Main {
|
||||
main(args) {
|
||||
local s = "abc"
|
||||
// twice to check stability
|
||||
print(new MiniVmScan().index_of_from(s, "b", 0))
|
||||
print(new MiniVmScan().index_of_from(s, "b", 0))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user