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}