code wiki / (root) / nx_value_parse_json.nx

nx_value_parse_json.nx source

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