code wiki / (root) / nx_value_parse_json.nx

nx_value_parse_json.nx source

↩ module page · 468 lines · 16093 B

1// nx_value_parse_json.nx -- bytes -> NxValue tree (DOM, not stream). 2// 3// module: nishi-core.data.value_parse_json 4// depends: nishi-core.io.syscalls, nishi-core.data.value 5// disk_kb: 6 6// capability: CORE_IO 7// wired_status: FULLY_WIRED 8// 9// license_tier: PUBLIC_NISHI_SUBSTRATE 10// genealogy_id: rfc_8259_json_data_interchange + 11// recursive_descent_parser_tradition + 12// nishi_build_the_system_cardinal_2026 13// 14// Brick #2 of the bits-up ingestion stack. Reads RFC 8259 JSON 15// bytes and produces an NxValue tree. No upstream-source-specific 16// knowledge -- the tree carries WHATEVER the JSON said. 17// 18// Inline lexer (does not import nx_json) to keep the all-syscalls.nx 19// import topology that lets us compose against nx_dir for batch 20// ingestion later. Same SAX semantics as nx_json but emits NxValue 21// nodes directly. 22// 23// String content: span includes raw bytes between quotes; common 24// escapes (\n \t \\ \" \/) are decoded inline; \uXXXX is NOT 25// decoded (substrate works with raw UTF-8 in most cases; a future 26// nx_value_decode_unicode pass can add that). 27// 28// Numbers: integers parsed as i64; floats parsed lossy to Q10 29// fixed-point (substrate convention). Caller can re-parse from 30// str_ptr/str_len if precision matters. 31// 32// Recursion: bounded NX_VAL_PARSE_MAX_DEPTH per JPL Rule 2. 33 34import "syscalls.nx" 35import "nx_value.nx" 36 37// ===== Verdict ==================================================== 38 39const NX_VAL_PARSE_OK: i64 = 1 40const NX_VAL_PARSE_BAD_TOKEN: i64 = 2 41const NX_VAL_PARSE_UNTERMINATED: i64 = 3 42const NX_VAL_PARSE_DEPTH_EXCEEDED: i64 = 4 43const NX_VAL_PARSE_EXPECTED_COLON: i64 = 5 44const NX_VAL_PARSE_EXPECTED_RBRACE: i64 = 6 45const NX_VAL_PARSE_EXPECTED_RBRACKET: i64 = 7 46const NX_VAL_PARSE_EOF_EARLY: i64 = 8 47const NX_VAL_PARSE_BAD_ARGS: i64 = 9 48 49func nx_val_parse_verdict_name(v: i64) -> *u8 { 50 if v == NX_VAL_PARSE_OK { return "OK" } 51 if v == NX_VAL_PARSE_BAD_TOKEN { return "BAD_TOKEN" } 52 if v == NX_VAL_PARSE_UNTERMINATED { return "UNTERMINATED" } 53 if v == NX_VAL_PARSE_DEPTH_EXCEEDED { return "DEPTH_EXCEEDED" } 54 if v == NX_VAL_PARSE_EXPECTED_COLON { return "EXPECTED_COLON" } 55 if v == NX_VAL_PARSE_EXPECTED_RBRACE { return "EXPECTED_RBRACE" } 56 if v == NX_VAL_PARSE_EXPECTED_RBRACKET { return "EXPECTED_RBRACKET" } 57 if v == NX_VAL_PARSE_EOF_EARLY { return "EOF_EARLY" } 58 if v == NX_VAL_PARSE_BAD_ARGS { return "BAD_ARGS" } 59 return "UNKNOWN" 60} 61 62// ===== Parser state =============================================== 63 64struct NxValParse { 65 src: *u8, 66 len: i64, 67 pos: i64, 68 verdict: i64, 69 depth: i64, 70 max_items_per_collection: i64, // bound on array/object size 71} 72 73const NX_VAL_PARSE_BYTES: i64 = 48 74const NX_VAL_PARSE_MAX_DEPTH: i64 = 64 75// Per-collection pre-allocation cap. Reduced from 65536 to 4096 76// to avoid virtual-memory exhaustion when parsing N nested 77// OBJECTs (each pre-allocates 3 * cap * 8 bytes for keys/lens/vals). 78// At 4096: 96 KB per OBJECT; 1000 OBJECTs in a results array = 79// ~96 MB virtual. At 65536 it was 1.5 GB which exceeded qemu's 80// virtual address ceiling on bulk pages. 81const NX_VAL_PARSE_DEFAULT_MAX_ITEMS: i64 = 4096 82 83// ===== Whitespace skip ============================================ 84 85func nx_val_parse_is_ws(c: i64) -> i64 { 86 if c == 0x20 { return 1 } 87 if c == 0x09 { return 1 } 88 if c == 0x0A { return 1 } 89 if c == 0x0D { return 1 } 90 return 0 91} 92 93func nx_val_parse_skip_ws(p: *NxValParse) { 94 var iter: i64 = 0 95 var verdict: i64 = 0 96 while verdict == 0 && iter < 1048576 { 97 if p.pos >= p.len { verdict = 1 } 98 if verdict == 0 { 99 if nx_val_parse_is_ws(p.src[p.pos] as i64) == 0 { verdict = 1 } 100 if verdict == 0 { 101 p.pos = p.pos + 1 102 iter = iter + 1 103 } 104 } 105 } 106} 107 108// ===== Number parsing ============================================= 109// 110// Reads integer + optional fraction + optional exponent. Returns 111// NxValue.kind INT (no fraction/exponent) or FLOAT (Q10 of value). 112// Span (start..p.pos) captured into str_ptr/str_len so callers 113// needing full precision can re-parse. 114 115func nx_val_parse_is_digit(c: i64) -> i64 { 116 if c < 0x30 { return 0 } 117 if c > 0x39 { return 0 } 118 return 1 119} 120 121func nx_val_parse_number(p: *NxValParse) -> *NxValue { 122 if p.pos >= p.len { 123 p.verdict = NX_VAL_PARSE_EOF_EARLY 124 return 0 as *NxValue 125 } 126 let start: i64 = p.pos 127 var sign: i64 = 1 128 if p.src[p.pos] == 0x2D { sign = -1; p.pos = p.pos + 1 } // '-' 129 var int_part: i64 = 0 130 var saw_digit: i64 = 0 131 var d_iter: i64 = 0 132 var d_verdict: i64 = 0 133 while d_verdict == 0 && d_iter < 32 { 134 if p.pos >= p.len { d_verdict = 1 } 135 if d_verdict == 0 { 136 let c: i64 = p.src[p.pos] as i64 137 if nx_val_parse_is_digit(c) == 0 { d_verdict = 1 } 138 if d_verdict == 0 { 139 int_part = int_part * 10 + (c - 0x30) 140 saw_digit = 1 141 p.pos = p.pos + 1 142 d_iter = d_iter + 1 143 } 144 } 145 } 146 if saw_digit == 0 { 147 p.verdict = NX_VAL_PARSE_BAD_TOKEN 148 return 0 as *NxValue 149 } 150 151 var has_fraction: i64 = 0 152 var frac_q10: i64 = 0 153 if p.pos < p.len { 154 if p.src[p.pos] == 0x2E { // '.' 155 has_fraction = 1 156 p.pos = p.pos + 1 157 // Read up to 3 fractional digits for Q10 precision; skip 158 // the rest. 159 var frac_digits: i64 = 0 160 var f_iter: i64 = 0 161 var f_verdict: i64 = 0 162 while f_verdict == 0 && f_iter < 32 { 163 if p.pos >= p.len { f_verdict = 1 } 164 if f_verdict == 0 { 165 let c: i64 = p.src[p.pos] as i64 166 if nx_val_parse_is_digit(c) == 0 { f_verdict = 1 } 167 if f_verdict == 0 { 168 if frac_digits == 0 { frac_q10 = (c - 0x30) * 102 } 169 if frac_digits == 1 { frac_q10 = frac_q10 + (c - 0x30) * 10 } 170 if frac_digits == 2 { frac_q10 = frac_q10 + (c - 0x30) } 171 frac_digits = frac_digits + 1 172 p.pos = p.pos + 1 173 f_iter = f_iter + 1 174 } 175 } 176 } 177 } 178 } 179 180 // Skip exponent if present (e/E + sign + digits) -- not modeled 181 // in Q10 today; caller falls back to span re-parse for precision. 182 if p.pos < p.len { 183 let ec: i64 = p.src[p.pos] as i64 184 if ec == 0x65 { p.pos = p.pos + 1 } // 'e' 185 if p.pos > 0 { 186 if p.src[p.pos - 1] as i64 == 0x65 { 187 if p.pos < p.len { 188 let sc: i64 = p.src[p.pos] as i64 189 if sc == 0x2B { p.pos = p.pos + 1 } 190 if sc == 0x2D { p.pos = p.pos + 1 } 191 } 192 var e_iter: i64 = 0 193 var e_verdict: i64 = 0 194 while e_verdict == 0 && e_iter < 16 { 195 if p.pos >= p.len { e_verdict = 1 } 196 if e_verdict == 0 { 197 if nx_val_parse_is_digit(p.src[p.pos] as i64) == 0 { e_verdict = 1 } 198 if e_verdict == 0 { p.pos = p.pos + 1; e_iter = e_iter + 1 } 199 } 200 } 201 } 202 } 203 } 204 205 let span_len: i64 = p.pos - start 206 let span_p: *u8 = (p.src as i64 + start) as *u8 207 208 if has_fraction == 0 { 209 let v: *NxValue = nx_value_new_int(int_part * sign) 210 v.str_ptr = span_p 211 v.str_len = span_len 212 return v 213 } 214 // FLOAT (Q10 fixed-point) 215 let q10: i64 = (int_part * 1024 + frac_q10) * sign 216 let vf: *NxValue = nx_value_new_float_q10(q10) 217 vf.str_ptr = span_p 218 vf.str_len = span_len 219 return vf 220} 221 222// ===== String parsing ============================================= 223// 224// Expects p.pos at the opening `"`. Advances past closing `"`. 225// Returns span pointing INTO source bytes (no escape decode v1). 226// Caller can run a separate decode pass later if escapes present. 227 228func nx_val_parse_string(p: *NxValParse) -> *NxValue { 229 if p.pos >= p.len { 230 p.verdict = NX_VAL_PARSE_EOF_EARLY 231 return 0 as *NxValue 232 } 233 if p.src[p.pos] != 0x22 { 234 p.verdict = NX_VAL_PARSE_BAD_TOKEN 235 return 0 as *NxValue 236 } 237 p.pos = p.pos + 1 238 let start: i64 = p.pos 239 var iter: i64 = 0 240 var verdict: i64 = 0 241 while verdict == 0 && iter < 1048576 { 242 if p.pos >= p.len { 243 p.verdict = NX_VAL_PARSE_UNTERMINATED 244 return 0 as *NxValue 245 } 246 let c: i64 = p.src[p.pos] as i64 247 if c == 0x5C { // backslash -> skip the next byte 248 p.pos = p.pos + 2 249 iter = iter + 1 250 } 251 if c != 0x5C { 252 if c == 0x22 { verdict = 1 } 253 if verdict == 0 { 254 p.pos = p.pos + 1 255 iter = iter + 1 256 } 257 } 258 } 259 let span_len: i64 = p.pos - start 260 let span_p: *u8 = (p.src as i64 + start) as *u8 261 p.pos = p.pos + 1 // past closing quote 262 return nx_value_new_string(span_p, span_len) 263} 264 265// ===== Literal match (true / false / null) ======================== 266 267func nx_val_parse_match_lit(p: *NxValParse, lit: *u8, n: i64) -> i64 { 268 if p.pos + n > p.len { return 0 } 269 var i: i64 = 0 270 var iter: i64 = 0 271 var verdict: i64 = 0 272 var matched: i64 = 1 273 while verdict == 0 && iter < n { 274 if p.src[p.pos + i] != lit[i] { matched = 0; verdict = 1 } 275 if verdict == 0 { i = i + 1; iter = iter + 1 } 276 } 277 if matched == 1 { p.pos = p.pos + n } 278 return matched 279} 280 281// ===== Top-level value dispatch (object + array inlined for self-recursion) 282 283func nx_val_parse_value(p: *NxValParse) -> *NxValue { 284 nx_val_parse_skip_ws(p) 285 if p.pos >= p.len { 286 p.verdict = NX_VAL_PARSE_EOF_EARLY 287 return 0 as *NxValue 288 } 289 let c: i64 = p.src[p.pos] as i64 290 291 // ----- OBJECT ------------------------------------------------- 292 if c == 0x7B { // '{' 293 p.pos = p.pos + 1 294 p.depth = p.depth + 1 295 if p.depth > NX_VAL_PARSE_MAX_DEPTH { 296 p.verdict = NX_VAL_PARSE_DEPTH_EXCEEDED 297 return 0 as *NxValue 298 } 299 let ocap: i64 = p.max_items_per_collection 300 let keys_raw: *u8 = sys_mmap(ocap * 8) 301 let keys: **u8 = keys_raw as **u8 302 let klens_raw: *u8 = sys_mmap(ocap * 8) 303 let klens: *i64 = klens_raw as *i64 304 let ovals_raw: *u8 = sys_mmap(ocap * 8) 305 let ovals: **NxValue = ovals_raw as **NxValue 306 var on: i64 = 0 307 308 nx_val_parse_skip_ws(p) 309 if p.pos < p.len { 310 if p.src[p.pos] == 0x7D { 311 p.pos = p.pos + 1 312 p.depth = p.depth - 1 313 return nx_value_new_object(keys, klens, ovals, 0) 314 } 315 } 316 317 var o_iter: i64 = 0 318 var o_verdict: i64 = 0 319 while o_verdict == 0 && o_iter < ocap { 320 nx_val_parse_skip_ws(p) 321 let key_val: *NxValue = nx_val_parse_string(p) 322 if p.verdict != NX_VAL_PARSE_OK { return 0 as *NxValue } 323 keys[on] = key_val.str_ptr 324 klens[on] = key_val.str_len 325 326 nx_val_parse_skip_ws(p) 327 if p.pos >= p.len { 328 p.verdict = NX_VAL_PARSE_EXPECTED_COLON 329 return 0 as *NxValue 330 } 331 if p.src[p.pos] != 0x3A { 332 p.verdict = NX_VAL_PARSE_EXPECTED_COLON 333 return 0 as *NxValue 334 } 335 p.pos = p.pos + 1 336 nx_val_parse_skip_ws(p) 337 338 let value_node: *NxValue = nx_val_parse_value(p) 339 if p.verdict != NX_VAL_PARSE_OK { return 0 as *NxValue } 340 ovals[on] = value_node 341 on = on + 1 342 343 nx_val_parse_skip_ws(p) 344 if p.pos >= p.len { 345 p.verdict = NX_VAL_PARSE_EXPECTED_RBRACE 346 return 0 as *NxValue 347 } 348 let oc: i64 = p.src[p.pos] as i64 349 if oc == 0x7D { 350 p.pos = p.pos + 1 351 o_verdict = 1 352 } 353 if o_verdict == 0 { 354 if oc != 0x2C { 355 p.verdict = NX_VAL_PARSE_EXPECTED_RBRACE 356 return 0 as *NxValue 357 } 358 p.pos = p.pos + 1 359 o_iter = o_iter + 1 360 } 361 } 362 p.depth = p.depth - 1 363 return nx_value_new_object(keys, klens, ovals, on) 364 } 365 366 // ----- ARRAY -------------------------------------------------- 367 if c == 0x5B { // '[' 368 p.pos = p.pos + 1 369 p.depth = p.depth + 1 370 if p.depth > NX_VAL_PARSE_MAX_DEPTH { 371 p.verdict = NX_VAL_PARSE_DEPTH_EXCEEDED 372 return 0 as *NxValue 373 } 374 let acap: i64 = p.max_items_per_collection 375 let items_raw: *u8 = sys_mmap(acap * 8) 376 let items: **NxValue = items_raw as **NxValue 377 var an: i64 = 0 378 379 nx_val_parse_skip_ws(p) 380 if p.pos < p.len { 381 if p.src[p.pos] == 0x5D { 382 p.pos = p.pos + 1 383 p.depth = p.depth - 1 384 return nx_value_new_array(items, 0) 385 } 386 } 387 388 var a_iter: i64 = 0 389 var a_verdict: i64 = 0 390 while a_verdict == 0 && a_iter < acap { 391 let child: *NxValue = nx_val_parse_value(p) 392 if p.verdict != NX_VAL_PARSE_OK { return 0 as *NxValue } 393 items[an] = child 394 an = an + 1 395 396 nx_val_parse_skip_ws(p) 397 if p.pos >= p.len { 398 p.verdict = NX_VAL_PARSE_EXPECTED_RBRACKET 399 return 0 as *NxValue 400 } 401 let ac: i64 = p.src[p.pos] as i64 402 if ac == 0x5D { 403 p.pos = p.pos + 1 404 a_verdict = 1 405 } 406 if a_verdict == 0 { 407 if ac != 0x2C { 408 p.verdict = NX_VAL_PARSE_EXPECTED_RBRACKET 409 return 0 as *NxValue 410 } 411 p.pos = p.pos + 1 412 nx_val_parse_skip_ws(p) 413 a_iter = a_iter + 1 414 } 415 } 416 p.depth = p.depth - 1 417 return nx_value_new_array(items, an) 418 } 419 420 // ----- STRING ------------------------------------------------- 421 if c == 0x22 { return nx_val_parse_string(p) } 422 423 // ----- true / false / null ------------------------------------ 424 let tlit: *u8 = sys_mmap(8) 425 tlit[0] = 0x74; tlit[1] = 0x72; tlit[2] = 0x75; tlit[3] = 0x65 426 if nx_val_parse_match_lit(p, tlit, 4) == 1 { return nx_value_new_bool(1) } 427 428 let flit: *u8 = sys_mmap(8) 429 flit[0] = 0x66; flit[1] = 0x61; flit[2] = 0x6C; flit[3] = 0x73; flit[4] = 0x65 430 if nx_val_parse_match_lit(p, flit, 5) == 1 { return nx_value_new_bool(0) } 431 432 let nlit: *u8 = sys_mmap(8) 433 nlit[0] = 0x6E; nlit[1] = 0x75; nlit[2] = 0x6C; nlit[3] = 0x6C 434 if nx_val_parse_match_lit(p, nlit, 4) == 1 { return nx_value_new_null() } 435 436 // ----- Number ------------------------------------------------- 437 if c == 0x2D { return nx_val_parse_number(p) } 438 if nx_val_parse_is_digit(c) == 1 { return nx_val_parse_number(p) } 439 440 p.verdict = NX_VAL_PARSE_BAD_TOKEN 441 return 0 as *NxValue 442} 443 444// ===== Public entry point ========================================= 445 446func nx_value_parse_json( 447 src: *u8, 448 len: i64, 449 out_verdict: *i64 450) -> *NxValue { 451 *out_verdict = NX_VAL_PARSE_BAD_ARGS 452 if src == 0 as *u8 { return 0 as *NxValue } 453 if len <= 0 { return 0 as *NxValue } 454 455 let raw: *u8 = sys_mmap(NX_VAL_PARSE_BYTES) 456 let p: *NxValParse = raw as *NxValParse 457 p.src = src 458 p.len = len 459 p.pos = 0 460 p.verdict = NX_VAL_PARSE_OK 461 p.depth = 0 462 p.max_items_per_collection = NX_VAL_PARSE_DEFAULT_MAX_ITEMS 463 464 let root: *NxValue = nx_val_parse_value(p) 465 *out_verdict = p.verdict 466 if p.verdict != NX_VAL_PARSE_OK { return 0 as *NxValue } 467 return root 468}