code wiki / _hdl_build / nx_dataset_export.nx

nx_dataset_export.nx source

↩ module page · 633 lines · 26382 B

1// nx_dataset_export.nx -- IMS Thrust D R2: BYTE-FAITHFUL multi-format dataset export. 2// 3// ONE source dataset -> THREE serializations (CSV / JSON / TSV) that are ROUND-TRIPPABLE: parse any of 4// the three back and you recover the IDENTICAL records (same headers, same cells, byte-exact). This is 5// the "dataset in lots of formats to download" half of the arc -- the data a chart visualises, offered 6// for download in the format the consumer wants, with no lossy drift between them. 7// 8// REUSE-vs-NEW: the UXF family (nx_uxf_tsv_migrate / nx_canon_cid) models "one source -> ONE content- 9// addressed CID for dedup/round-trip-to-CID" -- a DIFFERENT goal (it does not emit human-readable CSV/ 10// JSON/TSV text files a browser can download). The CSV escaping RULES here follow RFC 4180 exactly as 11// nx_chem_report_csv established (quote a field with , " CR LF; double internal "); the JSON string 12// escaping follows RFC 8259 as json_emit established. We implement the 3 generic emitters + 3 parsers 13// directly over a tiny generic ROW MODEL (no per-schema code) -- the gap those organs do not fill. 14// 15// ROW MODEL (NxDataset): nrows records, each with ncols string fields, in flat parallel arrays 16// hdr_ptr[c]/hdr_len[c] -- the c-th column header 17// cell_ptr[r*ncols+c]/cell_len[..] -- the field at (row r, col c) 18// Fields are arbitrary bytes (may contain commas, quotes, tabs, newlines). PURE: no I/O here; the 19// caller owns buffers (sys_mmap scratch only). license_tier: ORIGINAL 20import "nx_syscalls.nx" 21const DX_MAGIC_4096: i64 = 4096 22const DX_MAGIC_1024: i64 = 1024 23 24// ===== sealed verdict surface (codes 2910-2919) ===== 25const DX_OK: i64 = 0 26const DX_BAD_INPUT: i64 = 2910 27const DX_OVERFLOW: i64 = 2911 28const DX_PARSE: i64 = 2912 29 30const DX_MAXCOLS: i64 = 32 31const DX_MAXROWS: i64 = 256 32const DX_MAXCELLS: i64 = 8192 // DX_MAXROWS * DX_MAXCOLS ceiling guard 33 34// the generic dataset. cell at (r,c) lives at index r*ncols + c. 35struct NxDataset { 36 ncols: i64 37 nrows: i64 38 hdr_ptr: *i64 39 hdr_len: *i64 40 cell_ptr: *i64 41 cell_len: *i64 42 valid: i64 43} 44 45// allocate the parallel arrays for a dataset of up to DX_MAXROWS x DX_MAXCOLS. 46func dx_alloc(ds: *NxDataset, ncols: i64, nrows: i64) -> i64 { 47 if ncols < 0 { return 0 - DX_BAD_INPUT } 48 if ncols > DX_MAXCOLS { return 0 - DX_BAD_INPUT } 49 if nrows < 0 { return 0 - DX_BAD_INPUT } 50 if nrows > DX_MAXROWS { return 0 - DX_BAD_INPUT } 51 ds.ncols = ncols 52 ds.nrows = nrows 53 ds.hdr_ptr = sys_mmap(DX_MAXCOLS * 8) as *i64 54 ds.hdr_len = sys_mmap(DX_MAXCOLS * 8) as *i64 55 ds.cell_ptr = sys_mmap(DX_MAXCELLS * 8) as *i64 56 ds.cell_len = sys_mmap(DX_MAXCELLS * 8) as *i64 57 ds.valid = 1 58 return DX_OK 59} 60 61// set the c-th header to the NUL-terminated string s. 62func dx_set_hdr(ds: *NxDataset, c: i64, s: *u8) -> i64 { 63 if ds.valid != 1 { return 0 - DX_BAD_INPUT } 64 if c < 0 { return 0 - DX_BAD_INPUT } 65 if c >= ds.ncols { return 0 - DX_BAD_INPUT } 66 var n: i64 = 0 67 while s[n] != (0 as u8) { n = n + 1 } 68 ds.hdr_ptr[c] = s as i64 69 ds.hdr_len[c] = n 70 return DX_OK 71} 72 73// set the (r,c) cell to a byte range [p, p+len). (range form so cells may contain NULs/commas/etc.) 74func dx_set_cell(ds: *NxDataset, r: i64, c: i64, p: *u8, len: i64) -> i64 { 75 if ds.valid != 1 { return 0 - DX_BAD_INPUT } 76 if r < 0 { return 0 - DX_BAD_INPUT } 77 if r >= ds.nrows { return 0 - DX_BAD_INPUT } 78 if c < 0 { return 0 - DX_BAD_INPUT } 79 if c >= ds.ncols { return 0 - DX_BAD_INPUT } 80 let idx: i64 = r * ds.ncols + c 81 ds.cell_ptr[idx] = p as i64 82 ds.cell_len[idx] = len 83 return DX_OK 84} 85 86// convenience: set (r,c) from a NUL-terminated string. 87func dx_set_cell_z(ds: *NxDataset, r: i64, c: i64, s: *u8) -> i64 { 88 var n: i64 = 0 89 while s[n] != (0 as u8) { n = n + 1 } 90 return dx_set_cell(ds, r, c, s, n) 91} 92 93func dx_cell_ptr(ds: *NxDataset, r: i64, c: i64) -> *u8 { return ds.cell_ptr[r * ds.ncols + c] as *u8 } 94func dx_cell_len(ds: *NxDataset, r: i64, c: i64) -> i64 { return ds.cell_len[r * ds.ncols + c] } 95 96// ============================================================================ 97// byte-range equality. 98// ============================================================================ 99func dx_bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 100 if an != bn { return 0 } 101 var i: i64 = 0 102 while i < an { 103 if a[i] != b[i] { return 0 } 104 i = i + 1 105 } 106 return 1 107} 108 109// FULL dataset equality: same shape + every header + every cell byte-exact. The round-trip oracle. 110func dx_equal(a: *NxDataset, b: *NxDataset) -> i64 { 111 if a.valid != 1 { return 0 } 112 if b.valid != 1 { return 0 } 113 if a.ncols != b.ncols { return 0 } 114 if a.nrows != b.nrows { return 0 } 115 var c: i64 = 0 116 while c < a.ncols { 117 if dx_bytes_eq(a.hdr_ptr[c] as *u8, a.hdr_len[c], b.hdr_ptr[c] as *u8, b.hdr_len[c]) == 0 { return 0 } 118 c = c + 1 119 } 120 var r: i64 = 0 121 while r < a.nrows { 122 var cc: i64 = 0 123 while cc < a.ncols { 124 let ia: i64 = r * a.ncols + cc 125 if dx_bytes_eq(a.cell_ptr[ia] as *u8, a.cell_len[ia], b.cell_ptr[ia] as *u8, b.cell_len[ia]) == 0 { return 0 } 126 cc = cc + 1 127 } 128 r = r + 1 129 } 130 return 1 131} 132 133// ============================================================================ 134// CSV emit (RFC 4180). A field is quoted iff it contains , " CR or LF; internal " is doubled. 135// ============================================================================ 136func dx_field_needs_quote(p: *u8, n: i64) -> i64 { 137 var i: i64 = 0 138 while i < n { 139 let c: i64 = p[i] as i64 140 if c == 44 { return 1 } // ',' 141 if c == 34 { return 1 } // '"' 142 if c == 10 { return 1 } // LF 143 if c == 13 { return 1 } // CR 144 i = i + 1 145 } 146 return 0 147} 148 149// emit one CSV field (with escaping) into out at off; returns new off (or -overflow). 150func dx_emit_csv_field(out: *u8, off: i64, cap: i64, p: *u8, n: i64) -> i64 { 151 var o: i64 = off 152 if dx_field_needs_quote(p, n) == 1 { 153 if o >= cap { return 0 - DX_OVERFLOW } 154 out[o] = 34 as u8; o = o + 1 // opening " 155 var i: i64 = 0 156 while i < n { 157 let c: i64 = p[i] as i64 158 if c == 34 { 159 if o + 1 >= cap { return 0 - DX_OVERFLOW } 160 out[o] = 34 as u8; o = o + 1 161 out[o] = 34 as u8; o = o + 1 // doubled 162 } else { 163 if o >= cap { return 0 - DX_OVERFLOW } 164 out[o] = p[i]; o = o + 1 165 } 166 i = i + 1 167 } 168 if o >= cap { return 0 - DX_OVERFLOW } 169 out[o] = 34 as u8; o = o + 1 // closing " 170 } else { 171 var i2: i64 = 0 172 while i2 < n { 173 if o >= cap { return 0 - DX_OVERFLOW } 174 out[o] = p[i2]; o = o + 1 175 i2 = i2 + 1 176 } 177 } 178 return o 179} 180 181// emit the whole dataset as CSV (header row + data rows, CRLF-free LF line endings). NUL-terminates. 182// returns bytes written (>=0) or -DX_*. 183func dx_emit_csv(ds: *NxDataset, out: *u8, cap: i64) -> i64 { 184 if ds.valid != 1 { return 0 - DX_BAD_INPUT } 185 var o: i64 = 0 186 // header 187 var c: i64 = 0 188 while c < ds.ncols { 189 if c > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 44 as u8; o = o + 1 } 190 o = dx_emit_csv_field(out, o, cap, ds.hdr_ptr[c] as *u8, ds.hdr_len[c]) 191 if o < 0 { return o } 192 c = c + 1 193 } 194 if o >= cap { return 0 - DX_OVERFLOW } 195 out[o] = 10 as u8; o = o + 1 196 // rows 197 var r: i64 = 0 198 while r < ds.nrows { 199 var cc: i64 = 0 200 while cc < ds.ncols { 201 if cc > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 44 as u8; o = o + 1 } 202 let idx: i64 = r * ds.ncols + cc 203 o = dx_emit_csv_field(out, o, cap, ds.cell_ptr[idx] as *u8, ds.cell_len[idx]) 204 if o < 0 { return o } 205 cc = cc + 1 206 } 207 if o >= cap { return 0 - DX_OVERFLOW } 208 out[o] = 10 as u8; o = o + 1 209 r = r + 1 210 } 211 if o >= cap { return 0 - DX_OVERFLOW } 212 out[o] = 0 as u8 213 return o 214} 215 216// ============================================================================ 217// CSV parse (RFC 4180) -> NxDataset. Handles quoted fields, doubled "" , embedded , and newlines. 218// Allocates fresh cell byte-buffers (so parsed cells are independent of the source text). header row = 219// first line. ncols fixed by the header. returns DX_OK or -DX_PARSE. 220// ============================================================================ 221func dx_parse_csv(src: *u8, n: i64, ds: *NxDataset) -> i64 { 222 // first pass: parse into a temporary flat list of (offset,len) fields + a row-boundary marker. 223 // we materialise each field into its own buffer as we go. 224 let f_ptr: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 225 let f_len: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 226 let row_of: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 227 var nf: i64 = 0 228 var cur_row: i64 = 0 229 var fields_this_row: i64 = 0 230 var maxcols: i64 = 0 231 232 var i: i64 = 0 233 var row_had_data: i64 = 0 234 while i < n { 235 // parse one field starting at i. 236 let fbuf: *u8 = sys_mmap(DX_MAGIC_4096) 237 var fo: i64 = 0 238 var quoted: i64 = 0 239 if src[i] == 34 as u8 { quoted = 1; i = i + 1 } 240 var fdone: i64 = 0 241 while fdone == 0 { 242 if i >= n { fdone = 1 } 243 else { 244 let c: i64 = src[i] as i64 245 if quoted == 1 { 246 if c == 34 { 247 // doubled "" -> literal "; lone " -> end of quoted field. 248 if i + 1 < n { if src[i + 1] == 34 as u8 { fbuf[fo] = 34 as u8; fo = fo + 1; i = i + 2 } else { quoted = 0; i = i + 1 } } 249 else { quoted = 0; i = i + 1 } 250 } else { fbuf[fo] = src[i]; fo = fo + 1; i = i + 1 } 251 } else { 252 if c == 44 { fdone = 1 } // ',' ends field 253 else { if c == 10 { fdone = 1 } else { if c == 13 { fdone = 1 } else { fbuf[fo] = src[i]; fo = fo + 1; i = i + 1 } } } 254 } 255 } 256 } 257 // record the field 258 if nf >= DX_MAXCELLS { return 0 - DX_PARSE } 259 fbuf[fo] = 0 as u8 260 f_ptr[nf] = fbuf as i64 261 f_len[nf] = fo 262 row_of[nf] = cur_row 263 nf = nf + 1 264 fields_this_row = fields_this_row + 1 265 row_had_data = 1 266 267 // consume the delimiter we stopped on. 268 if i < n { 269 if src[i] == 44 as u8 { i = i + 1 } // next field on same row 270 else { 271 // end of line: consume CR?LF and close the row. 272 if src[i] == 13 as u8 { i = i + 1 } 273 if i < n { if src[i] == 10 as u8 { i = i + 1 } } 274 if fields_this_row > maxcols { maxcols = fields_this_row } 275 cur_row = cur_row + 1 276 fields_this_row = 0 277 row_had_data = 0 278 } 279 } 280 } 281 // close a final row that had data but no trailing newline. 282 if row_had_data == 1 { 283 if fields_this_row > maxcols { maxcols = fields_this_row } 284 cur_row = cur_row + 1 285 } 286 287 if cur_row < 1 { return 0 - DX_PARSE } // need at least the header row 288 let ncols: i64 = maxcols 289 let nrows: i64 = cur_row - 1 // first row is the header 290 if dx_alloc(ds, ncols, nrows) != DX_OK { return 0 - DX_PARSE } 291 292 // distribute fields into header (row 0) + cells (rows 1..). 293 var fi: i64 = 0 294 while fi < nf { 295 let r: i64 = row_of[fi] 296 // column index = position of this field within its row. 297 // recompute by scanning; cheap for our sizes. 298 var col: i64 = 0 299 var j: i64 = fi - 1 300 var scanning: i64 = 1 301 while scanning == 1 { 302 if j < 0 { scanning = 0 } 303 else { if row_of[j] == r { col = col + 1; j = j - 1 } else { scanning = 0 } } 304 } 305 if col < ncols { 306 if r == 0 { 307 ds.hdr_ptr[col] = f_ptr[fi] 308 ds.hdr_len[col] = f_len[fi] 309 } else { 310 let idx: i64 = (r - 1) * ncols + col 311 ds.cell_ptr[idx] = f_ptr[fi] 312 ds.cell_len[idx] = f_len[fi] 313 } 314 } 315 fi = fi + 1 316 } 317 // any short rows: fill missing trailing cells with empty (ptr=valid 0-len). default already 0/0. 318 return DX_OK 319} 320 321// ============================================================================ 322// TSV emit / parse. Fields are tab-separated; our model forbids literal TAB/NL inside a TSV field 323// (lossless because the CSV/JSON forms carry the same fields; TSV is the simple grid form). To keep 324// TSV byte-faithful for the ROUND TRIP we emit fields verbatim and parse on TAB/NL -- so the dataset 325// used for TSV round-trip must not contain TAB or NL in cells (the gate exercises a comma/quote cell 326// for CSV teeth, and a plain grid for the tri-format round trip). returns bytes / -DX_*. 327// ============================================================================ 328func dx_emit_tsv(ds: *NxDataset, out: *u8, cap: i64) -> i64 { 329 if ds.valid != 1 { return 0 - DX_BAD_INPUT } 330 var o: i64 = 0 331 var c: i64 = 0 332 while c < ds.ncols { 333 if c > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 9 as u8; o = o + 1 } 334 let hp: *u8 = ds.hdr_ptr[c] as *u8 335 let hn: i64 = ds.hdr_len[c] 336 var k: i64 = 0 337 while k < hn { if o >= cap { return 0 - DX_OVERFLOW } out[o] = hp[k]; o = o + 1; k = k + 1 } 338 c = c + 1 339 } 340 if o >= cap { return 0 - DX_OVERFLOW } 341 out[o] = 10 as u8; o = o + 1 342 var r: i64 = 0 343 while r < ds.nrows { 344 var cc: i64 = 0 345 while cc < ds.ncols { 346 if cc > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 9 as u8; o = o + 1 } 347 let idx: i64 = r * ds.ncols + cc 348 let p: *u8 = ds.cell_ptr[idx] as *u8 349 let ln: i64 = ds.cell_len[idx] 350 var k2: i64 = 0 351 while k2 < ln { if o >= cap { return 0 - DX_OVERFLOW } out[o] = p[k2]; o = o + 1; k2 = k2 + 1 } 352 cc = cc + 1 353 } 354 if o >= cap { return 0 - DX_OVERFLOW } 355 out[o] = 10 as u8; o = o + 1 356 r = r + 1 357 } 358 if o >= cap { return 0 - DX_OVERFLOW } 359 out[o] = 0 as u8 360 return o 361} 362 363func dx_parse_tsv(src: *u8, n: i64, ds: *NxDataset) -> i64 { 364 let f_ptr: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 365 let f_len: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 366 let row_of: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 367 var nf: i64 = 0 368 var cur_row: i64 = 0 369 var fields_this_row: i64 = 0 370 var maxcols: i64 = 0 371 var i: i64 = 0 372 var row_had_data: i64 = 0 373 while i < n { 374 let fbuf: *u8 = sys_mmap(DX_MAGIC_4096) 375 var fo: i64 = 0 376 var fdone: i64 = 0 377 while fdone == 0 { 378 if i >= n { fdone = 1 } 379 else { 380 let c: i64 = src[i] as i64 381 if c == 9 { fdone = 1 } else { if c == 10 { fdone = 1 } else { if c == 13 { fdone = 1 } else { fbuf[fo] = src[i]; fo = fo + 1; i = i + 1 } } } 382 } 383 } 384 if nf >= DX_MAXCELLS { return 0 - DX_PARSE } 385 fbuf[fo] = 0 as u8 386 f_ptr[nf] = fbuf as i64; f_len[nf] = fo; row_of[nf] = cur_row 387 nf = nf + 1; fields_this_row = fields_this_row + 1; row_had_data = 1 388 if i < n { 389 if src[i] == 9 as u8 { i = i + 1 } 390 else { 391 if src[i] == 13 as u8 { i = i + 1 } 392 if i < n { if src[i] == 10 as u8 { i = i + 1 } } 393 if fields_this_row > maxcols { maxcols = fields_this_row } 394 cur_row = cur_row + 1; fields_this_row = 0; row_had_data = 0 395 } 396 } 397 } 398 if row_had_data == 1 { if fields_this_row > maxcols { maxcols = fields_this_row } cur_row = cur_row + 1 } 399 if cur_row < 1 { return 0 - DX_PARSE } 400 let ncols: i64 = maxcols 401 let nrows: i64 = cur_row - 1 402 if dx_alloc(ds, ncols, nrows) != DX_OK { return 0 - DX_PARSE } 403 var fi: i64 = 0 404 while fi < nf { 405 let r: i64 = row_of[fi] 406 var col: i64 = 0 407 var j: i64 = fi - 1 408 var scanning: i64 = 1 409 while scanning == 1 { if j < 0 { scanning = 0 } else { if row_of[j] == r { col = col + 1; j = j - 1 } else { scanning = 0 } } } 410 if col < ncols { 411 if r == 0 { ds.hdr_ptr[col] = f_ptr[fi]; ds.hdr_len[col] = f_len[fi] } 412 else { let idx: i64 = (r - 1) * ncols + col; ds.cell_ptr[idx] = f_ptr[fi]; ds.cell_len[idx] = f_len[fi] } 413 } 414 fi = fi + 1 415 } 416 return DX_OK 417} 418 419// ============================================================================ 420// JSON emit: an array of row OBJECTS keyed by header. RFC 8259 string escaping (", \, control chars). 421// shape: [{"hdr0":"cell00","hdr1":"cell01"},{...}] -- self-describing, key per column. 422// ============================================================================ 423func dx_emit_json_str(out: *u8, off: i64, cap: i64, p: *u8, n: i64) -> i64 { 424 var o: i64 = off 425 if o >= cap { return 0 - DX_OVERFLOW } 426 out[o] = 34 as u8; o = o + 1 // " 427 var i: i64 = 0 428 while i < n { 429 let b: i64 = p[i] as i64 430 if b == 34 { if o + 1 >= cap { return 0 - DX_OVERFLOW } out[o] = 92 as u8; o = o + 1; out[o] = 34 as u8; o = o + 1 } 431 else { if b == 92 { if o + 1 >= cap { return 0 - DX_OVERFLOW } out[o] = 92 as u8; o = o + 1; out[o] = 92 as u8; o = o + 1 } 432 else { if b == 10 { if o + 1 >= cap { return 0 - DX_OVERFLOW } out[o] = 92 as u8; o = o + 1; out[o] = 110 as u8; o = o + 1 } 433 else { if b == 13 { if o + 1 >= cap { return 0 - DX_OVERFLOW } out[o] = 92 as u8; o = o + 1; out[o] = 114 as u8; o = o + 1 } 434 else { if b == 9 { if o + 1 >= cap { return 0 - DX_OVERFLOW } out[o] = 92 as u8; o = o + 1; out[o] = 116 as u8; o = o + 1 } 435 else { if b < 32 { 436 // \u00XX 437 if o + 5 >= cap { return 0 - DX_OVERFLOW } 438 out[o] = 92 as u8; o = o + 1; out[o] = 117 as u8; o = o + 1; out[o] = 48 as u8; o = o + 1; out[o] = 48 as u8; o = o + 1 439 let hi: i64 = (b >> 4) & 0xF 440 let lo: i64 = b & 0xF 441 var hc: i64 = 48 + hi 442 if hi > 9 { hc = 97 + hi - 10 } 443 var lc: i64 = 48 + lo 444 if lo > 9 { lc = 97 + lo - 10 } 445 out[o] = hc as u8; o = o + 1; out[o] = lc as u8; o = o + 1 446 } else { 447 if o >= cap { return 0 - DX_OVERFLOW } 448 out[o] = p[i]; o = o + 1 449 } } } } } } 450 i = i + 1 451 } 452 if o >= cap { return 0 - DX_OVERFLOW } 453 out[o] = 34 as u8; o = o + 1 454 return o 455} 456 457func dx_emit_json(ds: *NxDataset, out: *u8, cap: i64) -> i64 { 458 if ds.valid != 1 { return 0 - DX_BAD_INPUT } 459 var o: i64 = 0 460 if o >= cap { return 0 - DX_OVERFLOW } 461 out[o] = 91 as u8; o = o + 1 // [ 462 var r: i64 = 0 463 while r < ds.nrows { 464 if r > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 44 as u8; o = o + 1 } 465 if o >= cap { return 0 - DX_OVERFLOW } 466 out[o] = 123 as u8; o = o + 1 // { 467 var c: i64 = 0 468 while c < ds.ncols { 469 if c > 0 { if o >= cap { return 0 - DX_OVERFLOW } out[o] = 44 as u8; o = o + 1 } 470 o = dx_emit_json_str(out, o, cap, ds.hdr_ptr[c] as *u8, ds.hdr_len[c]) 471 if o < 0 { return o } 472 if o >= cap { return 0 - DX_OVERFLOW } 473 out[o] = 58 as u8; o = o + 1 // : 474 let idx: i64 = r * ds.ncols + c 475 o = dx_emit_json_str(out, o, cap, ds.cell_ptr[idx] as *u8, ds.cell_len[idx]) 476 if o < 0 { return o } 477 c = c + 1 478 } 479 if o >= cap { return 0 - DX_OVERFLOW } 480 out[o] = 125 as u8; o = o + 1 // } 481 r = r + 1 482 } 483 if o >= cap { return 0 - DX_OVERFLOW } 484 out[o] = 93 as u8; o = o + 1 // ] 485 if o >= cap { return 0 - DX_OVERFLOW } 486 out[o] = 0 as u8 487 return o 488} 489 490// JSON parse: read [{"k":"v",...},...] back into the model. We learn the column order from the FIRST 491// object's keys (matching how we emit, key-per-column in header order). Unescapes \" \\ \n \r \t \uXXXX. 492// returns DX_OK or -DX_PARSE. 493func dx_json_unescape_string(src: *u8, n: i64, i_io: *i64, outbuf: *u8) -> i64 { 494 var i: i64 = i_io[0] 495 if i >= n { return 0 - DX_PARSE } 496 if src[i] != 34 as u8 { return 0 - DX_PARSE } 497 i = i + 1 498 var o: i64 = 0 499 var done: i64 = 0 500 while done == 0 { 501 if i >= n { return 0 - DX_PARSE } 502 let c: i64 = src[i] as i64 503 if c == 34 { i = i + 1; done = 1 } 504 else { 505 if c == 92 { 506 i = i + 1 507 if i >= n { return 0 - DX_PARSE } 508 let e: i64 = src[i] as i64 509 if e == 34 { outbuf[o] = 34 as u8; o = o + 1; i = i + 1 } 510 else { if e == 92 { outbuf[o] = 92 as u8; o = o + 1; i = i + 1 } 511 else { if e == 110 { outbuf[o] = 10 as u8; o = o + 1; i = i + 1 } 512 else { if e == 114 { outbuf[o] = 13 as u8; o = o + 1; i = i + 1 } 513 else { if e == 116 { outbuf[o] = 9 as u8; o = o + 1; i = i + 1 } 514 else { if e == 117 { 515 // \uXXXX -- decode the low byte (our emitter only ever produces \u00XX for <0x20). 516 if i + 4 >= n { return 0 - DX_PARSE } 517 var val: i64 = 0 518 var h: i64 = 1 519 while h <= 4 { 520 let hc: i64 = src[i + h] as i64 521 var d: i64 = 0 522 if hc >= 48 { if hc <= 57 { d = hc - 48 } } 523 if hc >= 97 { if hc <= 102 { d = hc - 97 + 10 } } 524 if hc >= 65 { if hc <= 70 { d = hc - 65 + 10 } } 525 val = val * 16 + d 526 h = h + 1 527 } 528 outbuf[o] = (val & 0xff) as u8; o = o + 1 529 i = i + 5 530 } else { outbuf[o] = src[i]; o = o + 1; i = i + 1 } } } } } } 531 } else { outbuf[o] = src[i]; o = o + 1; i = i + 1 } 532 } 533 } 534 outbuf[o] = 0 as u8 535 i_io[0] = i 536 return o 537} 538 539func dx_parse_json(src: *u8, n: i64, ds: *NxDataset) -> i64 { 540 // collect keys (col order) from the first object; collect every value in row-major order. 541 let key_ptr: *i64 = sys_mmap(DX_MAXCOLS * 8) as *i64 542 let key_len: *i64 = sys_mmap(DX_MAXCOLS * 8) as *i64 543 let val_ptr: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 544 let val_len: *i64 = sys_mmap(DX_MAXCELLS * 8) as *i64 545 var ncols: i64 = 0 546 var nvals: i64 = 0 547 var nrows: i64 = 0 548 let ipos: *i64 = sys_mmap(16) as *i64 549 550 var i: i64 = 0 551 // expect leading '[' 552 while i < n { if src[i] == 91 as u8 { i = i + 1; break } i = i + 1 } 553 var in_arr: i64 = 1 554 while in_arr == 1 { 555 // skip whitespace/commas to the next '{' or ']' 556 var f: i64 = 0 557 while f == 0 { 558 if i >= n { in_arr = 0; f = 1 } 559 else { 560 let c: i64 = src[i] as i64 561 if c == 123 { f = 1 } // '{' 562 else { if c == 93 { in_arr = 0; f = 1; i = i + 1 } else { i = i + 1 } } 563 } 564 } 565 if in_arr == 1 { 566 // parse one object {"k":"v", ...} 567 i = i + 1 // past '{' 568 var col_in_row: i64 = 0 569 var obj_done: i64 = 0 570 while obj_done == 0 { 571 // skip to '"' (key) or '}' 572 var g: i64 = 0 573 while g == 0 { 574 if i >= n { return 0 - DX_PARSE } 575 let c2: i64 = src[i] as i64 576 if c2 == 34 { g = 1 } 577 else { if c2 == 125 { obj_done = 1; g = 1; i = i + 1 } else { i = i + 1 } } 578 } 579 if obj_done == 0 { 580 // key 581 let kbuf: *u8 = sys_mmap(DX_MAGIC_1024) 582 ipos[0] = i 583 let kl: i64 = dx_json_unescape_string(src, n, ipos, kbuf) 584 if kl < 0 { return 0 - DX_PARSE } 585 i = ipos[0] 586 // skip to ':' 587 while i < n { if src[i] == 58 as u8 { i = i + 1; break } i = i + 1 } 588 // skip whitespace to value '"' 589 while i < n { if src[i] == 34 as u8 { break } i = i + 1 } 590 let vbuf: *u8 = sys_mmap(DX_MAGIC_4096) 591 ipos[0] = i 592 let vl: i64 = dx_json_unescape_string(src, n, ipos, vbuf) 593 if vl < 0 { return 0 - DX_PARSE } 594 i = ipos[0] 595 // first object defines the columns. 596 if nrows == 0 { 597 if ncols < DX_MAXCOLS { key_ptr[ncols] = kbuf as i64; key_len[ncols] = kl; ncols = ncols + 1 } 598 } 599 if nvals < DX_MAXCELLS { val_ptr[nvals] = vbuf as i64; val_len[nvals] = vl; nvals = nvals + 1 } 600 col_in_row = col_in_row + 1 601 } 602 } 603 nrows = nrows + 1 604 } 605 } 606 if ncols < 1 { return 0 - DX_PARSE } 607 if dx_alloc(ds, ncols, nrows) != DX_OK { return 0 - DX_PARSE } 608 var c: i64 = 0 609 while c < ncols { ds.hdr_ptr[c] = key_ptr[c]; ds.hdr_len[c] = key_len[c]; c = c + 1 } 610 // values were collected row-major (ncols per row). 611 var vi: i64 = 0 612 while vi < nvals { 613 if vi < nrows * ncols { ds.cell_ptr[vi] = val_ptr[vi]; ds.cell_len[vi] = val_len[vi] } 614 vi = vi + 1 615 } 616 return DX_OK 617} 618 619// compile smoke; real KAT in nx_figure_gate. 620func main() -> i64 { 621 let ds: *NxDataset = sys_mmap(128) as *NxDataset 622 dx_alloc(ds, 2, 1) 623 dx_set_hdr(ds, 0, "k" as *u8); dx_set_hdr(ds, 1, "v" as *u8) 624 dx_set_cell_z(ds, 0, 0, "x" as *u8); dx_set_cell_z(ds, 0, 1, "y" as *u8) 625 let out: *u8 = sys_mmap(DX_MAGIC_4096) 626 let nc: i64 = dx_emit_csv(ds, out, DX_MAGIC_4096) 627 if nc <= 0 { return 1 } 628 let nj: i64 = dx_emit_json(ds, out, DX_MAGIC_4096) 629 if nj <= 0 { return 2 } 630 let nt: i64 = dx_emit_tsv(ds, out, DX_MAGIC_4096) 631 if nt <= 0 { return 3 } 632 return 0 633}