@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
package/codecs/README.md
CHANGED
|
@@ -106,6 +106,8 @@ as `k-parse.mjs`, keep that name. Every installed codec binary supports
|
|
|
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
108
|
| `k-djson` | `djson.mjs` | Convert JSON to/from a universal dynamic JSON singleton pattern with `--parse` or `--print`. |
|
|
109
|
+
| `k-djson-btrie` | `djson-btrie.mjs` | Binary bit-trie dynamic JSON. |
|
|
110
|
+
| `k-djson-ptrie` | `djson-ptrie.mjs` | Radix trie dynamic JSON. |
|
|
109
111
|
| `k-int` | `int.mjs` | Convert decimal integers to/from the binary stream with `--parse` or `--print`. |
|
|
110
112
|
| `k-ieee` | `ieee.mjs` | Convert float literals to/from the binary stream with `--parse` or `--print`. |
|
|
111
113
|
| `k-unit` | `unit.mjs` | Write or validate the unit value `{}` with `--parse` or `--print`. |
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic JSON Binary Bit-Trie (djson-btrie) codec.
|
|
5
|
+
*
|
|
6
|
+
* Uses a unified binary bit-trie for both JSON arrays (list) and JSON
|
|
7
|
+
* objects (object):
|
|
8
|
+
*
|
|
9
|
+
* TRIE = <
|
|
10
|
+
* {} nil,
|
|
11
|
+
* {
|
|
12
|
+
* < {} nil, JSON val > val,
|
|
13
|
+
* TRIE 0,
|
|
14
|
+
* TRIE 1
|
|
15
|
+
* } trie
|
|
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.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
Value,
|
|
26
|
+
isProduct,
|
|
27
|
+
isVariant,
|
|
28
|
+
withPattern,
|
|
29
|
+
patternFromFilter,
|
|
30
|
+
runCodecCLI
|
|
31
|
+
} from "./runtime/codec-sdk.mjs";
|
|
32
|
+
import { encodeNumberToValue, decodeValueToNumber } from "./ieee.mjs";
|
|
33
|
+
import {
|
|
34
|
+
codePointToUnicodeValue,
|
|
35
|
+
unicodeValueToCodePoint,
|
|
36
|
+
textToStringValue,
|
|
37
|
+
stringValueToText
|
|
38
|
+
} from "./runtime/unicode-string.mjs";
|
|
39
|
+
|
|
40
|
+
export const doc = `
|
|
41
|
+
Dynamic JSON Binary Bit-Trie (djson-btrie) codec.
|
|
42
|
+
|
|
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
|
|
52
|
+
>
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
export const DJSON_BTRIE_PATTERN = patternFromFilter(`
|
|
56
|
+
? <
|
|
57
|
+
{
|
|
58
|
+
< {} "+", {} "-" > sign,
|
|
59
|
+
{
|
|
60
|
+
< {} 0, {} 1 >=Bit 0,
|
|
61
|
+
Bit 1, Bit 2, Bit 3, Bit 4, Bit 5,
|
|
62
|
+
Bit 6, Bit 7, Bit 8, Bit 9, Bit 10
|
|
63
|
+
} exponent,
|
|
64
|
+
{
|
|
65
|
+
Bit 0, Bit 1, Bit 2, Bit 3, Bit 4, Bit 5,
|
|
66
|
+
Bit 6, Bit 7, Bit 8, Bit 9, Bit 10, Bit 11,
|
|
67
|
+
Bit 12, Bit 13, Bit 14, Bit 15, Bit 16, Bit 17,
|
|
68
|
+
Bit 18, Bit 19, Bit 20, Bit 21, Bit 22, Bit 23,
|
|
69
|
+
Bit 24, Bit 25, Bit 26, Bit 27, Bit 28, Bit 29,
|
|
70
|
+
Bit 30, Bit 31, Bit 32, Bit 33, Bit 34, Bit 35,
|
|
71
|
+
Bit 36, Bit 37, Bit 38, Bit 39, Bit 40, Bit 41,
|
|
72
|
+
Bit 42, Bit 43, Bit 44, Bit 45, Bit 46, Bit 47,
|
|
73
|
+
Bit 48, Bit 49, Bit 50, Bit 51
|
|
74
|
+
} fraction
|
|
75
|
+
} number,
|
|
76
|
+
|
|
77
|
+
<
|
|
78
|
+
{} nil,
|
|
79
|
+
{
|
|
80
|
+
<
|
|
81
|
+
{
|
|
82
|
+
< {} 0, {} 1 >=Bit7 0,
|
|
83
|
+
Bit7 1, Bit7 2, Bit7 3, Bit7 4, Bit7 5, Bit7 6
|
|
84
|
+
}=Bits7 ascii,
|
|
85
|
+
{
|
|
86
|
+
<
|
|
87
|
+
{ Bit7 0, Bit7 1, Bit7 2 }=Bits3 h08_0F,
|
|
88
|
+
Bits7 h10_7F,
|
|
89
|
+
Bits7 h80_CF,
|
|
90
|
+
Bits3 hD0_D7,
|
|
91
|
+
{} hF9,
|
|
92
|
+
{ Bit7 0 }=Bits1 hFA_FB,
|
|
93
|
+
Bits1 hFC_FD,
|
|
94
|
+
Bits1 hFE_FF
|
|
95
|
+
> hi,
|
|
96
|
+
{
|
|
97
|
+
Bit7 0, Bit7 1, Bit7 2, Bit7 3,
|
|
98
|
+
Bit7 4, Bit7 5, Bit7 6, Bit7 7
|
|
99
|
+
}=Bits8 lo
|
|
100
|
+
} bmp_common,
|
|
101
|
+
{
|
|
102
|
+
<
|
|
103
|
+
{ Bit7 0, Bit7 1, Bit7 2, Bit7 3 } hE0_EF,
|
|
104
|
+
Bits3 hF0_F7,
|
|
105
|
+
{} hF8
|
|
106
|
+
> hi,
|
|
107
|
+
Bits8 lo
|
|
108
|
+
} bmp_private_use,
|
|
109
|
+
{
|
|
110
|
+
Bit7 0, Bit7 1, Bit7 2, Bit7 3, Bit7 4, Bit7 5,
|
|
111
|
+
Bit7 6, Bit7 7, Bit7 8, Bit7 9, Bit7 10
|
|
112
|
+
} plane0,
|
|
113
|
+
{ Bits8 mid, Bits8 lo } supplementary_plane1,
|
|
114
|
+
{
|
|
115
|
+
<
|
|
116
|
+
Bits1 p02_03,
|
|
117
|
+
{ Bit7 0, Bit7 1 } p04_07,
|
|
118
|
+
Bits3 p08_0F,
|
|
119
|
+
{} p10
|
|
120
|
+
> plane,
|
|
121
|
+
Bits8 mid,
|
|
122
|
+
Bits8 lo
|
|
123
|
+
} supplementary_planes2_16
|
|
124
|
+
> car,
|
|
125
|
+
Str cdr
|
|
126
|
+
} cons
|
|
127
|
+
> = Str string,
|
|
128
|
+
|
|
129
|
+
< {} true, {} false > bool,
|
|
130
|
+
{} null,
|
|
131
|
+
|
|
132
|
+
<
|
|
133
|
+
{} nil,
|
|
134
|
+
{
|
|
135
|
+
< {} nil, JSON val > val,
|
|
136
|
+
TRIE 0,
|
|
137
|
+
TRIE 1
|
|
138
|
+
} trie
|
|
139
|
+
> = TRIE list,
|
|
140
|
+
|
|
141
|
+
TRIE object
|
|
142
|
+
> = JSON
|
|
143
|
+
`);
|
|
144
|
+
|
|
145
|
+
const UNIT = Value.product({});
|
|
146
|
+
const NIL = Value.variant("nil", UNIT);
|
|
147
|
+
|
|
148
|
+
function requireProduct(value, where) {
|
|
149
|
+
if (!isProduct(value)) {
|
|
150
|
+
throw new Error(`${where}: expected Product`);
|
|
151
|
+
}
|
|
152
|
+
return value.product;
|
|
153
|
+
}
|
|
154
|
+
|
|
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;
|
|
275
|
+
}
|
|
276
|
+
|
|
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;
|
|
294
|
+
}
|
|
295
|
+
if (bits[i + 1] === 0) {
|
|
296
|
+
const cp = readBitsMSB(bits, i + 2, 11);
|
|
297
|
+
out += String.fromCodePoint(cp);
|
|
298
|
+
i += 13;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
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;
|
|
397
|
+
}
|
|
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-btrie: 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-btrie: unexpected tag ${kNode.tag}`);
|
|
428
|
+
}
|
|
429
|
+
const prod = requireProduct(kNode.value, "djson-btrie.trie");
|
|
430
|
+
if (!isVariant(prod.val)) {
|
|
431
|
+
throw new Error("djson-btrie.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-btrie.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;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function fromJsonValue(value) {
|
|
450
|
+
if (value === null) {
|
|
451
|
+
return Value.variant("null", UNIT);
|
|
452
|
+
}
|
|
453
|
+
if (typeof value === "boolean") {
|
|
454
|
+
return Value.variant(
|
|
455
|
+
"bool",
|
|
456
|
+
Value.variant(value ? "true" : "false", UNIT)
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (typeof value === "number") {
|
|
460
|
+
if (!Number.isFinite(value)) {
|
|
461
|
+
throw new Error("JSON numbers must be finite");
|
|
462
|
+
}
|
|
463
|
+
return Value.variant("number", encodeNumberToValue(value));
|
|
464
|
+
}
|
|
465
|
+
if (typeof value === "string") {
|
|
466
|
+
return Value.variant("string", textToStringValue(value));
|
|
467
|
+
}
|
|
468
|
+
if (Array.isArray(value)) {
|
|
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));
|
|
477
|
+
}
|
|
478
|
+
if (value && typeof value === "object") {
|
|
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));
|
|
492
|
+
}
|
|
493
|
+
throw new Error(`Unsupported JSON value: ${value}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function toJsonValue(value) {
|
|
497
|
+
if (!isVariant(value)) {
|
|
498
|
+
throw new Error("djson-btrie: expected top-level Variant");
|
|
499
|
+
}
|
|
500
|
+
if (value.tag === "null") {
|
|
501
|
+
requireProduct(value.value, "djson-btrie.null");
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
if (value.tag === "bool") {
|
|
505
|
+
if (!isVariant(value.value)) {
|
|
506
|
+
throw new Error("djson-btrie.bool: expected variant");
|
|
507
|
+
}
|
|
508
|
+
if (value.value.tag === "true") return true;
|
|
509
|
+
if (value.value.tag === "false") return false;
|
|
510
|
+
throw new Error(`djson-btrie.bool: invalid tag ${value.value.tag}`);
|
|
511
|
+
}
|
|
512
|
+
if (value.tag === "number") {
|
|
513
|
+
return decodeValueToNumber(value.value);
|
|
514
|
+
}
|
|
515
|
+
if (value.tag === "string") {
|
|
516
|
+
return stringValueToText(value.value);
|
|
517
|
+
}
|
|
518
|
+
if (value.tag === "list") {
|
|
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));
|
|
524
|
+
}
|
|
525
|
+
if (value.tag === "object") {
|
|
526
|
+
const entries = collectTrieEntries(value.value);
|
|
527
|
+
const result = {};
|
|
528
|
+
for (const e of entries) {
|
|
529
|
+
result[bitPathToString(e.bits)] = toJsonValue(e.val);
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
throw new Error(`djson-btrie: unsupported tag ${value.tag}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export const pattern = DJSON_BTRIE_PATTERN;
|
|
537
|
+
|
|
538
|
+
export function parse(rawText) {
|
|
539
|
+
const text = String(rawText).trim();
|
|
540
|
+
if (!text) throw new Error("Empty JSON input");
|
|
541
|
+
const parsed = JSON.parse(text);
|
|
542
|
+
const val = fromJsonValue(parsed);
|
|
543
|
+
return withPattern(val, DJSON_BTRIE_PATTERN);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function print(value) {
|
|
547
|
+
return JSON.stringify(toJsonValue(value));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export const codec = {
|
|
551
|
+
name: "djson-btrie",
|
|
552
|
+
doc,
|
|
553
|
+
pattern: DJSON_BTRIE_PATTERN,
|
|
554
|
+
parse,
|
|
555
|
+
print
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export default codec;
|
|
559
|
+
|
|
560
|
+
runCodecCLI(import.meta.url, { parse, print, doc });
|