@fraczak/k 8.0.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/DOCS/CODECS.md +22 -24
- package/DOCS/CODEC_ARCHITECTURE.md +3 -5
- package/DOCS/JSON_APPROACHES_COMPARISON.md +9 -13
- package/DOCS/WEB_REPL_REQUIREMENTS.md +3 -4
- package/Examples/djson.k +631 -165
- package/README.md +1 -3
- package/browser/repl-browser.mjs +0 -4
- package/browser/vfs-data.mjs +3 -9
- package/codecs/README.md +1 -3
- package/codecs/djson.mjs +375 -84
- package/package.json +1 -4
- package/scripts/build-repl-html.mjs +1 -11
- package/scripts/run-tests.mjs +5 -0
- package/tests/test-codecs.mjs +4 -34
- package/tests/test-djson-methods.mjs +285 -0
- package/tests/test-repl-html.mjs +0 -4
- package/tests/test-repl.mjs +14 -55
- package/Examples/djson-btrie.k +0 -414
- package/Examples/djson-ptrie.k +0 -663
- package/codecs/djson-btrie.mjs +0 -560
- package/codecs/djson-ptrie.mjs +0 -587
- package/scripts/perf-djson.mjs +0 -1142
- package/tests/bench-djson-tries.mjs +0 -340
package/codecs/README.md
CHANGED
|
@@ -105,9 +105,7 @@ as `k-parse.mjs`, keep that name. Every installed codec binary supports
|
|
|
105
105
|
| `k-print` | `k-print.mjs` | Read a binary pattern+value stream and write the decoded JSON-like value. Use `--debug` to include the decoded pattern. |
|
|
106
106
|
| `k-show` | `show.mjs` | Pass the wire stream through unchanged on stdout while printing the decoded value and filter to stderr. |
|
|
107
107
|
| `k-json` | `json.mjs` | Convert JSON to/from the binary stream with `--parse` or `--print`. |
|
|
108
|
-
| `k-djson` | `djson.mjs` |
|
|
109
|
-
| `k-djson-btrie` | `djson-btrie.mjs` | Binary bit-trie dynamic JSON. |
|
|
110
|
-
| `k-djson-ptrie` | `djson-ptrie.mjs` | Radix trie dynamic JSON. |
|
|
108
|
+
| `k-djson` | `djson.mjs` | Dynamic binary bit-trie JSON codec. |
|
|
111
109
|
| `k-int` | `int.mjs` | Convert decimal integers to/from the binary stream with `--parse` or `--print`. |
|
|
112
110
|
| `k-ieee` | `ieee.mjs` | Convert float literals to/from the binary stream with `--parse` or `--print`. |
|
|
113
111
|
| `k-unit` | `unit.mjs` | Write or validate the unit value `{}` with `--parse` or `--print`. |
|
package/codecs/djson.mjs
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Dynamic JSON (djson)
|
|
4
|
+
* Dynamic JSON (djson) binary bit-trie codec.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Uses a unified binary bit-trie for both JSON arrays (list) and JSON
|
|
7
|
+
* objects (object):
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* TRIE = <
|
|
10
|
+
* {} nil,
|
|
11
|
+
* {
|
|
12
|
+
* < {} nil, JSON val > val,
|
|
13
|
+
* TRIE 0,
|
|
14
|
+
* TRIE 1
|
|
15
|
+
* } trie
|
|
16
16
|
* >
|
|
17
|
+
*
|
|
18
|
+
* - In `list`, element at index i >= 0 is stored at the canonical binary
|
|
19
|
+
* bit-path of i (0 -> [0], 1 -> [1], 2 -> [1,0], 3 -> [1,1], ...).
|
|
20
|
+
* - In `object`, entry with key k is stored at the prefix-free Unicode
|
|
21
|
+
* character bit-path of k.
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
24
|
import {
|
|
@@ -26,22 +31,24 @@ import {
|
|
|
26
31
|
} from "./runtime/codec-sdk.mjs";
|
|
27
32
|
import { encodeNumberToValue, decodeValueToNumber } from "./ieee.mjs";
|
|
28
33
|
import {
|
|
34
|
+
codePointToUnicodeValue,
|
|
35
|
+
unicodeValueToCodePoint,
|
|
29
36
|
textToStringValue,
|
|
30
37
|
stringValueToText
|
|
31
38
|
} from "./runtime/unicode-string.mjs";
|
|
32
39
|
|
|
33
40
|
export const doc = `
|
|
34
|
-
Dynamic JSON (djson)
|
|
41
|
+
Dynamic JSON (djson) binary bit-trie codec.
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
Represents both JSON arrays (list) and JSON objects (object) using a
|
|
44
|
+
unified binary bit-trie:
|
|
45
|
+
TRIE = <
|
|
46
|
+
{} nil,
|
|
47
|
+
{
|
|
48
|
+
< {} nil, JSON val > val,
|
|
49
|
+
TRIE 0,
|
|
50
|
+
TRIE 1
|
|
51
|
+
} trie
|
|
45
52
|
>
|
|
46
53
|
`;
|
|
47
54
|
|
|
@@ -122,13 +129,21 @@ export const DJSON_PATTERN = patternFromFilter(`
|
|
|
122
129
|
< {} true, {} false > bool,
|
|
123
130
|
{} null,
|
|
124
131
|
|
|
125
|
-
<
|
|
132
|
+
<
|
|
133
|
+
{} nil,
|
|
134
|
+
{
|
|
135
|
+
< {} nil, JSON val > val,
|
|
136
|
+
TRIE 0,
|
|
137
|
+
TRIE 1
|
|
138
|
+
} trie
|
|
139
|
+
> = TRIE list,
|
|
126
140
|
|
|
127
|
-
|
|
141
|
+
TRIE object
|
|
128
142
|
> = JSON
|
|
129
143
|
`);
|
|
130
144
|
|
|
131
145
|
const UNIT = Value.product({});
|
|
146
|
+
const NIL = Value.variant("nil", UNIT);
|
|
132
147
|
|
|
133
148
|
function requireProduct(value, where) {
|
|
134
149
|
if (!isProduct(value)) {
|
|
@@ -137,37 +152,298 @@ function requireProduct(value, where) {
|
|
|
137
152
|
return value.product;
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
function
|
|
141
|
-
let
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
function pushBitsMSB(out, width, n) {
|
|
156
|
+
for (let i = width - 1; i >= 0; i--) {
|
|
157
|
+
out.push((n >> i) & 1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readBitsMSB(bits, offset, width) {
|
|
162
|
+
let n = 0;
|
|
163
|
+
for (let i = 0; i < width; i++) {
|
|
164
|
+
n = (n << 1) | bits[offset + i];
|
|
165
|
+
}
|
|
166
|
+
return n;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function indexToBitPath(index) {
|
|
170
|
+
if (index === 0) return [0];
|
|
171
|
+
const bits = [];
|
|
172
|
+
let n = index;
|
|
173
|
+
while (n > 0) {
|
|
174
|
+
bits.push(n & 1);
|
|
175
|
+
n = Math.floor(n / 2);
|
|
176
|
+
}
|
|
177
|
+
bits.reverse();
|
|
178
|
+
return bits;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function bitPathToIndex(bits) {
|
|
182
|
+
if (bits.length === 1 && bits[0] === 0) return 0;
|
|
183
|
+
let n = 0;
|
|
184
|
+
for (const b of bits) {
|
|
185
|
+
n = n * 2 + b;
|
|
186
|
+
}
|
|
187
|
+
return n;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function codePointToBitPath(cp, out = []) {
|
|
191
|
+
if (cp <= 0x7f) {
|
|
192
|
+
out.push(0);
|
|
193
|
+
pushBitsMSB(out, 7, cp);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
if (cp <= 0x7ff) {
|
|
197
|
+
out.push(1, 0);
|
|
198
|
+
pushBitsMSB(out, 11, cp);
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
if (cp <= 0xffff) {
|
|
202
|
+
const hi = (cp >> 8) & 0xff;
|
|
203
|
+
const lo = cp & 0xff;
|
|
204
|
+
if (hi >= 0xe0 && hi <= 0xf8) {
|
|
205
|
+
out.push(1, 1, 1, 0);
|
|
206
|
+
if (hi <= 0xef) {
|
|
207
|
+
out.push(0);
|
|
208
|
+
pushBitsMSB(out, 4, hi - 0xe0);
|
|
209
|
+
} else if (hi <= 0xf7) {
|
|
210
|
+
out.push(1, 0);
|
|
211
|
+
pushBitsMSB(out, 3, hi - 0xf0);
|
|
212
|
+
} else {
|
|
213
|
+
out.push(1, 1);
|
|
214
|
+
}
|
|
215
|
+
pushBitsMSB(out, 8, lo);
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
out.push(1, 1, 0);
|
|
219
|
+
if (hi >= 0x08 && hi <= 0x0f) {
|
|
220
|
+
out.push(0, 0, 0);
|
|
221
|
+
pushBitsMSB(out, 3, hi - 0x08);
|
|
222
|
+
} else if (hi >= 0x10 && hi <= 0x7f) {
|
|
223
|
+
out.push(0, 0, 1);
|
|
224
|
+
pushBitsMSB(out, 7, hi - 0x10);
|
|
225
|
+
} else if (hi >= 0x80 && hi <= 0xcf) {
|
|
226
|
+
out.push(0, 1, 0);
|
|
227
|
+
pushBitsMSB(out, 7, hi - 0x80);
|
|
228
|
+
} else if (hi >= 0xd0 && hi <= 0xd7) {
|
|
229
|
+
out.push(0, 1, 1);
|
|
230
|
+
pushBitsMSB(out, 3, hi - 0xd0);
|
|
231
|
+
} else if (hi === 0xf9) {
|
|
232
|
+
out.push(1, 0, 0);
|
|
233
|
+
} else if (hi >= 0xfa && hi <= 0xfb) {
|
|
234
|
+
out.push(1, 0, 1);
|
|
235
|
+
pushBitsMSB(out, 1, hi - 0xfa);
|
|
236
|
+
} else if (hi >= 0xfc && hi <= 0xfd) {
|
|
237
|
+
out.push(1, 1, 0);
|
|
238
|
+
pushBitsMSB(out, 1, hi - 0xfc);
|
|
239
|
+
} else if (hi >= 0xfe && hi <= 0xff) {
|
|
240
|
+
out.push(1, 1, 1);
|
|
241
|
+
pushBitsMSB(out, 1, hi - 0xfe);
|
|
242
|
+
} else {
|
|
243
|
+
throw new Error(`Unsupported BMP hi byte: ${hi}`);
|
|
244
|
+
}
|
|
245
|
+
pushBitsMSB(out, 8, lo);
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
const mid = (cp >> 8) & 0xff;
|
|
249
|
+
const lo = cp & 0xff;
|
|
250
|
+
if (cp <= 0x1ffff) {
|
|
251
|
+
out.push(1, 1, 1, 1, 0);
|
|
252
|
+
pushBitsMSB(out, 8, mid);
|
|
253
|
+
pushBitsMSB(out, 8, lo);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
const plane = cp >> 16;
|
|
257
|
+
out.push(1, 1, 1, 1, 1);
|
|
258
|
+
if (plane >= 2 && plane <= 3) {
|
|
259
|
+
out.push(0, 0);
|
|
260
|
+
pushBitsMSB(out, 1, plane - 2);
|
|
261
|
+
} else if (plane >= 4 && plane <= 7) {
|
|
262
|
+
out.push(0, 1);
|
|
263
|
+
pushBitsMSB(out, 2, plane - 4);
|
|
264
|
+
} else if (plane >= 8 && plane <= 15) {
|
|
265
|
+
out.push(1, 0);
|
|
266
|
+
pushBitsMSB(out, 3, plane - 8);
|
|
267
|
+
} else if (plane === 16) {
|
|
268
|
+
out.push(1, 1);
|
|
269
|
+
} else {
|
|
270
|
+
throw new Error(`Unsupported plane: ${plane}`);
|
|
271
|
+
}
|
|
272
|
+
pushBitsMSB(out, 8, mid);
|
|
273
|
+
pushBitsMSB(out, 8, lo);
|
|
274
|
+
return out;
|
|
149
275
|
}
|
|
150
276
|
|
|
151
|
-
function
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
277
|
+
export function stringToBitPath(str) {
|
|
278
|
+
const out = [];
|
|
279
|
+
for (const ch of str) {
|
|
280
|
+
codePointToBitPath(ch.codePointAt(0), out);
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function bitPathToString(bits) {
|
|
286
|
+
let i = 0;
|
|
287
|
+
let out = "";
|
|
288
|
+
while (i < bits.length) {
|
|
289
|
+
if (bits[i] === 0) {
|
|
290
|
+
const cp = readBitsMSB(bits, i + 1, 7);
|
|
291
|
+
out += String.fromCodePoint(cp);
|
|
292
|
+
i += 8;
|
|
293
|
+
continue;
|
|
157
294
|
}
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
295
|
+
if (bits[i + 1] === 0) {
|
|
296
|
+
const cp = readBitsMSB(bits, i + 2, 11);
|
|
297
|
+
out += String.fromCodePoint(cp);
|
|
298
|
+
i += 13;
|
|
299
|
+
continue;
|
|
161
300
|
}
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
301
|
+
if (bits[i + 2] === 0) {
|
|
302
|
+
i += 3;
|
|
303
|
+
const tag = readBitsMSB(bits, i, 3);
|
|
304
|
+
i += 3;
|
|
305
|
+
let hi;
|
|
306
|
+
if (tag === 0) {
|
|
307
|
+
hi = 0x08 + readBitsMSB(bits, i, 3);
|
|
308
|
+
i += 3;
|
|
309
|
+
} else if (tag === 1) {
|
|
310
|
+
hi = 0x10 + readBitsMSB(bits, i, 7);
|
|
311
|
+
i += 7;
|
|
312
|
+
} else if (tag === 2) {
|
|
313
|
+
hi = 0x80 + readBitsMSB(bits, i, 7);
|
|
314
|
+
i += 7;
|
|
315
|
+
} else if (tag === 3) {
|
|
316
|
+
hi = 0xd0 + readBitsMSB(bits, i, 3);
|
|
317
|
+
i += 3;
|
|
318
|
+
} else if (tag === 4) {
|
|
319
|
+
hi = 0xf9;
|
|
320
|
+
} else if (tag === 5) {
|
|
321
|
+
hi = 0xfa + readBitsMSB(bits, i, 1);
|
|
322
|
+
i += 1;
|
|
323
|
+
} else if (tag === 6) {
|
|
324
|
+
hi = 0xfc + readBitsMSB(bits, i, 1);
|
|
325
|
+
i += 1;
|
|
326
|
+
} else {
|
|
327
|
+
hi = 0xfe + readBitsMSB(bits, i, 1);
|
|
328
|
+
i += 1;
|
|
329
|
+
}
|
|
330
|
+
const lo = readBitsMSB(bits, i, 8);
|
|
331
|
+
i += 8;
|
|
332
|
+
out += String.fromCodePoint((hi << 8) | lo);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (bits[i + 3] === 0) {
|
|
336
|
+
i += 4;
|
|
337
|
+
let hi;
|
|
338
|
+
if (bits[i] === 0) {
|
|
339
|
+
hi = 0xe0 + readBitsMSB(bits, i + 1, 4);
|
|
340
|
+
i += 5;
|
|
341
|
+
} else if (bits[i + 1] === 0) {
|
|
342
|
+
hi = 0xf0 + readBitsMSB(bits, i + 2, 3);
|
|
343
|
+
i += 5;
|
|
344
|
+
} else {
|
|
345
|
+
hi = 0xf8;
|
|
346
|
+
i += 2;
|
|
347
|
+
}
|
|
348
|
+
const lo = readBitsMSB(bits, i, 8);
|
|
349
|
+
i += 8;
|
|
350
|
+
out += String.fromCodePoint((hi << 8) | lo);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (bits[i + 4] === 0) {
|
|
354
|
+
i += 5;
|
|
355
|
+
const mid = readBitsMSB(bits, i, 8);
|
|
356
|
+
const lo = readBitsMSB(bits, i + 8, 8);
|
|
357
|
+
i += 16;
|
|
358
|
+
out += String.fromCodePoint((1 << 16) | (mid << 8) | lo);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
i += 5;
|
|
362
|
+
let plane;
|
|
363
|
+
if (bits[i] === 0 && bits[i + 1] === 0) {
|
|
364
|
+
plane = 2 + readBitsMSB(bits, i + 2, 1);
|
|
365
|
+
i += 3;
|
|
366
|
+
} else if (bits[i] === 0 && bits[i + 1] === 1) {
|
|
367
|
+
plane = 4 + readBitsMSB(bits, i + 2, 2);
|
|
368
|
+
i += 4;
|
|
369
|
+
} else if (bits[i] === 1 && bits[i + 1] === 0) {
|
|
370
|
+
plane = 8 + readBitsMSB(bits, i + 2, 3);
|
|
371
|
+
i += 5;
|
|
372
|
+
} else {
|
|
373
|
+
plane = 16;
|
|
374
|
+
i += 2;
|
|
375
|
+
}
|
|
376
|
+
const mid = readBitsMSB(bits, i, 8);
|
|
377
|
+
const lo = readBitsMSB(bits, i + 8, 8);
|
|
378
|
+
i += 16;
|
|
379
|
+
out += String.fromCodePoint((plane << 16) | (mid << 8) | lo);
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function makeMutableNode() {
|
|
385
|
+
return { val: null, b0: null, b1: null };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function insertMutableTrie(root, bits, kVal) {
|
|
389
|
+
let curr = root;
|
|
390
|
+
for (let i = 0; i < bits.length; i++) {
|
|
391
|
+
if (bits[i] === 0) {
|
|
392
|
+
if (!curr.b0) curr.b0 = makeMutableNode();
|
|
393
|
+
curr = curr.b0;
|
|
394
|
+
} else {
|
|
395
|
+
if (!curr.b1) curr.b1 = makeMutableNode();
|
|
396
|
+
curr = curr.b1;
|
|
166
397
|
}
|
|
167
|
-
const pair = requireProduct(current.value, `${where}.cons`);
|
|
168
|
-
result.push(pair.car);
|
|
169
|
-
current = pair.cdr;
|
|
170
398
|
}
|
|
399
|
+
curr.val = kVal;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function mutableNodeToK(node) {
|
|
403
|
+
if (!node) return NIL;
|
|
404
|
+
const valField = node.val !== null
|
|
405
|
+
? Value.variant("val", node.val)
|
|
406
|
+
: NIL;
|
|
407
|
+
const b0 = mutableNodeToK(node.b0);
|
|
408
|
+
const b1 = mutableNodeToK(node.b1);
|
|
409
|
+
if (node.val === null && b0 === NIL && b1 === NIL) {
|
|
410
|
+
return NIL;
|
|
411
|
+
}
|
|
412
|
+
return Value.variant("trie", Value.product({
|
|
413
|
+
val: valField,
|
|
414
|
+
0: b0,
|
|
415
|
+
1: b1
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function collectTrieEntries(kNode, pathBits = [], entries = []) {
|
|
420
|
+
if (!isVariant(kNode)) {
|
|
421
|
+
throw new Error("djson: expected variant (nil or trie)");
|
|
422
|
+
}
|
|
423
|
+
if (kNode.tag === "nil") {
|
|
424
|
+
return entries;
|
|
425
|
+
}
|
|
426
|
+
if (kNode.tag !== "trie") {
|
|
427
|
+
throw new Error(`djson: unexpected tag ${kNode.tag}`);
|
|
428
|
+
}
|
|
429
|
+
const prod = requireProduct(kNode.value, "djson.trie");
|
|
430
|
+
if (!isVariant(prod.val)) {
|
|
431
|
+
throw new Error("djson.trie.val: expected variant");
|
|
432
|
+
}
|
|
433
|
+
if (prod.val.tag === "val") {
|
|
434
|
+
entries.push({ bits: pathBits.slice(), val: prod.val.value });
|
|
435
|
+
} else if (prod.val.tag !== "nil") {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`djson.trie.val: unexpected tag ${prod.val.tag}`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
pathBits.push(0);
|
|
441
|
+
collectTrieEntries(prod[0], pathBits, entries);
|
|
442
|
+
pathBits.pop();
|
|
443
|
+
pathBits.push(1);
|
|
444
|
+
collectTrieEntries(prod[1], pathBits, entries);
|
|
445
|
+
pathBits.pop();
|
|
446
|
+
return entries;
|
|
171
447
|
}
|
|
172
448
|
|
|
173
449
|
export function fromJsonValue(value) {
|
|
@@ -190,81 +466,96 @@ export function fromJsonValue(value) {
|
|
|
190
466
|
return Value.variant("string", textToStringValue(value));
|
|
191
467
|
}
|
|
192
468
|
if (Array.isArray(value)) {
|
|
193
|
-
|
|
469
|
+
if (value.length === 0) {
|
|
470
|
+
return Value.variant("list", NIL);
|
|
471
|
+
}
|
|
472
|
+
const root = makeMutableNode();
|
|
473
|
+
for (let i = 0; i < value.length; i++) {
|
|
474
|
+
insertMutableTrie(root, indexToBitPath(i), fromJsonValue(value[i]));
|
|
475
|
+
}
|
|
476
|
+
return Value.variant("list", mutableNodeToK(root));
|
|
194
477
|
}
|
|
195
478
|
if (value && typeof value === "object") {
|
|
196
|
-
const keys = Object.keys(value)
|
|
197
|
-
|
|
198
|
-
Value.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
479
|
+
const keys = Object.keys(value);
|
|
480
|
+
if (keys.length === 0) {
|
|
481
|
+
return Value.variant("object", NIL);
|
|
482
|
+
}
|
|
483
|
+
const root = makeMutableNode();
|
|
484
|
+
for (const k of keys) {
|
|
485
|
+
insertMutableTrie(
|
|
486
|
+
root,
|
|
487
|
+
stringToBitPath(k),
|
|
488
|
+
fromJsonValue(value[k])
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return Value.variant("object", mutableNodeToK(root));
|
|
204
492
|
}
|
|
205
493
|
throw new Error(`Unsupported JSON value: ${value}`);
|
|
206
494
|
}
|
|
207
495
|
|
|
208
496
|
export function toJsonValue(value) {
|
|
209
497
|
if (!isVariant(value)) {
|
|
210
|
-
throw new Error("djson
|
|
498
|
+
throw new Error("djson: expected top-level Variant");
|
|
211
499
|
}
|
|
212
|
-
|
|
213
500
|
if (value.tag === "null") {
|
|
214
501
|
requireProduct(value.value, "djson.null");
|
|
215
502
|
return null;
|
|
216
503
|
}
|
|
217
|
-
|
|
218
504
|
if (value.tag === "bool") {
|
|
219
505
|
if (!isVariant(value.value)) {
|
|
220
506
|
throw new Error("djson.bool: expected variant");
|
|
221
507
|
}
|
|
222
|
-
if (value.value.tag === "true")
|
|
223
|
-
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
if (value.value.tag === "false") {
|
|
227
|
-
requireProduct(value.value.value, "djson.bool.false");
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
508
|
+
if (value.value.tag === "true") return true;
|
|
509
|
+
if (value.value.tag === "false") return false;
|
|
230
510
|
throw new Error(`djson.bool: invalid tag ${value.value.tag}`);
|
|
231
511
|
}
|
|
232
|
-
|
|
233
512
|
if (value.tag === "number") {
|
|
234
513
|
return decodeValueToNumber(value.value);
|
|
235
514
|
}
|
|
236
|
-
|
|
237
515
|
if (value.tag === "string") {
|
|
238
516
|
return stringValueToText(value.value);
|
|
239
517
|
}
|
|
240
|
-
|
|
241
518
|
if (value.tag === "list") {
|
|
242
|
-
const
|
|
243
|
-
|
|
519
|
+
const entries = collectTrieEntries(value.value);
|
|
520
|
+
entries.sort((a, b) =>
|
|
521
|
+
bitPathToIndex(a.bits) - bitPathToIndex(b.bits)
|
|
522
|
+
);
|
|
523
|
+
return entries.map((e) => toJsonValue(e.val));
|
|
244
524
|
}
|
|
245
|
-
|
|
246
525
|
if (value.tag === "object") {
|
|
247
|
-
const
|
|
526
|
+
const entries = collectTrieEntries(value.value);
|
|
248
527
|
const result = {};
|
|
249
|
-
for (const
|
|
250
|
-
|
|
251
|
-
const key = stringValueToText(pair.key);
|
|
252
|
-
result[key] = toJsonValue(pair.val);
|
|
528
|
+
for (const e of entries) {
|
|
529
|
+
result[bitPathToString(e.bits)] = toJsonValue(e.val);
|
|
253
530
|
}
|
|
254
531
|
return result;
|
|
255
532
|
}
|
|
256
|
-
|
|
257
|
-
throw new Error(`Unsupported djson tag: ${value.tag}`);
|
|
533
|
+
throw new Error(`djson: unsupported tag ${value.tag}`);
|
|
258
534
|
}
|
|
259
535
|
|
|
260
|
-
export
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
536
|
+
export const DJSON_BTRIE_PATTERN = DJSON_PATTERN;
|
|
537
|
+
export const pattern = DJSON_PATTERN;
|
|
538
|
+
|
|
539
|
+
export function parse(rawText) {
|
|
540
|
+
const text = String(rawText).trim();
|
|
541
|
+
if (!text) throw new Error("Empty JSON input");
|
|
542
|
+
const parsed = JSON.parse(text);
|
|
543
|
+
const val = fromJsonValue(parsed);
|
|
544
|
+
return withPattern(val, DJSON_PATTERN);
|
|
264
545
|
}
|
|
265
546
|
|
|
266
547
|
export function print(value) {
|
|
267
548
|
return JSON.stringify(toJsonValue(value));
|
|
268
549
|
}
|
|
269
550
|
|
|
551
|
+
export const codec = {
|
|
552
|
+
name: "djson",
|
|
553
|
+
doc,
|
|
554
|
+
pattern: DJSON_PATTERN,
|
|
555
|
+
parse,
|
|
556
|
+
print
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
export default codec;
|
|
560
|
+
|
|
270
561
|
runCodecCLI(import.meta.url, { parse, print, doc });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"description": "k — a language for content-addressable schemas and data transformations over algebraic data types (products and tagged unions); includes runtime, contract derivation, REPL, and codec toolchain",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"perf:poly:pkir": "BACKENDS=pkir node scripts/perf-poly.mjs",
|
|
34
34
|
"perf:poly:llvm-pkir": "BACKENDS=llvm-pkir node scripts/perf-poly.mjs",
|
|
35
35
|
"perf:poly:trace": "node scripts/perf-poly.mjs --trace",
|
|
36
|
-
"perf:djson": "node scripts/perf-djson.mjs",
|
|
37
36
|
"perf:idempotency": "node scripts/benchmark-idempotency.mjs",
|
|
38
37
|
"deps:graph": "node scripts/generate-dependency-graph.mjs",
|
|
39
38
|
"compare": "node harness/cli.mjs",
|
|
@@ -60,8 +59,6 @@
|
|
|
60
59
|
"k-wasm-compile": "backends/wasm/bin/k-wasm-compile.mjs",
|
|
61
60
|
"k-wasm-run": "backends/wasm/bin/k-wasm-run.mjs",
|
|
62
61
|
"k-djson": "codecs/djson.mjs",
|
|
63
|
-
"k-djson-btrie": "codecs/djson-btrie.mjs",
|
|
64
|
-
"k-djson-ptrie": "codecs/djson-ptrie.mjs",
|
|
65
62
|
"k-ieee": "codecs/ieee.mjs",
|
|
66
63
|
"k-int": "codecs/int.mjs",
|
|
67
64
|
"k-json": "codecs/json.mjs",
|
|
@@ -84,9 +84,7 @@ const verifiedExamples = [
|
|
|
84
84
|
"ieee.k",
|
|
85
85
|
"poly.k",
|
|
86
86
|
"karatsuba-mult.k",
|
|
87
|
-
"djson.k"
|
|
88
|
-
"djson-btrie.k",
|
|
89
|
-
"djson-ptrie.k"
|
|
87
|
+
"djson.k"
|
|
90
88
|
];
|
|
91
89
|
const examplesDir = path.join(projectRoot, "Examples");
|
|
92
90
|
for (const filename of verifiedExamples) {
|
|
@@ -102,8 +100,6 @@ for (const filename of verifiedExamples) {
|
|
|
102
100
|
const verifiedCodecs = [
|
|
103
101
|
"json.mjs",
|
|
104
102
|
"djson.mjs",
|
|
105
|
-
"djson-btrie.mjs",
|
|
106
|
-
"djson-ptrie.mjs",
|
|
107
103
|
"utf8.mjs",
|
|
108
104
|
"int.mjs",
|
|
109
105
|
"unit.mjs",
|
|
@@ -1698,8 +1694,6 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1698
1694
|
<option value="poly.k">poly.k</option>
|
|
1699
1695
|
<option value="karatsuba-mult.k">karatsuba-mult.k</option>
|
|
1700
1696
|
<option value="djson.k">djson.k</option>
|
|
1701
|
-
<option value="djson-btrie.k">djson-btrie.k</option>
|
|
1702
|
-
<option value="djson-ptrie.k">djson-ptrie.k</option>
|
|
1703
1697
|
</optgroup>
|
|
1704
1698
|
<optgroup label="Quick Expressions">
|
|
1705
1699
|
<option value="expr:{} | ok">{} | ok</option>
|
|
@@ -1720,14 +1714,10 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1720
1714
|
<option value="expr::codec load utf8.mjs">:codec load utf8.mjs</option>
|
|
1721
1715
|
<option value="expr::codec load json.mjs">:codec load json.mjs</option>
|
|
1722
1716
|
<option value="expr::codec load djson.mjs">:codec load djson.mjs</option>
|
|
1723
|
-
<option value="expr::codec load djson-btrie.mjs">:codec load djson-btrie.mjs</option>
|
|
1724
|
-
<option value="expr::codec load djson-ptrie.mjs">:codec load djson-ptrie.mjs</option>
|
|
1725
1717
|
<option value="expr::codec load ieee.mjs">:codec load ieee.mjs</option>
|
|
1726
1718
|
<option value="expr::codec load unit.mjs">:codec load unit.mjs</option>
|
|
1727
1719
|
<option value="expr::input json.mjs {"a":12,"b":"hello"}">:input json.mjs {"a":12,"b":"hello"}</option>
|
|
1728
1720
|
<option value="expr::input djson.mjs {"a":12,"b":"hello"}">:input djson.mjs {"a":12,"b":"hello"}</option>
|
|
1729
|
-
<option value="expr::input djson-btrie.mjs {"a":12,"b":"hello"}">:input djson-btrie.mjs {"a":12,"b":"hello"}</option>
|
|
1730
|
-
<option value="expr::input djson-ptrie.mjs {"a":12,"b":"hello"}">:input djson-ptrie.mjs {"a":12,"b":"hello"}</option>
|
|
1731
1721
|
<option value="expr::input int.mjs [0,1,2]">:input int.mjs [0,1,2]</option>
|
|
1732
1722
|
</optgroup>
|
|
1733
1723
|
</select>
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -43,6 +43,11 @@ const tests = [
|
|
|
43
43
|
["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
|
|
44
44
|
["tests/test-structural-values.mjs", node, ["tests/test-structural-values.mjs"]],
|
|
45
45
|
["tests/test-codecs.mjs", node, ["tests/test-codecs.mjs"]],
|
|
46
|
+
[
|
|
47
|
+
"tests/test-djson-methods.mjs",
|
|
48
|
+
node,
|
|
49
|
+
["tests/test-djson-methods.mjs"]
|
|
50
|
+
],
|
|
46
51
|
["tests/test-deep-wire-value.mjs", node, ["tests/test-deep-wire-value.mjs"]],
|
|
47
52
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
48
53
|
["tests/test-kir.mjs", node, ["tests/test-kir.mjs"]],
|