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}