nx_value.nx source
↩ module page · 295 lines · 9135 B
1// nx_value.nx -- shape-free tagged tree representation.
2//
3// module: nishi-core.data.value
4// depends: nishi-core.io.syscalls
5// disk_kb: 4
6// capability: CORE_IO
7// wired_status: FULLY_WIRED
8//
9// license_tier: PUBLIC_NISHI_SUBSTRATE
10// genealogy_id: json_data_model_rfc_8259 +
11// s_expression_lisp_cons_cell_tradition +
12// nishi_build_the_system_cardinal_2026 +
13// nishi_no_skipping_build_the_missing_dependency
14//
15// THE shape-free representation any structured data parses INTO.
16// Brick #1 of the bits-up ingestion stack:
17//
18// bytes
19// -> nx_value_parse_json -> NxValue tree (Brick #2)
20// -> nx_value_parse_csv -> NxValue tree (queued)
21// -> nx_value_parse_xml -> NxValue tree (queued)
22//
23// NxValue tree
24// -> nx_shape_descriptor -> SHAPE record (Brick #3, queued)
25// -> nx_canonical_map -> substrate record (Brick #5, queued)
26//
27// No upstream-source-specific parsing happens at this layer. The
28// tree carries WHATEVER the source emitted; later passes interrogate
29// shape + map to canonical fields.
30
31import "syscalls.nx"
32
33// ===== Kind sealed enum ===========================================
34
35const NX_VAL_NULL: i64 = 1
36const NX_VAL_BOOL: i64 = 2
37const NX_VAL_INT: i64 = 3
38const NX_VAL_FLOAT: i64 = 4 // Q10 fixed-point per substrate convention
39const NX_VAL_STRING: i64 = 5
40const NX_VAL_ARRAY: i64 = 6
41const NX_VAL_OBJECT: i64 = 7
42
43func nx_val_kind_name(k: i64) -> *u8 {
44 if k == NX_VAL_NULL { return "NULL" }
45 if k == NX_VAL_BOOL { return "BOOL" }
46 if k == NX_VAL_INT { return "INT" }
47 if k == NX_VAL_FLOAT { return "FLOAT" }
48 if k == NX_VAL_STRING { return "STRING" }
49 if k == NX_VAL_ARRAY { return "ARRAY" }
50 if k == NX_VAL_OBJECT { return "OBJECT" }
51 return "UNKNOWN"
52}
53
54// ===== NxValue node (single allocation per value) =================
55//
56// Polymorphic payload: only the fields matching `kind` are
57// meaningful. Designed for ARENA allocation (every alloc is
58// sys_mmap of NX_VALUE_BYTES) so callers can free a whole subtree
59// by tracking arena boundaries. No GC, no refcount.
60//
61// STRING: str_ptr/str_len point into source bytes when zero-copy;
62// may point into substrate-managed copies for synthesized values.
63// ARRAY: items_ptr is a contiguous block of *NxValue (n_items
64// pointers, each 8 bytes). Allocated as sys_mmap(n_items * 8).
65// OBJECT: keys_ptr + key_lens_ptr + items_ptr are parallel arrays
66// each of length n_items. Key ordering preserved (insertion).
67
68struct NxValue {
69 kind: i64, // NX_VAL_*
70 int_value: i64, // for INT, BOOL (0/1), or FLOAT (Q10)
71 str_ptr: *u8, // for STRING (also: number-literal bytes for FLOAT precision-preserving)
72 str_len: i64, // for STRING
73 items_ptr: **NxValue, // for ARRAY / OBJECT (values)
74 n_items: i64, // for ARRAY / OBJECT
75 keys_ptr: **u8, // for OBJECT (key string pointers)
76 key_lens_ptr: *i64, // for OBJECT (key lengths, parallel to keys_ptr)
77}
78
79const NX_VALUE_BYTES: i64 = 64 // 8 fields * 8 bytes
80
81// ===== Constructors ================================================
82
83func nx_value_new_null() -> *NxValue {
84 let raw: *u8 = sys_mmap(NX_VALUE_BYTES)
85 let v: *NxValue = raw as *NxValue
86 v.kind = NX_VAL_NULL
87 v.int_value = 0
88 v.str_ptr = 0 as *u8
89 v.str_len = 0
90 v.items_ptr = 0 as **NxValue
91 v.n_items = 0
92 v.keys_ptr = 0 as **u8
93 v.key_lens_ptr = 0 as *i64
94 return v
95}
96
97func nx_value_new_bool(b: i64) -> *NxValue {
98 let v: *NxValue = nx_value_new_null()
99 v.kind = NX_VAL_BOOL
100 if b == 0 { v.int_value = 0 }
101 if b != 0 { v.int_value = 1 }
102 return v
103}
104
105func nx_value_new_int(n: i64) -> *NxValue {
106 let v: *NxValue = nx_value_new_null()
107 v.kind = NX_VAL_INT
108 v.int_value = n
109 return v
110}
111
112func nx_value_new_float_q10(q10: i64) -> *NxValue {
113 let v: *NxValue = nx_value_new_null()
114 v.kind = NX_VAL_FLOAT
115 v.int_value = q10
116 return v
117}
118
119func nx_value_new_string(ptr: *u8, len: i64) -> *NxValue {
120 let v: *NxValue = nx_value_new_null()
121 v.kind = NX_VAL_STRING
122 v.str_ptr = ptr
123 v.str_len = len
124 return v
125}
126
127func nx_value_new_array(items: **NxValue, count: i64) -> *NxValue {
128 let v: *NxValue = nx_value_new_null()
129 v.kind = NX_VAL_ARRAY
130 v.items_ptr = items
131 v.n_items = count
132 return v
133}
134
135func nx_value_new_object(
136 keys: **u8,
137 key_lens: *i64,
138 values: **NxValue,
139 count: i64
140) -> *NxValue {
141 let v: *NxValue = nx_value_new_null()
142 v.kind = NX_VAL_OBJECT
143 v.keys_ptr = keys
144 v.key_lens_ptr = key_lens
145 v.items_ptr = values
146 v.n_items = count
147 return v
148}
149
150// ===== Accessors ==================================================
151
152func nx_value_kind(v: *NxValue) -> i64 {
153 if v == 0 as *NxValue { return NX_VAL_NULL }
154 return v.kind
155}
156
157func nx_value_is_null(v: *NxValue) -> i64 {
158 if v == 0 as *NxValue { return 1 }
159 if v.kind == NX_VAL_NULL { return 1 }
160 return 0
161}
162
163func nx_value_array_len(v: *NxValue) -> i64 {
164 if v == 0 as *NxValue { return 0 }
165 if v.kind != NX_VAL_ARRAY {
166 if v.kind != NX_VAL_OBJECT { return 0 }
167 }
168 return v.n_items
169}
170
171func nx_value_array_get(v: *NxValue, idx: i64) -> *NxValue {
172 if v == 0 as *NxValue { return 0 as *NxValue }
173 if v.kind != NX_VAL_ARRAY { return 0 as *NxValue }
174 if idx < 0 { return 0 as *NxValue }
175 if idx >= v.n_items { return 0 as *NxValue }
176 return v.items_ptr[idx]
177}
178
179// Bounded helper: byte compare two NUL-agnostic spans.
180const NX_VALUE_MAX_KEY_COMPARE: i64 = 16384
181
182func nx_value_bytes_equal(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
183 if an != bn { return 0 }
184 if an == 0 { return 1 }
185 var i: i64 = 0
186 var iter: i64 = 0
187 var verdict: i64 = 0
188 var eq: i64 = 1
189 while verdict == 0 && iter < NX_VALUE_MAX_KEY_COMPARE {
190 if i >= an { verdict = 1 }
191 if verdict == 0 {
192 if a[i] != b[i] { eq = 0; verdict = 1 }
193 if verdict == 0 {
194 i = i + 1
195 iter = iter + 1
196 }
197 }
198 }
199 return eq
200}
201
202// Look up an object key. Returns *NxValue if found, NULL otherwise.
203// Linear scan (parallel keys array); object cardinalities at our
204// scale (typical < 50 fields per record) make this fine.
205
206const NX_VALUE_MAX_OBJECT_KEYS: i64 = 65536
207
208func nx_value_object_get(v: *NxValue, key: *u8, key_len: i64) -> *NxValue {
209 if v == 0 as *NxValue { return 0 as *NxValue }
210 if v.kind != NX_VAL_OBJECT { return 0 as *NxValue }
211 if key == 0 as *u8 { return 0 as *NxValue }
212 if key_len <= 0 { return 0 as *NxValue }
213 var i: i64 = 0
214 var iter: i64 = 0
215 var verdict: i64 = 0
216 var found: *NxValue = 0 as *NxValue
217 while verdict == 0 && iter < NX_VALUE_MAX_OBJECT_KEYS {
218 if i >= v.n_items { verdict = 1 }
219 if verdict == 0 {
220 let kp: *u8 = v.keys_ptr[i]
221 let kl: i64 = v.key_lens_ptr[i]
222 if nx_value_bytes_equal(kp, kl, key, key_len) == 1 {
223 found = v.items_ptr[i]
224 verdict = 1
225 }
226 if verdict == 0 {
227 i = i + 1
228 iter = iter + 1
229 }
230 }
231 }
232 return found
233}
234
235// Object key index lookup (returns position or -1).
236func nx_value_object_index_of(v: *NxValue, key: *u8, key_len: i64) -> i64 {
237 if v == 0 as *NxValue { return -1 }
238 if v.kind != NX_VAL_OBJECT { return -1 }
239 var i: i64 = 0
240 var iter: i64 = 0
241 var verdict: i64 = 0
242 var idx: i64 = -1
243 while verdict == 0 && iter < NX_VALUE_MAX_OBJECT_KEYS {
244 if i >= v.n_items { verdict = 1 }
245 if verdict == 0 {
246 let kp: *u8 = v.keys_ptr[i]
247 let kl: i64 = v.key_lens_ptr[i]
248 if nx_value_bytes_equal(kp, kl, key, key_len) == 1 {
249 idx = i
250 verdict = 1
251 }
252 if verdict == 0 {
253 i = i + 1
254 iter = iter + 1
255 }
256 }
257 }
258 return idx
259}
260
261// Convenience: get string field from object as ptr/len pair.
262// Caller passes pointers to receive results. Returns 1 on found
263// AND value-is-string; 0 otherwise.
264func nx_value_object_get_string(
265 v: *NxValue,
266 key: *u8,
267 key_len: i64,
268 out_ptr: **u8,
269 out_len: *i64
270) -> i64 {
271 *out_ptr = 0 as *u8
272 *out_len = 0
273 let child: *NxValue = nx_value_object_get(v, key, key_len)
274 if child == 0 as *NxValue { return 0 }
275 if child.kind != NX_VAL_STRING { return 0 }
276 *out_ptr = child.str_ptr
277 *out_len = child.str_len
278 return 1
279}
280
281// Convenience: get int field from object. Returns 1 on found AND
282// value-is-int; 0 otherwise.
283func nx_value_object_get_int(
284 v: *NxValue,
285 key: *u8,
286 key_len: i64,
287 out: *i64
288) -> i64 {
289 *out = 0
290 let child: *NxValue = nx_value_object_get(v, key, key_len)
291 if child == 0 as *NxValue { return 0 }
292 if child.kind != NX_VAL_INT { return 0 }
293 *out = child.int_value
294 return 1
295}