@fraczak/k 7.2.0 → 8.0.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 +66 -0
- package/DOCS/BIG_PICTURE.md +54 -7
- package/DOCS/CODECS.md +21 -11
- package/DOCS/CODEC_ARCHITECTURE.md +5 -3
- package/DOCS/DICTIONARY.md +13 -4
- package/DOCS/FILE_FORMATS.md +3 -2
- package/DOCS/JSON_APPROACHES_COMPARISON.md +306 -0
- package/DOCS/NAMED_PATTERNS.md +317 -0
- package/DOCS/PATTERNS.md +59 -11
- package/DOCS/REPL.md +13 -2
- package/DOCS/WEB_REPL_REQUIREMENTS.md +4 -2
- package/Examples/arithmetics.k +22 -17
- package/Examples/bnat.k +21 -11
- package/Examples/buda.k +37 -20
- package/Examples/byte.k +3 -1
- package/Examples/code.k +38 -39
- package/Examples/djson-btrie.k +414 -0
- package/Examples/djson-ptrie.k +663 -0
- package/Examples/djson.k +323 -0
- package/Examples/esc.k +1 -1
- package/Examples/karatsuba-mult.k +16 -13
- package/Examples/list.k +8 -4
- package/Examples/nat.k +17 -11
- package/Examples/poly.k +11 -3
- package/Examples/vhdl_to_k.k +43 -31
- package/README.md +42 -16
- package/browser/repl-browser.mjs +6 -0
- package/browser/tree-view.mjs +35 -16
- package/browser/vfs-data.mjs +15 -7
- package/codecs/README.md +2 -0
- package/codecs/djson-btrie.mjs +560 -0
- package/codecs/djson-ptrie.mjs +587 -0
- package/core.k +79 -115
- package/index.mjs +3 -0
- package/named-patterns.mjs +267 -0
- package/object.mjs +31 -3
- package/package.json +4 -1
- package/parser.jison +68 -5
- package/parser.mjs +111 -60
- package/pretty.mjs +5 -0
- package/repl.mjs +188 -22
- package/scripts/build-repl-html.mjs +68 -4
- package/scripts/perf-djson.mjs +1142 -0
- package/scripts/run-tests.mjs +5 -0
- package/symbol-table.mjs +20 -2
- package/tests/bench-djson-tries.mjs +340 -0
- package/tests/test-codecs.mjs +59 -16
- package/tests/test-named-patterns.mjs +189 -0
- package/tests/test-repl-html.mjs +51 -4
- package/tests/test-repl.mjs +190 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic JSON Patricia/Radix Trie (djson-ptrie) codec.
|
|
5
|
+
*
|
|
6
|
+
* Implements the trie-array data structure for both JSON arrays (list)
|
|
7
|
+
* and JSON objects (object):
|
|
8
|
+
*
|
|
9
|
+
* - `list`: Radix/Patricia bit-trie with compressed bit `prefix` and
|
|
10
|
+
* subtree `size`:
|
|
11
|
+
* LIST = <
|
|
12
|
+
* {} nil,
|
|
13
|
+
* {
|
|
14
|
+
* BITS prefix,
|
|
15
|
+
* BITS size,
|
|
16
|
+
* < {} nil, JSON val > val,
|
|
17
|
+
* LIST 0,
|
|
18
|
+
* LIST 1
|
|
19
|
+
* } trie
|
|
20
|
+
* >
|
|
21
|
+
*
|
|
22
|
+
* - `object`: Character-level Radix/Patricia trie (trie-array over
|
|
23
|
+
* Unicode characters) with compressed `prefix` string, `size`, `val`,
|
|
24
|
+
* and sorted `dict` of `{ CHAR char, OBJ trie }` branches:
|
|
25
|
+
* OBJ = <
|
|
26
|
+
* {} nil,
|
|
27
|
+
* {
|
|
28
|
+
* Str prefix,
|
|
29
|
+
* BITS size,
|
|
30
|
+
* < {} nil, JSON val > val,
|
|
31
|
+
* <
|
|
32
|
+
* {} nil,
|
|
33
|
+
* { { CHAR char, OBJ trie } car, DICT cdr } cons
|
|
34
|
+
* >=DICT dict
|
|
35
|
+
* } trie
|
|
36
|
+
* >
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
Value,
|
|
41
|
+
isProduct,
|
|
42
|
+
isVariant,
|
|
43
|
+
withPattern,
|
|
44
|
+
patternFromFilter,
|
|
45
|
+
runCodecCLI
|
|
46
|
+
} from "./runtime/codec-sdk.mjs";
|
|
47
|
+
import { encodeNumberToValue, decodeValueToNumber } from "./ieee.mjs";
|
|
48
|
+
import {
|
|
49
|
+
codePointToUnicodeValue,
|
|
50
|
+
unicodeValueToCodePoint,
|
|
51
|
+
textToStringValue,
|
|
52
|
+
stringValueToText
|
|
53
|
+
} from "./runtime/unicode-string.mjs";
|
|
54
|
+
import { indexToBitPath, bitPathToIndex } from "./djson-btrie.mjs";
|
|
55
|
+
|
|
56
|
+
export const doc = `
|
|
57
|
+
Dynamic JSON Patricia/Radix Trie (djson-ptrie) codec (trie-array style).
|
|
58
|
+
|
|
59
|
+
Represents JSON arrays with a compressed bit-radix trie (with prefix &
|
|
60
|
+
size) and JSON objects with a character-level radix trie (with prefix,
|
|
61
|
+
size, val, and sorted dict).
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
export const DJSON_PTRIE_PATTERN = patternFromFilter(`
|
|
65
|
+
? <
|
|
66
|
+
{
|
|
67
|
+
< {} "+", {} "-" > sign,
|
|
68
|
+
{
|
|
69
|
+
< {} 0, {} 1 >=Bit 0,
|
|
70
|
+
Bit 1, Bit 2, Bit 3, Bit 4, Bit 5,
|
|
71
|
+
Bit 6, Bit 7, Bit 8, Bit 9, Bit 10
|
|
72
|
+
} exponent,
|
|
73
|
+
{
|
|
74
|
+
Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5,
|
|
75
|
+
Bit 6, Bit 7, Bit 8, Bit 9, Bit 10, Bit 11,
|
|
76
|
+
Bit 12, Bit 13, Bit 14, Bit 15, Bit 16, Bit 17,
|
|
77
|
+
Bit 18, Bit 19, Bit 20, Bit 21, Bit 22, Bit 23,
|
|
78
|
+
Bit 24, Bit 25, Bit 26, Bit 27, Bit 28, Bit 29,
|
|
79
|
+
Bit 30, Bit 31, Bit 32, Bit 33, Bit 34, Bit 35,
|
|
80
|
+
Bit 36, Bit 37, Bit 38, Bit 39, Bit 40, Bit 41,
|
|
81
|
+
Bit 42, Bit 43, Bit 44, Bit 45, Bit 46, Bit 47,
|
|
82
|
+
Bit 48, Bit 49, Bit 50, Bit 51
|
|
83
|
+
} fraction
|
|
84
|
+
} number,
|
|
85
|
+
|
|
86
|
+
<
|
|
87
|
+
{} nil,
|
|
88
|
+
{
|
|
89
|
+
<
|
|
90
|
+
{
|
|
91
|
+
< {} 0, {} 1 >=Bit7 0,
|
|
92
|
+
Bit7 1, Bit7 2, Bit7 3, Bit7 4, Bit7 5, Bit7 6
|
|
93
|
+
}=Bits7 ascii,
|
|
94
|
+
{
|
|
95
|
+
<
|
|
96
|
+
{ Bit7 0, Bit7 1, Bit7 2 }=Bits3 h08_0F,
|
|
97
|
+
Bits7 h10_7F,
|
|
98
|
+
Bits7 h80_CF,
|
|
99
|
+
Bits3 hD0_D7,
|
|
100
|
+
{} hF9,
|
|
101
|
+
{ Bit7 0 }=Bits1 hFA_FB,
|
|
102
|
+
Bits1 hFC_FD,
|
|
103
|
+
Bits1 hFE_FF
|
|
104
|
+
> hi,
|
|
105
|
+
{
|
|
106
|
+
Bit7 0, Bit7 1, Bit7 2, Bit7 3,
|
|
107
|
+
Bit7 4, Bit7 5, Bit7 6, Bit7 7
|
|
108
|
+
}=Bits8 lo
|
|
109
|
+
} bmp_common,
|
|
110
|
+
{
|
|
111
|
+
<
|
|
112
|
+
{ Bit7 0, Bit7 1, Bit7 2, Bit7 3 } hE0_EF,
|
|
113
|
+
Bits3 hF0_F7,
|
|
114
|
+
{} hF8
|
|
115
|
+
> hi,
|
|
116
|
+
Bits8 lo
|
|
117
|
+
} bmp_private_use,
|
|
118
|
+
{
|
|
119
|
+
Bit7 0, Bit7 1, Bit7 2, Bit7 3, Bit7 4, Bit7 5,
|
|
120
|
+
Bit7 6, Bit7 7, Bit7 8, Bit7 9, Bit7 10
|
|
121
|
+
} plane0,
|
|
122
|
+
{ Bits8 mid, Bits8 lo } supplementary_plane1,
|
|
123
|
+
{
|
|
124
|
+
<
|
|
125
|
+
Bits1 p02_03,
|
|
126
|
+
{ Bit7 0, Bit7 1 } p04_07,
|
|
127
|
+
Bits3 p08_0F,
|
|
128
|
+
{} p10
|
|
129
|
+
> plane,
|
|
130
|
+
Bits8 mid,
|
|
131
|
+
Bits8 lo
|
|
132
|
+
} supplementary_planes2_16
|
|
133
|
+
>=CHAR car,
|
|
134
|
+
Str cdr
|
|
135
|
+
} cons
|
|
136
|
+
> = Str string,
|
|
137
|
+
|
|
138
|
+
< {} true, {} false > bool,
|
|
139
|
+
{} null,
|
|
140
|
+
|
|
141
|
+
<
|
|
142
|
+
{} nil,
|
|
143
|
+
{
|
|
144
|
+
< {} _, BITS 0, BITS 1 >=BITS prefix,
|
|
145
|
+
BITS size,
|
|
146
|
+
< {} nil, JSON val > val,
|
|
147
|
+
LIST 0,
|
|
148
|
+
LIST 1
|
|
149
|
+
} trie
|
|
150
|
+
> = LIST list,
|
|
151
|
+
|
|
152
|
+
<
|
|
153
|
+
{} nil,
|
|
154
|
+
{
|
|
155
|
+
Str prefix,
|
|
156
|
+
BITS size,
|
|
157
|
+
< {} nil, JSON val > val,
|
|
158
|
+
<
|
|
159
|
+
{} nil,
|
|
160
|
+
{
|
|
161
|
+
{ CHAR char, OBJ trie } car,
|
|
162
|
+
DICT cdr
|
|
163
|
+
} cons
|
|
164
|
+
>=DICT dict
|
|
165
|
+
} trie
|
|
166
|
+
> = OBJ object
|
|
167
|
+
> = JSON
|
|
168
|
+
`);
|
|
169
|
+
|
|
170
|
+
const UNIT = Value.product({});
|
|
171
|
+
const NIL = Value.variant("nil", UNIT);
|
|
172
|
+
const BITS_EMPTY = Value.variant("_", UNIT);
|
|
173
|
+
|
|
174
|
+
function requireProduct(value, where) {
|
|
175
|
+
if (!isProduct(value)) {
|
|
176
|
+
throw new Error(`${where}: expected Product`);
|
|
177
|
+
}
|
|
178
|
+
return value.product;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Encode an array of bits [b0, b1, ..., bk-1] into a `bits?` chain
|
|
183
|
+
* where b0 is the outermost tag (`|0` or `|1`) and `_` is at the base.
|
|
184
|
+
*/
|
|
185
|
+
function bitArrayToBitsK(bits) {
|
|
186
|
+
let out = BITS_EMPTY;
|
|
187
|
+
for (let i = bits.length - 1; i >= 0; i--) {
|
|
188
|
+
out = Value.variant(bits[i] === 1 ? "1" : "0", out);
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function bitsKToBitArray(val) {
|
|
194
|
+
const out = [];
|
|
195
|
+
let cur = val;
|
|
196
|
+
while (true) {
|
|
197
|
+
if (!isVariant(cur)) {
|
|
198
|
+
throw new Error("djson-ptrie.bits: expected variant");
|
|
199
|
+
}
|
|
200
|
+
if (cur.tag === "_") return out;
|
|
201
|
+
if (cur.tag === "0") {
|
|
202
|
+
out.push(0);
|
|
203
|
+
cur = cur.value;
|
|
204
|
+
} else if (cur.tag === "1") {
|
|
205
|
+
out.push(1);
|
|
206
|
+
cur = cur.value;
|
|
207
|
+
} else {
|
|
208
|
+
throw new Error(`djson-ptrie.bits: unexpected tag ${cur.tag}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function natToBitsK(n) {
|
|
214
|
+
return bitArrayToBitsK(indexToBitPath(n));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ----------------------------------------------------------------------
|
|
218
|
+
// 1. Patricia Bit-Trie for `list` (trie-array over bits)
|
|
219
|
+
// ----------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
function commonBitPrefixLen(a, b) {
|
|
222
|
+
const len = Math.min(a.length, b.length);
|
|
223
|
+
let i = 0;
|
|
224
|
+
while (i < len && a[i] === b[i]) i++;
|
|
225
|
+
return i;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function splitBitTrieNode(x, n) {
|
|
229
|
+
const bitAtN = x.prefix[n];
|
|
230
|
+
const y = {
|
|
231
|
+
size: x.size - 1,
|
|
232
|
+
prefix: x.prefix.slice(n + 1),
|
|
233
|
+
val: x.val,
|
|
234
|
+
b0: x.b0,
|
|
235
|
+
b1: x.b1
|
|
236
|
+
};
|
|
237
|
+
x.prefix = x.prefix.slice(0, n);
|
|
238
|
+
x.val = null;
|
|
239
|
+
x.b0 = bitAtN === 0 ? y : null;
|
|
240
|
+
x.b1 = bitAtN === 1 ? y : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function addToBitTrie(t, bits, kVal) {
|
|
244
|
+
let curr = t;
|
|
245
|
+
let s = bits;
|
|
246
|
+
while (true) {
|
|
247
|
+
curr.size++;
|
|
248
|
+
const pLen = commonBitPrefixLen(curr.prefix, s);
|
|
249
|
+
if (pLen === s.length && pLen === curr.prefix.length) {
|
|
250
|
+
curr.val = kVal;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (pLen === s.length) {
|
|
254
|
+
splitBitTrieNode(curr, pLen);
|
|
255
|
+
curr.val = kVal;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (pLen !== curr.prefix.length) {
|
|
259
|
+
splitBitTrieNode(curr, pLen);
|
|
260
|
+
const nextBit = s[pLen];
|
|
261
|
+
const newChild = {
|
|
262
|
+
size: 1,
|
|
263
|
+
prefix: s.slice(pLen + 1),
|
|
264
|
+
val: kVal,
|
|
265
|
+
b0: null,
|
|
266
|
+
b1: null
|
|
267
|
+
};
|
|
268
|
+
if (nextBit === 0) curr.b0 = newChild;
|
|
269
|
+
else curr.b1 = newChild;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const nextBit = s[pLen];
|
|
273
|
+
const q = nextBit === 0 ? curr.b0 : curr.b1;
|
|
274
|
+
if (!q) {
|
|
275
|
+
const newChild = {
|
|
276
|
+
size: 1,
|
|
277
|
+
prefix: s.slice(pLen + 1),
|
|
278
|
+
val: kVal,
|
|
279
|
+
b0: null,
|
|
280
|
+
b1: null
|
|
281
|
+
};
|
|
282
|
+
if (nextBit === 0) curr.b0 = newChild;
|
|
283
|
+
else curr.b1 = newChild;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
curr = q;
|
|
287
|
+
s = s.slice(pLen + 1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function bitTrieNodeToK(node) {
|
|
292
|
+
if (!node) return NIL;
|
|
293
|
+
return Value.variant("trie", Value.product({
|
|
294
|
+
prefix: bitArrayToBitsK(node.prefix),
|
|
295
|
+
size: natToBitsK(node.size),
|
|
296
|
+
val: node.val !== null ? Value.variant("val", node.val) : NIL,
|
|
297
|
+
0: bitTrieNodeToK(node.b0),
|
|
298
|
+
1: bitTrieNodeToK(node.b1)
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function collectBitTrieEntries(kNode, pathBits = [], entries = []) {
|
|
303
|
+
if (!isVariant(kNode)) {
|
|
304
|
+
throw new Error("djson-ptrie.list: expected variant");
|
|
305
|
+
}
|
|
306
|
+
if (kNode.tag === "nil") return entries;
|
|
307
|
+
if (kNode.tag !== "trie") {
|
|
308
|
+
throw new Error(`djson-ptrie.list: unexpected tag ${kNode.tag}`);
|
|
309
|
+
}
|
|
310
|
+
const prod = requireProduct(kNode.value, "djson-ptrie.list.trie");
|
|
311
|
+
const nodePrefix = bitsKToBitArray(prod.prefix);
|
|
312
|
+
const fullPrefix = pathBits.concat(nodePrefix);
|
|
313
|
+
if (isVariant(prod.val) && prod.val.tag === "val") {
|
|
314
|
+
entries.push({ bits: fullPrefix, val: prod.val.value });
|
|
315
|
+
}
|
|
316
|
+
collectBitTrieEntries(prod[0], fullPrefix.concat([0]), entries);
|
|
317
|
+
collectBitTrieEntries(prod[1], fullPrefix.concat([1]), entries);
|
|
318
|
+
return entries;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ----------------------------------------------------------------------
|
|
322
|
+
// 2. Character-Level Radix Trie for `object` (Unicode radix-tree)
|
|
323
|
+
// ----------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
function commonCharPrefixLen(a, b) {
|
|
326
|
+
const len = Math.min(a.length, b.length);
|
|
327
|
+
let i = 0;
|
|
328
|
+
while (i < len && a[i] === b[i]) i++;
|
|
329
|
+
return i;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function splitCharTrieNode(x, n) {
|
|
333
|
+
const charAtN = x.prefix[n];
|
|
334
|
+
const y = {
|
|
335
|
+
size: x.size - 1,
|
|
336
|
+
prefix: x.prefix.slice(n + 1),
|
|
337
|
+
val: x.val,
|
|
338
|
+
dict: x.dict,
|
|
339
|
+
order: x.order
|
|
340
|
+
};
|
|
341
|
+
x.dict = new Map([[charAtN, y]]);
|
|
342
|
+
x.order = [charAtN];
|
|
343
|
+
x.prefix = x.prefix.slice(0, n);
|
|
344
|
+
x.val = null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function addToCharTrie(t, cpArray, kVal) {
|
|
348
|
+
let curr = t;
|
|
349
|
+
let s = cpArray;
|
|
350
|
+
while (true) {
|
|
351
|
+
curr.size++;
|
|
352
|
+
const pLen = commonCharPrefixLen(curr.prefix, s);
|
|
353
|
+
if (pLen === s.length && pLen === curr.prefix.length) {
|
|
354
|
+
curr.val = kVal;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (pLen === s.length) {
|
|
358
|
+
splitCharTrieNode(curr, pLen);
|
|
359
|
+
curr.val = kVal;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (pLen !== curr.prefix.length) {
|
|
363
|
+
splitCharTrieNode(curr, pLen);
|
|
364
|
+
const c = s[pLen];
|
|
365
|
+
curr.dict.set(c, {
|
|
366
|
+
size: 1,
|
|
367
|
+
prefix: s.slice(pLen + 1),
|
|
368
|
+
val: kVal,
|
|
369
|
+
dict: new Map(),
|
|
370
|
+
order: []
|
|
371
|
+
});
|
|
372
|
+
curr.order.push(c);
|
|
373
|
+
curr.order.sort((a, b) => a - b);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const c = s[pLen];
|
|
377
|
+
const q = curr.dict.get(c);
|
|
378
|
+
if (!q) {
|
|
379
|
+
curr.dict.set(c, {
|
|
380
|
+
size: 1,
|
|
381
|
+
prefix: s.slice(pLen + 1),
|
|
382
|
+
val: kVal,
|
|
383
|
+
dict: new Map(),
|
|
384
|
+
order: []
|
|
385
|
+
});
|
|
386
|
+
curr.order.push(c);
|
|
387
|
+
curr.order.sort((a, b) => a - b);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
curr = q;
|
|
391
|
+
s = s.slice(pLen + 1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function cpArrayToStringK(cpArray) {
|
|
396
|
+
let out = NIL;
|
|
397
|
+
for (let i = cpArray.length - 1; i >= 0; i--) {
|
|
398
|
+
out = Value.variant("cons", Value.product({
|
|
399
|
+
car: codePointToUnicodeValue(cpArray[i]),
|
|
400
|
+
cdr: out
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function charTrieNodeToK(node) {
|
|
407
|
+
if (!node) return NIL;
|
|
408
|
+
let dictK = NIL;
|
|
409
|
+
for (let i = node.order.length - 1; i >= 0; i--) {
|
|
410
|
+
const cp = node.order[i];
|
|
411
|
+
const child = node.dict.get(cp);
|
|
412
|
+
dictK = Value.variant("cons", Value.product({
|
|
413
|
+
car: Value.product({
|
|
414
|
+
char: codePointToUnicodeValue(cp),
|
|
415
|
+
trie: charTrieNodeToK(child)
|
|
416
|
+
}),
|
|
417
|
+
cdr: dictK
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
return Value.variant("trie", Value.product({
|
|
421
|
+
prefix: cpArrayToStringK(node.prefix),
|
|
422
|
+
size: natToBitsK(node.size),
|
|
423
|
+
val: node.val !== null ? Value.variant("val", node.val) : NIL,
|
|
424
|
+
dict: dictK
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function collectCharTrieEntries(kNode, prefixStr = "", entries = []) {
|
|
429
|
+
if (!isVariant(kNode)) {
|
|
430
|
+
throw new Error("djson-ptrie.object: expected variant");
|
|
431
|
+
}
|
|
432
|
+
if (kNode.tag === "nil") return entries;
|
|
433
|
+
if (kNode.tag !== "trie") {
|
|
434
|
+
throw new Error(`djson-ptrie.object: unexpected tag ${kNode.tag}`);
|
|
435
|
+
}
|
|
436
|
+
const prod = requireProduct(kNode.value, "djson-ptrie.object.trie");
|
|
437
|
+
const nodePrefix = stringValueToText(prod.prefix);
|
|
438
|
+
const fullPrefix = prefixStr + nodePrefix;
|
|
439
|
+
if (isVariant(prod.val) && prod.val.tag === "val") {
|
|
440
|
+
entries.push({ key: fullPrefix, val: prod.val.value });
|
|
441
|
+
}
|
|
442
|
+
let curDict = prod.dict;
|
|
443
|
+
while (true) {
|
|
444
|
+
if (!isVariant(curDict)) {
|
|
445
|
+
throw new Error("djson-ptrie.object.dict: expected variant");
|
|
446
|
+
}
|
|
447
|
+
if (curDict.tag === "nil") break;
|
|
448
|
+
if (curDict.tag !== "cons") {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`djson-ptrie.object.dict: unexpected tag ${curDict.tag}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const pair = requireProduct(
|
|
454
|
+
curDict.value,
|
|
455
|
+
"djson-ptrie.object.dict.cons"
|
|
456
|
+
);
|
|
457
|
+
const entry = requireProduct(pair.car, "djson-ptrie.object.dict.car");
|
|
458
|
+
const ch = String.fromCodePoint(unicodeValueToCodePoint(entry.char));
|
|
459
|
+
collectCharTrieEntries(entry.trie, fullPrefix + ch, entries);
|
|
460
|
+
curDict = pair.cdr;
|
|
461
|
+
}
|
|
462
|
+
return entries;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function fromJsonValue(value) {
|
|
466
|
+
if (value === null) {
|
|
467
|
+
return Value.variant("null", UNIT);
|
|
468
|
+
}
|
|
469
|
+
if (typeof value === "boolean") {
|
|
470
|
+
return Value.variant(
|
|
471
|
+
"bool",
|
|
472
|
+
Value.variant(value ? "true" : "false", UNIT)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (typeof value === "number") {
|
|
476
|
+
if (!Number.isFinite(value)) {
|
|
477
|
+
throw new Error("JSON numbers must be finite");
|
|
478
|
+
}
|
|
479
|
+
return Value.variant("number", encodeNumberToValue(value));
|
|
480
|
+
}
|
|
481
|
+
if (typeof value === "string") {
|
|
482
|
+
return Value.variant("string", textToStringValue(value));
|
|
483
|
+
}
|
|
484
|
+
if (Array.isArray(value)) {
|
|
485
|
+
if (value.length === 0) {
|
|
486
|
+
return Value.variant("list", NIL);
|
|
487
|
+
}
|
|
488
|
+
const firstBits = indexToBitPath(0);
|
|
489
|
+
const root = {
|
|
490
|
+
size: 1,
|
|
491
|
+
prefix: firstBits,
|
|
492
|
+
val: fromJsonValue(value[0]),
|
|
493
|
+
b0: null,
|
|
494
|
+
b1: null
|
|
495
|
+
};
|
|
496
|
+
for (let i = 1; i < value.length; i++) {
|
|
497
|
+
addToBitTrie(root, indexToBitPath(i), fromJsonValue(value[i]));
|
|
498
|
+
}
|
|
499
|
+
return Value.variant("list", bitTrieNodeToK(root));
|
|
500
|
+
}
|
|
501
|
+
if (value && typeof value === "object") {
|
|
502
|
+
const keys = Object.keys(value);
|
|
503
|
+
if (keys.length === 0) {
|
|
504
|
+
return Value.variant("object", NIL);
|
|
505
|
+
}
|
|
506
|
+
const firstCps = Array.from(keys[0]).map((c) => c.codePointAt(0));
|
|
507
|
+
const root = {
|
|
508
|
+
size: 1,
|
|
509
|
+
prefix: firstCps,
|
|
510
|
+
val: fromJsonValue(value[keys[0]]),
|
|
511
|
+
dict: new Map(),
|
|
512
|
+
order: []
|
|
513
|
+
};
|
|
514
|
+
for (let i = 1; i < keys.length; i++) {
|
|
515
|
+
const cps = Array.from(keys[i]).map((c) => c.codePointAt(0));
|
|
516
|
+
addToCharTrie(root, cps, fromJsonValue(value[keys[i]]));
|
|
517
|
+
}
|
|
518
|
+
return Value.variant("object", charTrieNodeToK(root));
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Unsupported JSON value: ${value}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function toJsonValue(value) {
|
|
524
|
+
if (!isVariant(value)) {
|
|
525
|
+
throw new Error("djson-ptrie: expected top-level Variant");
|
|
526
|
+
}
|
|
527
|
+
if (value.tag === "null") {
|
|
528
|
+
requireProduct(value.value, "djson-ptrie.null");
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
if (value.tag === "bool") {
|
|
532
|
+
if (!isVariant(value.value)) {
|
|
533
|
+
throw new Error("djson-ptrie.bool: expected variant");
|
|
534
|
+
}
|
|
535
|
+
if (value.value.tag === "true") return true;
|
|
536
|
+
if (value.value.tag === "false") return false;
|
|
537
|
+
throw new Error(`djson-ptrie.bool: invalid tag ${value.value.tag}`);
|
|
538
|
+
}
|
|
539
|
+
if (value.tag === "number") {
|
|
540
|
+
return decodeValueToNumber(value.value);
|
|
541
|
+
}
|
|
542
|
+
if (value.tag === "string") {
|
|
543
|
+
return stringValueToText(value.value);
|
|
544
|
+
}
|
|
545
|
+
if (value.tag === "list") {
|
|
546
|
+
const entries = collectBitTrieEntries(value.value);
|
|
547
|
+
entries.sort((a, b) =>
|
|
548
|
+
bitPathToIndex(a.bits) - bitPathToIndex(b.bits)
|
|
549
|
+
);
|
|
550
|
+
return entries.map((e) => toJsonValue(e.val));
|
|
551
|
+
}
|
|
552
|
+
if (value.tag === "object") {
|
|
553
|
+
const entries = collectCharTrieEntries(value.value);
|
|
554
|
+
const result = {};
|
|
555
|
+
for (const e of entries) {
|
|
556
|
+
result[e.key] = toJsonValue(e.val);
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
throw new Error(`djson-ptrie: unsupported tag ${value.tag}`);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export const pattern = DJSON_PTRIE_PATTERN;
|
|
564
|
+
|
|
565
|
+
export function parse(rawText) {
|
|
566
|
+
const text = String(rawText).trim();
|
|
567
|
+
if (!text) throw new Error("Empty JSON input");
|
|
568
|
+
const parsed = JSON.parse(text);
|
|
569
|
+
const val = fromJsonValue(parsed);
|
|
570
|
+
return withPattern(val, DJSON_PTRIE_PATTERN);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function print(value) {
|
|
574
|
+
return JSON.stringify(toJsonValue(value));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export const codec = {
|
|
578
|
+
name: "djson-ptrie",
|
|
579
|
+
doc,
|
|
580
|
+
pattern: DJSON_PTRIE_PATTERN,
|
|
581
|
+
parse,
|
|
582
|
+
print
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
export default codec;
|
|
586
|
+
|
|
587
|
+
runCodecCLI(import.meta.url, { parse, print, doc });
|