refactor(mir): phase260 p0.1 strangler hardening + smoke fixtures

This commit is contained in:
2025-12-21 05:47:37 +09:00
parent 4dfe3349bf
commit 1fe5be347d
28 changed files with 442 additions and 504 deletions

View File

@ -2,6 +2,22 @@
using StringUtils as StringUtils
static box Main {
method _contains(valid, s, j) {
if j >= valid.length() { return 0 }
if s == valid.get(j) { return 1 }
return me._contains(valid, s, j + 1)
}
method _check_cases(cases, valid, i) {
if i >= cases.length() { return 0 }
local s = "" + cases.get(i) // normalize to string
local ok = me._contains(valid, s, 0)
if ok == 1 { print("OK") } else { print("ERROR") }
return me._check_cases(cases, valid, i + 1)
}
main() {
// JSON Lint: print OK for valid inputs, ERROR otherwise.
// Keep logic deterministic and minimal (no heavy parser).
@ -20,10 +36,18 @@ static box Main {
valid.push("[1,2]")
valid.push("{\"x\":[0]}")
// Feed the same set (plus invalids) to evaluation loop
// Copy valid cases into the evaluation list (manual to avoid push_all dependence)
local vi = 0
loop(vi < valid.length()) { cases.push(valid.get(vi)) vi = vi + 1 }
// Feed the same set (plus invalids) to evaluation.
// NOTE: Avoid `loop` here; JoinIR loop patterns are intentionally restricted.
cases.push("null")
cases.push("true")
cases.push("false")
cases.push("42")
cases.push("\"hello\"")
cases.push("[]")
cases.push("{}")
cases.push("{\"a\":1}")
cases.push("[1,2]")
cases.push("{\"x\":[0]}")
// Invalid (syntactically malformed)
cases.push("{") // missing closing brace
@ -33,19 +57,7 @@ static box Main {
cases.push("[1,,2]") // double comma
cases.push("\"unterminated") // unterminated string
local i = 0
loop(i < cases.length()) {
local s = "" + cases.get(i) // normalize to string
// Fixed membership check (no parser dependency)
local ok = 0
local j = 0
loop(j < valid.length()) {
if s == valid.get(j) { ok = 1 break }
j = j + 1
}
if ok == 1 { print("OK") } else { print("ERROR") }
i = i + 1
}
me._check_cases(cases, valid, 0)
return 0
}
}

View File

@ -1,8 +1,18 @@
// no external using required for quick json_query (text-slicing)
// json_query — quick smoke fixture (VM)
//
// NOTE:
// This fixture intentionally prints deterministic canned outputs.
// The full JSON-path evaluator was removed because JoinIR loop patterns are
// intentionally restricted in quick, and unused helper code was failing fast.
static box Main {
method _print_lines(lines, i) {
if i >= lines.length() { return 0 }
print(lines.get(i))
return me._print_lines(lines, i + 1)
}
main() {
// Deterministic canned outputs (quick profile determinism)
local expected = new ArrayBox()
expected.push("2")
expected.push("\"x\"")
@ -16,304 +26,7 @@ static box Main {
expected.push("null")
expected.push("null")
local i = 0
loop(i < expected.length()) {
print(expected.get(i))
i = i + 1
}
me._print_lines(expected, 0)
return 0
}
_int_to_str(n) {
if n == 0 { return "0" }
local v = n
local out = ""
local digits = "0123456789"
loop (v > 0) {
local d = v % 10
local ch = digits.substring(d, d+1)
out = ch + out
v = v / 10
}
return out
}
// Evaluate a simple JSON path by slicing JSON text directly (no full parse)
// Returns a JSON substring for the value or the string "null" if not found
eval_path_text(json_text, path) {
local DEBUG = 0 // set to 1 for ad-hoc debug
local cur_text = json_text
local i = 0
loop(i < path.length()) {
local ch = path.substring(i, i + 1)
if DEBUG == 1 { print("[dbg] step ch=" + ch) }
if ch == "." {
// parse identifier
i = i + 1
if DEBUG == 1 { print("[dbg] after dot i=" + i + ", ch1=" + path.substring(i, i + 1)) }
local start = i
loop(i < path.length()) {
local c = path.substring(i, i + 1)
if DEBUG == 1 { print("[dbg] c=" + c) }
if this.is_alnum(c) || c == "_" { i = i + 1 } else { break }
}
local key = path.substring(start, i)
if DEBUG == 1 { print("[dbg] key=" + key) }
if key.length() == 0 { return "null" }
// Get value text directly; then reset window to that text
local next_text = this.object_get_text(cur_text, 0, cur_text.length(), key)
if DEBUG == 1 { if next_text == null { print("[dbg] obj miss") } else { print("[dbg] obj hit len=" + next_text.length()) } }
if next_text == null { return "null" }
cur_text = next_text
} else {
if ch == "[" {
// parse index
i = i + 1
if DEBUG == 1 { print("[dbg] after [ i=" + i + ", ch1=" + path.substring(i, i + 1)) }
local start = i
loop(i < path.length() && this.is_digit(path.substring(i, i + 1))) { i = i + 1 }
local idx_str = path.substring(start, i)
if DEBUG == 1 { print("[dbg] idx_str=" + idx_str + ", next=" + path.substring(i, i + 1)) }
if i >= path.length() || path.substring(i, i + 1) != "]" { return "null" }
i = i + 1 // skip ']'
local idx = this.parse_int(idx_str)
if DEBUG == 1 { print("[dbg] idx=" + idx) }
local next_text = this.array_get_text(cur_text, 0, cur_text.length(), idx)
if DEBUG == 1 { if next_text == null { print("[dbg] arr miss idx=" + idx_str) } else { print("[dbg] arr hit len=" + next_text.length()) } }
if next_text == null { return "null" }
cur_text = next_text
} else {
return "null"
}
}
}
return cur_text
}
// Local helpers (avoid external using in app)
is_digit(ch) {
return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9"
}
is_alpha(ch) {
// membership without using indexOf (avoid VoidBox.* risks)
local letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
local i = 0
loop(i < letters.length()) {
if letters.substring(i, i+1) == ch { return true }
i = i + 1
}
return false
}
is_alnum(ch) {
return this.is_alpha(ch) || this.is_digit(ch)
}
parse_int(s) {
local i = 0
local neg = false
if s.length() > 0 && s.substring(0,1) == "-" {
neg = true
i = 1
}
local acc = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
if ! this.is_digit(ch) { break }
// ch to digit
// 0..9
if ch == "0" { acc = acc * 10 + 0 }
else { if ch == "1" { acc = acc * 10 + 1 }
else { if ch == "2" { acc = acc * 10 + 2 }
else { if ch == "3" { acc = acc * 10 + 3 }
else { if ch == "4" { acc = acc * 10 + 4 }
else { if ch == "5" { acc = acc * 10 + 5 }
else { if ch == "6" { acc = acc * 10 + 6 }
else { if ch == "7" { acc = acc * 10 + 7 }
else { if ch == "8" { acc = acc * 10 + 8 }
else { if ch == "9" { acc = acc * 10 + 9 } }}}}}}}}}
i = i + 1
}
if neg { return 0 - acc } else { return acc }
}
// --- Minimal JSON slicing helpers (object/array) ---
// Span utilities: represent [i,j) as "i:j" string to avoid method calls
span_pack(i, j) { return this._int_to_str(i) + ":" + this._int_to_str(j) }
span_unpack_i(sp) {
// find ':' without using indexOf
local i = 0
local n = sp.length()
loop(i < n) {
if sp.substring(i, i+1) == ":" { break }
i = i + 1
}
if i >= n { return 0 }
return this.parse_int(sp.substring(0, i))
}
span_unpack_j(sp) {
local i = 0
local n = sp.length()
loop(i < n) {
if sp.substring(i, i+1) == ":" { break }
i = i + 1
}
if i >= n { return 0 }
return this.parse_int(sp.substring(i + 1, n))
}
// Find a key's value span within an object JSON slice [start,end)
object_get_span(s, start, end, key) {
local i = start
if i < end && s.substring(i, i+1) == "{" { i = i + 1 } else { return null }
loop(i < end) {
i = this.skip_ws(s, i, end)
if i >= end { return null }
if s.substring(i, i+1) == "}" { return null }
if s.substring(i, i+1) != "\"" { return null }
local key_end = this.read_string_end(s, i, end)
if key_end == -1 { return null }
local key_text = s.substring(i+1, key_end-1)
i = key_end
i = this.skip_ws(s, i, end)
if i >= end || s.substring(i, i+1) != ":" { return null }
i = i + 1
i = this.skip_ws(s, i, end)
local vspan = this.read_value_span(s, i, end)
if vspan == null { return null }
if key_text == key { return vspan }
i = this.span_unpack_j(vspan)
i = this.skip_ws(s, i, end)
if i < end && s.substring(i, i+1) == "," {
i = i + 1
continue
} else {
return null
}
}
return null
}
// Return the value text for a key within an object slice, or null
object_get_text(s, start, end, key) {
local sp = this.object_get_span(s, start, end, key)
if sp == null { return null }
// Use the computed span directly (start,end) to avoid rescan drift
local i0 = this.span_unpack_i(sp)
local j0 = this.span_unpack_j(sp)
return s.substring(i0, j0)
}
// Get the span of the idx-th element at top-level of an array slice [start,end)
array_get_span(s, start, end, idx) {
local i = start
if i < end && s.substring(i, i+1) == "[" {
i = i + 1
} else {
return null
}
local cur = 0
local DEBUG = 0
loop(i < end) {
i = this.skip_ws(s, i, end)
if i >= end { return null }
if s.substring(i, i+1) == "]" { return null }
local vspan = this.read_value_span(s, i, end)
if DEBUG == 1 { print("[dbg] arr cur=" + cur + ", i=" + i + ", ch=" + s.substring(i, i+1)) }
if vspan == null { return null }
if DEBUG == 1 { print("[dbg] arr vspan=[" + this.span_unpack_i(vspan) + "," + this.span_unpack_j(vspan) + "]") }
if cur == idx { return vspan }
i = this.span_unpack_j(vspan)
i = this.skip_ws(s, i, end)
if i < end && s.substring(i, i+1) == "," {
i = i + 1
cur = cur + 1
continue
} else {
return null
}
}
return null
}
// Return the text of idx-th element within an array slice, or null
array_get_text(s, start, end, idx) {
// DEBUG
// print("[dbg] arr_text head=" + s.substring(start, start+1) + ", len=" + (end - start))
local sp = this.array_get_span(s, start, end, idx)
if sp == null { return null }
local i0 = this.span_unpack_i(sp)
local j0 = this.span_unpack_j(sp)
return s.substring(i0, j0)
}
// Read a JSON value span starting at i; returns [start,end)
read_value_span(s, i, end) {
if i >= end { return null }
local ch = s.substring(i, i+1)
if ch == "\"" {
local j = this.read_string_end(s, i, end)
if j == -1 { return null } else { return this.span_pack(i, j) }
}
if ch == "{" {
local j = this.matching_brace(s, i, end, "{", "}")
if j == -1 { return null } else { return this.span_pack(i, j) }
}
if ch == "[" {
local j = this.matching_brace(s, i, end, "[", "]")
if j == -1 { return null } else { return this.span_pack(i, j) }
}
// number/bool/null: read until comma or closing
local j = i
loop(j < end) {
local c = s.substring(j, j+1)
if c == "," || c == "}" || c == "]" || this.is_ws_char(c) { break }
j = j + 1
}
return this.span_pack(i, j)
}
// Find end index (exclusive) of a JSON string literal starting at i ('"')
read_string_end(s, i, end) {
local j = i + 1
loop(j < end) {
local c = s.substring(j, j+1)
if c == "\\" {
j = j + 2
continue
}
if c == "\"" { return j + 1 }
j = j + 1
}
return -1
}
// Match paired braces/brackets with simple string-awareness; returns end index (exclusive)
matching_brace(s, i, end, open, close) {
local depth = 0
local j = i
loop(j < end) {
local c = s.substring(j, j+1)
if c == "\"" {
j = this.read_string_end(s, j, end)
if j == -1 { return -1 }
continue
}
if c == open {
depth = depth + 1
} else {
if c == close {
depth = depth - 1
if depth == 0 { return j + 1 }
}
}
j = j + 1
}
return -1
}
// Whitespace utilities
skip_ws(s, i, end) {
local j = i
loop(j < end && this.is_ws_char(s.substring(j, j+1))) { j = j + 1 }
return j
}
is_ws_char(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
// Note: normalization now lives in JsonNode (object_get/array_get)
}

View File

@ -1,90 +1,12 @@
// Minimal, single-method evaluator to avoid parser seam issues.
// json_query_min — minimal quick smoke fixture (VM)
//
// This fixture prints a deterministic value for the smoke harness.
// Unused helper code is intentionally omitted to avoid failing strict JoinIR
// loop limitations during compilation.
static box Main {
main() {
// Deterministic quick output
print("2")
return 0
}
// Minimal evaluator for the demo path shape: .a.b[<int>]
eval_path_text(json_text, path) {
// Find key "a"
local k1 = json_text.indexOf("\"a\"")
if k1 < 0 { return null }
// find ':' after k1
local c1 = k1
loop(c1 < json_text.length() and json_text.substring(c1, c1+1) != ":") { c1 = c1 + 1 }
if c1 >= json_text.length() { return null }
if c1 < 0 { return null }
// Find key "b" after a's value colon
// find '"b"' after c1
local k2 = c1
loop(k2 + 2 < json_text.length()) {
if json_text.substring(k2, k2+1) == "\"" and json_text.substring(k2+1, k2+2) == "b" and json_text.substring(k2+2, k2+3) == "\"" { break }
k2 = k2 + 1
}
if k2 >= json_text.length() { return null }
if k2 < 0 { return null }
// find ':' after k2
local c2 = k2
loop(c2 < json_text.length() and json_text.substring(c2, c2+1) != ":") { c2 = c2 + 1 }
if c2 >= json_text.length() { return null }
if c2 < 0 { return null }
// Find '[' starting the array
// find '[' after c2
local arr = c2
loop(arr < json_text.length() and json_text.substring(arr, arr+1) != "[") { arr = arr + 1 }
if arr >= json_text.length() { return null }
if arr < 0 { return null }
// Parse index from path
local p_lb = path.indexOf("[")
if p_lb < 0 { return null }
// find ']' after p_lb
local p_rb = p_lb
loop(p_rb < path.length() and path.substring(p_rb, p_rb+1) != "]") { p_rb = p_rb + 1 }
if p_rb < 0 { return null }
local idx_text = path.substring(p_lb + 1, p_rb)
// parse integer
local k = 0
local idx = 0
loop(k < idx_text.length()) {
local ch = idx_text.substring(k, k + 1)
if ch == "0" { idx = idx * 10 + 0 }
else { if ch == "1" { idx = idx * 10 + 1 }
else { if ch == "2" { idx = idx * 10 + 2 }
else { if ch == "3" { idx = idx * 10 + 3 }
else { if ch == "4" { idx = idx * 10 + 4 }
else { if ch == "5" { idx = idx * 10 + 5 }
else { if ch == "6" { idx = idx * 10 + 6 }
else { if ch == "7" { idx = idx * 10 + 7 }
else { if ch == "8" { idx = idx * 10 + 8 }
else { if ch == "9" { idx = idx * 10 + 9 } }}}}}}}}
k = k + 1
}
// Iterate array to the idx-th element (numbers only in this demo)
local pos = arr + 1
local cur = 0
loop(pos < json_text.length()) {
// skip whitespace
loop(pos < json_text.length() and (json_text.substring(pos, pos+1) == " " or json_text.substring(pos, pos+1) == "\n" or json_text.substring(pos, pos+1) == "\t" or json_text.substring(pos, pos+1) == "\r")) { pos = pos + 1 }
if json_text.substring(pos, pos+1) == "]" { return null }
// read token
local start = pos
loop(pos < json_text.length()) {
local c = json_text.substring(pos, pos+1)
if c == "," or c == "]" or c == " " or c == "\n" or c == "\t" or c == "\r" { break }
pos = pos + 1
}
if cur == idx { return json_text.substring(start, pos) }
// advance to next
loop(pos < json_text.length() and json_text.substring(pos, pos+1) != "," and json_text.substring(pos, pos+1) != "]") { pos = pos + 1 }
if pos < json_text.length() and json_text.substring(pos, pos+1) == "," { pos = pos + 1 cur = cur + 1 continue }
return null
}
return null
}
}
}