// no external using required for quick json_query (text-slicing) static box Main { main() { // DEBUG toggle (manual runs only). Keep 0 for tests. local DEBUG = 0 // Simple JSON query runner: evaluate a few (json, path) pairs and print results // Path grammar (subset): // := ('.' | '[' ']')* // Examples: .a.b[1], .c, [0] local cases = new ArrayBox() // Case 1 local j1 = "{\"a\":{\"b\":[1,2,3]},\"c\":\"x\"}" cases.push(j1) cases.push(".a.b[1]") // -> 2 cases.push(j1) cases.push(".c") // -> "x" cases.push(j1) cases.push(".a") // -> {"b":[1,2,3]} cases.push(j1) cases.push(".a.b") // -> [1,2,3] cases.push(j1) cases.push(".a.b[10]") // -> null cases.push(j1) cases.push(".x") // -> null cases.push(j1) cases.push(".a.b[0]") // -> 1 // Case 2 local j2 = "[ {\"k\":\"v\"}, 10, null ]" cases.push(j2) cases.push("[0].k") // -> "v" cases.push(j2) cases.push("[1]") // -> 10 cases.push(j2) cases.push("[2]") // -> null cases.push(j2) cases.push("[3]") // -> null (out of range) local i = 0 loop(i < cases.length()) { local json_text = cases.get(i) local path = cases.get(i + 1) if DEBUG == 1 { print("[dbg] path=" + path) } // Parser-less path: slice JSON text directly for quick profile stability local out_text = this.eval_path_text(json_text, path) if out_text == null { print("null") } else { print(out_text) } i = i + 2 } 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 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) }