code wiki / (root) / nx_value.nx

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}