@fraczak/k 7.1.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 +99 -0
- package/DOCS/BIG_PICTURE.md +54 -7
- package/DOCS/CODECS.md +47 -2
- package/DOCS/CODEC_ARCHITECTURE.md +14 -5
- 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 +24 -5
- 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 -15
- package/Value.mjs +28 -3
- package/backend-api.mjs +5 -1
- package/browser/repl-browser.mjs +6 -0
- package/browser/tree-view.mjs +82 -24
- package/browser/vfs-data.mjs +21 -12
- package/codecs/README.md +3 -0
- package/codecs/djson-btrie.mjs +560 -0
- package/codecs/djson-ptrie.mjs +587 -0
- package/codecs/djson.mjs +270 -0
- package/codecs/int.mjs +2 -1
- package/codecs/json.mjs +7 -2
- package/codecs/k-parse.mjs +7 -1
- package/codecs/k-print.mjs +4 -0
- package/codecs/runtime/codec-sdk.mjs +16 -2
- package/codecs/runtime/pattern-json.mjs +80 -1
- package/codecs/runtime/prefix-codec.mjs +629 -67
- package/codecs/runtime/show-value.mjs +93 -51
- package/codecs/runtime/unicode-string.mjs +29 -6
- package/codecs/show.mjs +4 -0
- package/codecs/utf16.mjs +2 -1
- package/core.k +79 -115
- package/index.mjs +3 -0
- package/named-patterns.mjs +267 -0
- package/object.mjs +31 -3
- package/package.json +5 -1
- package/parser.jison +68 -5
- package/parser.mjs +111 -60
- package/pretty.mjs +5 -0
- package/repl.mjs +245 -28
- package/scripts/build-repl-html.mjs +76 -5
- 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 +211 -0
- package/tests/test-named-patterns.mjs +189 -0
- package/tests/test-repl-html.mjs +52 -4
- package/tests/test-repl.mjs +223 -0
package/scripts/run-tests.mjs
CHANGED
|
@@ -32,6 +32,11 @@ const derivationTests = fs.readdirSync(path.join(root, "tests/code-derivation"))
|
|
|
32
32
|
|
|
33
33
|
const tests = [
|
|
34
34
|
["tests/test.mjs", node, ["tests/test.mjs"]],
|
|
35
|
+
[
|
|
36
|
+
"tests/test-named-patterns.mjs",
|
|
37
|
+
node,
|
|
38
|
+
["tests/test-named-patterns.mjs"]
|
|
39
|
+
],
|
|
35
40
|
...derivationTests,
|
|
36
41
|
["tests/test-fingerprint.mjs", node, ["tests/test-fingerprint.mjs"]],
|
|
37
42
|
["tests/test-hash-fuzz.mjs", node, ["tests/test-hash-fuzz.mjs"]],
|
package/symbol-table.mjs
CHANGED
|
@@ -93,6 +93,7 @@ function union(rels) {
|
|
|
93
93
|
class SymbolTable {
|
|
94
94
|
constructor() {
|
|
95
95
|
this.rels = {}; // name -> {def: rel_exp}
|
|
96
|
+
this.patterns = {}; // name -> {params: string[], filter: filter_ast}
|
|
96
97
|
this.codes = {
|
|
97
98
|
[unitCode]: {
|
|
98
99
|
code: "product",
|
|
@@ -109,12 +110,29 @@ class SymbolTable {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
add_rel(name, rel) {
|
|
112
|
-
if (this.rels[name] != undefined)
|
|
113
|
-
console.error(
|
|
113
|
+
if (this.rels[name] != undefined) {
|
|
114
|
+
console.error(
|
|
115
|
+
`SymbolTable: rel '${name}' is redefined ` +
|
|
116
|
+
`(lines ${rel.start?.line}:${rel.start?.column}...` +
|
|
117
|
+
`${rel.end?.line}:${rel.end?.column})`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
114
120
|
|
|
115
121
|
this.rels[name] = {def: rel};
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
add_pattern(name, params, filter) {
|
|
125
|
+
if (this.patterns[name] != undefined) {
|
|
126
|
+
console.error(
|
|
127
|
+
`SymbolTable: pattern '${name}' is redefined ` +
|
|
128
|
+
`(lines ${filter.start?.line}:${filter.start?.column}...` +
|
|
129
|
+
`${filter.end?.line}:${filter.end?.column})`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.patterns[name] = { params, filter };
|
|
134
|
+
}
|
|
135
|
+
|
|
118
136
|
add_code(name, code) {
|
|
119
137
|
this.codes[name] = code;
|
|
120
138
|
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { performance } from "node:perf_hooks";
|
|
6
|
+
import { createState, evaluateInput } from "../repl.mjs";
|
|
7
|
+
import * as djsonCodec from "../codecs/djson.mjs";
|
|
8
|
+
import btrieCodec, { indexToBitPath } from "../codecs/djson-btrie.mjs";
|
|
9
|
+
import ptrieCodec from "../codecs/djson-ptrie.mjs";
|
|
10
|
+
import {
|
|
11
|
+
compileWasmArtifactFromObject,
|
|
12
|
+
instantiateWasmArtifact,
|
|
13
|
+
writeValueToArena
|
|
14
|
+
} from "../backends/wasm/src/wasm.mjs";
|
|
15
|
+
import {
|
|
16
|
+
compileCaseExecutable,
|
|
17
|
+
prepareRelation,
|
|
18
|
+
runExecutableMainBench,
|
|
19
|
+
wireInput
|
|
20
|
+
} from "../backends/llvm/tests/perf-support.mjs";
|
|
21
|
+
|
|
22
|
+
const llvmCacheDir = fs.mkdtempSync(
|
|
23
|
+
path.join(os.tmpdir(), "k-djson-bench-")
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
console.log("=== 1. Codec Round-Trip Verification ===");
|
|
27
|
+
const sampleJson = {
|
|
28
|
+
name: "Alice",
|
|
29
|
+
age: 30,
|
|
30
|
+
active: true,
|
|
31
|
+
nothing: null,
|
|
32
|
+
scores: [10, 20, 30, 40, 50],
|
|
33
|
+
nested: { a: 1, ab: 2, abc: 3, abd: 4, z: 99 }
|
|
34
|
+
};
|
|
35
|
+
const sampleStr = JSON.stringify(sampleJson);
|
|
36
|
+
|
|
37
|
+
for (const [label, c, pat] of [
|
|
38
|
+
["djson (cons-list)", djsonCodec, djsonCodec.DJSON_PATTERN],
|
|
39
|
+
["djson-btrie (bit-trie)", btrieCodec, btrieCodec.pattern],
|
|
40
|
+
["djson-ptrie (radix-trie)", ptrieCodec, ptrieCodec.pattern]
|
|
41
|
+
]) {
|
|
42
|
+
const kVal = c.parse(sampleStr);
|
|
43
|
+
const roundTrip = JSON.parse(c.print(kVal));
|
|
44
|
+
assert.deepStrictEqual(
|
|
45
|
+
roundTrip,
|
|
46
|
+
sampleJson,
|
|
47
|
+
`${label} round-trip failed`
|
|
48
|
+
);
|
|
49
|
+
console.log(
|
|
50
|
+
` [OK] ${label.padEnd(26)} pattern nodes: ${pat.length}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function indexToKLiteral(idx) {
|
|
55
|
+
const bits = indexToBitPath(idx);
|
|
56
|
+
const tags = bits.slice().reverse().map((b) => `|${b}`).join("");
|
|
57
|
+
return `({}|_${tags})`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function setupBenchmarkState(kFile, benchSnippet) {
|
|
61
|
+
const st = createState({ engine: "wasm" });
|
|
62
|
+
await evaluateInput(`:load ${kFile}`, st);
|
|
63
|
+
await evaluateInput(benchSnippet, st);
|
|
64
|
+
return st;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function benchRelationOnInput({
|
|
68
|
+
state,
|
|
69
|
+
relName,
|
|
70
|
+
kInput,
|
|
71
|
+
codec,
|
|
72
|
+
expectedJson,
|
|
73
|
+
calls = 1000
|
|
74
|
+
}) {
|
|
75
|
+
const wire = wireInput(kInput, codec.pattern);
|
|
76
|
+
const wireBytes = wire.inputWire.length;
|
|
77
|
+
const prepared = prepareRelation(state, relName);
|
|
78
|
+
|
|
79
|
+
// 1. Wasm Compilation & Execution
|
|
80
|
+
const tWasmComp0 = performance.now();
|
|
81
|
+
const wasmBytes = await compileWasmArtifactFromObject(prepared.object, {
|
|
82
|
+
relation: prepared.relationName,
|
|
83
|
+
inputEnvelopePattern: wire.inputPattern,
|
|
84
|
+
usePKIR: false
|
|
85
|
+
});
|
|
86
|
+
const runner = await instantiateWasmArtifact(wasmBytes);
|
|
87
|
+
const wasmCompileMs = performance.now() - tWasmComp0;
|
|
88
|
+
|
|
89
|
+
// Verify output once
|
|
90
|
+
const outVal = runner.executeValue(kInput);
|
|
91
|
+
assert(outVal !== undefined, `${relName} Wasm returned undefined`);
|
|
92
|
+
assert.deepStrictEqual(
|
|
93
|
+
JSON.parse(codec.print(outVal)),
|
|
94
|
+
expectedJson,
|
|
95
|
+
`Wasm output mismatch for ${relName}`
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Write input once to arena, then loop N calls resetting arena mark
|
|
99
|
+
const ptrIn = writeValueToArena(
|
|
100
|
+
runner.exports,
|
|
101
|
+
kInput,
|
|
102
|
+
runner.inputPattern,
|
|
103
|
+
0,
|
|
104
|
+
new Map(),
|
|
105
|
+
runner.tags,
|
|
106
|
+
runner.metadata.inputPattern
|
|
107
|
+
);
|
|
108
|
+
const mark = runner.exports.arena_mark();
|
|
109
|
+
const entryFn = runner.exports[runner.metadata.entry];
|
|
110
|
+
|
|
111
|
+
// Warmup
|
|
112
|
+
for (let i = 0; i < 20; i++) {
|
|
113
|
+
runner.exports.arena_reset(mark);
|
|
114
|
+
entryFn(ptrIn);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const tWasmExec0 = performance.now();
|
|
118
|
+
for (let i = 0; i < calls; i++) {
|
|
119
|
+
runner.exports.arena_reset(mark);
|
|
120
|
+
entryFn(ptrIn);
|
|
121
|
+
}
|
|
122
|
+
const wasmExecNsPerCall =
|
|
123
|
+
((performance.now() - tWasmExec0) * 1e6) / calls;
|
|
124
|
+
|
|
125
|
+
// 2. LLVM Compilation & Execution (-O3)
|
|
126
|
+
const tLlvmComp0 = performance.now();
|
|
127
|
+
const exePath = compileCaseExecutable({
|
|
128
|
+
object: prepared.object,
|
|
129
|
+
relationName: prepared.relationName,
|
|
130
|
+
relHash: prepared.relHash,
|
|
131
|
+
inputPattern: wire.inputPattern,
|
|
132
|
+
cacheDir: llvmCacheDir,
|
|
133
|
+
sourceLabel: relName,
|
|
134
|
+
runtimeMode: "fast",
|
|
135
|
+
clangOpt: "-O3",
|
|
136
|
+
usePKIR: false
|
|
137
|
+
});
|
|
138
|
+
const llvmCompileMs = performance.now() - tLlvmComp0;
|
|
139
|
+
const llvmRes = await runExecutableMainBench(
|
|
140
|
+
exePath,
|
|
141
|
+
wire.inputWire,
|
|
142
|
+
calls
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
wireBytes,
|
|
147
|
+
wasmBytes: wasmBytes.length,
|
|
148
|
+
wasmCompileMs,
|
|
149
|
+
wasmExecNsPerCall,
|
|
150
|
+
llvmCompileMs,
|
|
151
|
+
llvmExecNsPerCall: llvmRes.perCallNs
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const SCHEMAS = [
|
|
156
|
+
{
|
|
157
|
+
name: "djson (cons)",
|
|
158
|
+
kFile: "Examples/djson.k",
|
|
159
|
+
codec: { ...djsonCodec, pattern: djsonCodec.DJSON_PATTERN },
|
|
160
|
+
dotExpr:
|
|
161
|
+
"bench_dot = json? { " +
|
|
162
|
+
"/list /cons .car /string key, " +
|
|
163
|
+
"/list /cons .cdr /cons .car /object object } dot json?;"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "djson-btrie",
|
|
167
|
+
kFile: "Examples/djson-btrie.k",
|
|
168
|
+
codec: btrieCodec,
|
|
169
|
+
dotExpr:
|
|
170
|
+
"bench_dot = json? { " +
|
|
171
|
+
"{ 0 index, /list list } nth /string key, " +
|
|
172
|
+
"{ 1 index, /list list } nth /object object } dot json?;"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "djson-ptrie",
|
|
176
|
+
kFile: "Examples/djson-ptrie.k",
|
|
177
|
+
codec: ptrieCodec,
|
|
178
|
+
dotExpr:
|
|
179
|
+
"bench_dot = json? { " +
|
|
180
|
+
"{ 0 index, /list list } nth /string key, " +
|
|
181
|
+
"{ 1 index, /list list } nth /object object } dot json?;"
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
console.log(
|
|
186
|
+
"\n=== 2A. Object Lookup (`dot`) — " +
|
|
187
|
+
"Shared-Prefix Keys (Wasm vs LLVM -O3) ==="
|
|
188
|
+
);
|
|
189
|
+
for (const numKeys of [10, 100, 500]) {
|
|
190
|
+
const obj = {};
|
|
191
|
+
for (let i = 0; i < numKeys; i++) {
|
|
192
|
+
const k = `field_prefix_${String(i).padStart(4, "0")}`;
|
|
193
|
+
obj[k] = i * 10;
|
|
194
|
+
}
|
|
195
|
+
const targetKey =
|
|
196
|
+
`field_prefix_${String(numKeys - 1).padStart(4, "0")}`;
|
|
197
|
+
const expectedVal = (numKeys - 1) * 10;
|
|
198
|
+
const inputJsonStr = JSON.stringify([targetKey, obj]);
|
|
199
|
+
|
|
200
|
+
console.log(
|
|
201
|
+
`\n--- Object with ${numKeys} shared-prefix keys ` +
|
|
202
|
+
`(querying '${targetKey}') ---`
|
|
203
|
+
);
|
|
204
|
+
console.log(
|
|
205
|
+
"Schema | Wire (B) | Wasm (B) | " +
|
|
206
|
+
"Wasm Comp | LLVM Comp | Wasm (ns/call) | LLVM (ns/call)"
|
|
207
|
+
);
|
|
208
|
+
console.log(
|
|
209
|
+
"----------------+----------+----------+-" +
|
|
210
|
+
"----------+-----------+----------------+---------------"
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
for (const s of SCHEMAS) {
|
|
214
|
+
const st = await setupBenchmarkState(s.kFile, s.dotExpr);
|
|
215
|
+
const kInput = s.codec.parse(inputJsonStr);
|
|
216
|
+
const res = await benchRelationOnInput({
|
|
217
|
+
state: st,
|
|
218
|
+
relName: "bench_dot",
|
|
219
|
+
kInput,
|
|
220
|
+
codec: s.codec,
|
|
221
|
+
expectedJson: expectedVal,
|
|
222
|
+
calls: 1000
|
|
223
|
+
});
|
|
224
|
+
console.log(
|
|
225
|
+
`${s.name.padEnd(15)} | ${String(res.wireBytes).padStart(8)} | ` +
|
|
226
|
+
`${String(res.wasmBytes).padStart(8)} | ` +
|
|
227
|
+
`${res.wasmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
228
|
+
`${res.llvmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
229
|
+
`${res.wasmExecNsPerCall.toFixed(0).padStart(14)} | ` +
|
|
230
|
+
`${res.llvmExecNsPerCall.toFixed(0).padStart(14)}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(
|
|
236
|
+
"\n=== 2B. Object Lookup (`dot`) — " +
|
|
237
|
+
"Distinct/Varied Keys (Wasm vs LLVM -O3) ==="
|
|
238
|
+
);
|
|
239
|
+
const WORDS = [
|
|
240
|
+
"account", "balance", "category", "database", "endpoint",
|
|
241
|
+
"filename", "geometry", "hostname", "identity", "journal",
|
|
242
|
+
"keyboard", "language", "metadata", "network", "operator",
|
|
243
|
+
"password", "quantity", "resource", "security", "template",
|
|
244
|
+
"username", "validate", "workflow", "x509cert", "yearbook", "zoneinfo"
|
|
245
|
+
];
|
|
246
|
+
for (const numKeys of [10, 100, 500]) {
|
|
247
|
+
const obj = {};
|
|
248
|
+
for (let i = 0; i < numKeys; i++) {
|
|
249
|
+
const w1 = WORDS[i % WORDS.length];
|
|
250
|
+
const w2 = WORDS[Math.floor(i / WORDS.length) % WORDS.length];
|
|
251
|
+
const k = `${w1}_${i}_${w2}_attr`;
|
|
252
|
+
obj[k] = i * 10;
|
|
253
|
+
}
|
|
254
|
+
const targetKey = Object.keys(obj).sort().at(-1);
|
|
255
|
+
const expectedVal = obj[targetKey];
|
|
256
|
+
const inputJsonStr = JSON.stringify([targetKey, obj]);
|
|
257
|
+
|
|
258
|
+
console.log(
|
|
259
|
+
`\n--- Object with ${numKeys} varied keys ` +
|
|
260
|
+
`(querying '${targetKey}') ---`
|
|
261
|
+
);
|
|
262
|
+
console.log(
|
|
263
|
+
"Schema | Wire (B) | Wasm (B) | " +
|
|
264
|
+
"Wasm Comp | LLVM Comp | Wasm (ns/call) | LLVM (ns/call)"
|
|
265
|
+
);
|
|
266
|
+
console.log(
|
|
267
|
+
"----------------+----------+----------+-" +
|
|
268
|
+
"----------+-----------+----------------+---------------"
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
for (const s of SCHEMAS) {
|
|
272
|
+
const st = await setupBenchmarkState(s.kFile, s.dotExpr);
|
|
273
|
+
const kInput = s.codec.parse(inputJsonStr);
|
|
274
|
+
const res = await benchRelationOnInput({
|
|
275
|
+
state: st,
|
|
276
|
+
relName: "bench_dot",
|
|
277
|
+
kInput,
|
|
278
|
+
codec: s.codec,
|
|
279
|
+
expectedJson: expectedVal,
|
|
280
|
+
calls: 1000
|
|
281
|
+
});
|
|
282
|
+
console.log(
|
|
283
|
+
`${s.name.padEnd(15)} | ${String(res.wireBytes).padStart(8)} | ` +
|
|
284
|
+
`${String(res.wasmBytes).padStart(8)} | ` +
|
|
285
|
+
`${res.wasmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
286
|
+
`${res.llvmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
287
|
+
`${res.wasmExecNsPerCall.toFixed(0).padStart(14)} | ` +
|
|
288
|
+
`${res.llvmExecNsPerCall.toFixed(0).padStart(14)}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(
|
|
294
|
+
"\n=== 3. List Element Lookup (`nth`) Benchmark (Wasm vs LLVM -O3) ==="
|
|
295
|
+
);
|
|
296
|
+
for (const len of [10, 100, 500]) {
|
|
297
|
+
const arr = Array.from({ length: len }, (_, i) => i * 3);
|
|
298
|
+
const targetIdx = len - 1;
|
|
299
|
+
const expectedVal = targetIdx * 3;
|
|
300
|
+
const inputJsonStr = JSON.stringify(arr);
|
|
301
|
+
const idxLit = indexToKLiteral(targetIdx);
|
|
302
|
+
const nthExpr =
|
|
303
|
+
`bench_nth = json? { ${idxLit} index, /list list } nth json?;`;
|
|
304
|
+
|
|
305
|
+
console.log(
|
|
306
|
+
`\n--- Array with ${len} elements ` +
|
|
307
|
+
`(querying index ${targetIdx}) ---`
|
|
308
|
+
);
|
|
309
|
+
console.log(
|
|
310
|
+
"Schema | Wire (B) | Wasm (B) | " +
|
|
311
|
+
"Wasm Comp | LLVM Comp | Wasm (ns/call) | LLVM (ns/call)"
|
|
312
|
+
);
|
|
313
|
+
console.log(
|
|
314
|
+
"----------------+----------+----------+-" +
|
|
315
|
+
"----------+-----------+----------------+---------------"
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
for (const s of SCHEMAS) {
|
|
319
|
+
const st = await setupBenchmarkState(s.kFile, nthExpr);
|
|
320
|
+
const kInput = s.codec.parse(inputJsonStr);
|
|
321
|
+
const res = await benchRelationOnInput({
|
|
322
|
+
state: st,
|
|
323
|
+
relName: "bench_nth",
|
|
324
|
+
kInput,
|
|
325
|
+
codec: s.codec,
|
|
326
|
+
expectedJson: expectedVal,
|
|
327
|
+
calls: 1000
|
|
328
|
+
});
|
|
329
|
+
console.log(
|
|
330
|
+
`${s.name.padEnd(15)} | ${String(res.wireBytes).padStart(8)} | ` +
|
|
331
|
+
`${String(res.wasmBytes).padStart(8)} | ` +
|
|
332
|
+
`${res.wasmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
333
|
+
`${res.llvmCompileMs.toFixed(0).padStart(7)}ms | ` +
|
|
334
|
+
`${res.wasmExecNsPerCall.toFixed(0).padStart(14)} | ` +
|
|
335
|
+
`${res.llvmExecNsPerCall.toFixed(0).padStart(14)}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fs.rmSync(llvmCacheDir, { recursive: true, force: true });
|
package/tests/test-codecs.mjs
CHANGED
|
@@ -8,10 +8,17 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import * as SDK from "../codecs/runtime/codec-sdk.mjs";
|
|
9
9
|
import * as IntCodec from "../codecs/int.mjs";
|
|
10
10
|
import * as JsonCodec from "../codecs/json.mjs";
|
|
11
|
+
import * as DjsonCodec from "../codecs/djson.mjs";
|
|
12
|
+
import * as DjsonBtrieCodec from "../codecs/djson-btrie.mjs";
|
|
13
|
+
import * as DjsonPtrieCodec from "../codecs/djson-ptrie.mjs";
|
|
11
14
|
import * as Utf8Codec from "../codecs/utf8.mjs";
|
|
12
15
|
import * as Utf16Codec from "../codecs/utf16.mjs";
|
|
13
16
|
import * as IeeeCodec from "../codecs/ieee.mjs";
|
|
14
17
|
import * as UnitCodec from "../codecs/unit.mjs";
|
|
18
|
+
import {
|
|
19
|
+
valueToK,
|
|
20
|
+
valueWithEnvelopeToK
|
|
21
|
+
} from "../codecs/runtime/show-value.mjs";
|
|
15
22
|
|
|
16
23
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
24
|
|
|
@@ -49,6 +56,17 @@ console.log("2. Verifying in-memory parse -> enveloped value (P, v)...");
|
|
|
49
56
|
for (const [name, codec, sampleInput] of [
|
|
50
57
|
["int.mjs", IntCodec, "42"],
|
|
51
58
|
["json.mjs", JsonCodec, '{"x": 1}'],
|
|
59
|
+
["djson.mjs", DjsonCodec, '{"x": 1, "arr": [true, null, "hi"]}'],
|
|
60
|
+
[
|
|
61
|
+
"djson-btrie.mjs",
|
|
62
|
+
DjsonBtrieCodec,
|
|
63
|
+
'{"x": 1, "arr": [true, null, "hi"]}'
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"djson-ptrie.mjs",
|
|
67
|
+
DjsonPtrieCodec,
|
|
68
|
+
'{"x": 1, "arr": [true, null, "hi"]}'
|
|
69
|
+
],
|
|
52
70
|
["utf8.mjs", Utf8Codec, "hello"],
|
|
53
71
|
["utf16.mjs", Utf16Codec, "hello"],
|
|
54
72
|
["ieee.mjs", IeeeCodec, "3.14"],
|
|
@@ -68,6 +86,9 @@ console.log("3. Verifying CLI --help flag...");
|
|
|
68
86
|
for (const relPath of [
|
|
69
87
|
"codecs/int.mjs",
|
|
70
88
|
"codecs/json.mjs",
|
|
89
|
+
"codecs/djson.mjs",
|
|
90
|
+
"codecs/djson-btrie.mjs",
|
|
91
|
+
"codecs/djson-ptrie.mjs",
|
|
71
92
|
"codecs/utf8.mjs",
|
|
72
93
|
"codecs/utf16.mjs",
|
|
73
94
|
"codecs/ieee.mjs",
|
|
@@ -111,6 +132,24 @@ console.log("4. Verifying CLI --parse and --print pipelines...");
|
|
|
111
132
|
assert.deepEqual(JSON.parse(printRes.stdout.toString("utf8")), JSON.parse(jsonInput));
|
|
112
133
|
}
|
|
113
134
|
|
|
135
|
+
// djson.mjs: dynamic JSON AST with singleton pattern
|
|
136
|
+
{
|
|
137
|
+
const djsonInput = JSON.stringify({
|
|
138
|
+
name: "k",
|
|
139
|
+
count: 42.5,
|
|
140
|
+
tags: ["fast", "typed"],
|
|
141
|
+
meta: { active: true, extra: null }
|
|
142
|
+
});
|
|
143
|
+
const parseRes = runCLI("codecs/djson.mjs", ["--parse"], djsonInput);
|
|
144
|
+
assert.equal(parseRes.status, 0);
|
|
145
|
+
const printRes = runCLI("codecs/djson.mjs", ["--print"], parseRes.stdout);
|
|
146
|
+
assert.equal(printRes.status, 0);
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
JSON.parse(printRes.stdout.toString("utf8")),
|
|
149
|
+
JSON.parse(djsonInput)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
// utf8.mjs: unicode text
|
|
115
154
|
{
|
|
116
155
|
const utf8Input = "Hello 🌍, ąęćłó! \nSecond line.";
|
|
@@ -183,4 +222,176 @@ console.log("6. Verifying pattern derivation via k type system (patternFromFilte
|
|
|
183
222
|
assert.throws(() => SDK.patternFromFilter(".field"), /Main expression must be a filter or a type name/);
|
|
184
223
|
}
|
|
185
224
|
|
|
225
|
+
// 7. Lazy Prefix-Free Wire Decoding
|
|
226
|
+
console.log("7. Verifying lazy prefix-free wire decoding...");
|
|
227
|
+
{
|
|
228
|
+
assert.equal(typeof SDK.decodeWireLazy, "function");
|
|
229
|
+
|
|
230
|
+
// Lazy decoding should instantiate root in sub-millisecond time
|
|
231
|
+
const sampleStr = "a".repeat(20000);
|
|
232
|
+
const parsedStr = Utf8Codec.parse(sampleStr);
|
|
233
|
+
const wireStr = SDK.encodeToWire(parsedStr, parsedStr.pattern);
|
|
234
|
+
|
|
235
|
+
const t0 = performance.now();
|
|
236
|
+
const decodedLazy = SDK.decodeWire(wireStr);
|
|
237
|
+
const constructMs = performance.now() - t0;
|
|
238
|
+
assert(
|
|
239
|
+
constructMs < 50,
|
|
240
|
+
`Lazy decode took ${constructMs}ms (expected <50ms)`
|
|
241
|
+
);
|
|
242
|
+
assert.equal(decodedLazy.value.tag, "cons");
|
|
243
|
+
|
|
244
|
+
// On-demand traversal through print()
|
|
245
|
+
const printedStr = Utf8Codec.print(decodedLazy.value);
|
|
246
|
+
assert.equal(printedStr, sampleStr);
|
|
247
|
+
|
|
248
|
+
// Lazy list of integers
|
|
249
|
+
const ints = Array.from({ length: 5000 }, (_, i) => i).join(",");
|
|
250
|
+
const sampleList = `[${ints}]`;
|
|
251
|
+
const parsedList = IntCodec.parse(sampleList);
|
|
252
|
+
const wireList = SDK.encodeToWire(parsedList, parsedList.pattern);
|
|
253
|
+
const decodedList = SDK.decodeWireLazy(wireList);
|
|
254
|
+
assert.equal(IntCodec.print(decodedList.value), sampleList);
|
|
255
|
+
|
|
256
|
+
// Verify stack-safe toJSON on deep variant chains
|
|
257
|
+
let deepVal = SDK.Value.product({});
|
|
258
|
+
for (let i = 0; i < 15000; i++) {
|
|
259
|
+
deepVal = SDK.Value.variant("step", deepVal);
|
|
260
|
+
}
|
|
261
|
+
const json = deepVal.toJSON();
|
|
262
|
+
assert(json && typeof json === "object");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 8. High-Speed Direct Bitstream String Decoding & Bounded Rendering
|
|
266
|
+
console.log("8. Verifying fast string decoding & bounded rendering...");
|
|
267
|
+
{
|
|
268
|
+
const testStrings = [
|
|
269
|
+
"hello world",
|
|
270
|
+
"Zażółć gęślą jaźń",
|
|
271
|
+
"Unicode: 🚀🎉 𝒦-lang π ≈ 3.14159",
|
|
272
|
+
"a".repeat(50000),
|
|
273
|
+
""
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const s of testStrings) {
|
|
277
|
+
const parsed = Utf8Codec.parse(s);
|
|
278
|
+
const wire = SDK.encodeToWire(parsed, parsed.pattern);
|
|
279
|
+
const decoded = SDK.decodeWire(wire);
|
|
280
|
+
const restored = Utf8Codec.print(decoded.value);
|
|
281
|
+
assert.equal(
|
|
282
|
+
restored,
|
|
283
|
+
s,
|
|
284
|
+
`String round-trip failed for "${s.slice(0, 20)}..."`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Fast decoding performance: 50,000 ASCII chars in < 500ms
|
|
289
|
+
const bigAscii = "x".repeat(50000);
|
|
290
|
+
const bigParsed = Utf8Codec.parse(bigAscii);
|
|
291
|
+
const bigWire = SDK.encodeToWire(bigParsed, bigParsed.pattern);
|
|
292
|
+
const tStart = performance.now();
|
|
293
|
+
const bigDecoded = SDK.decodeWire(bigWire);
|
|
294
|
+
const bigRestored = Utf8Codec.print(bigDecoded.value);
|
|
295
|
+
const elapsed = performance.now() - tStart;
|
|
296
|
+
assert.equal(bigRestored.length, 50000);
|
|
297
|
+
assert(
|
|
298
|
+
elapsed < 500,
|
|
299
|
+
`Fast string decoding took ${elapsed}ms (expected < 500ms)`
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// Bounded rendering: valueToK with limit
|
|
303
|
+
let deepVal = SDK.Value.product({});
|
|
304
|
+
for (let i = 0; i < 1000; i++) {
|
|
305
|
+
deepVal = SDK.Value.variant("item", deepVal);
|
|
306
|
+
}
|
|
307
|
+
const preview = valueToK(deepVal, 30);
|
|
308
|
+
assert(preview.endsWith("..."), "Output should end with '...'");
|
|
309
|
+
assert(preview.length <= 35, "Preview length should stay bounded");
|
|
310
|
+
|
|
311
|
+
// valueWithEnvelopeToK with limit
|
|
312
|
+
const enveloped = SDK.withPattern(deepVal, [["any", []]]);
|
|
313
|
+
const envPreview = valueWithEnvelopeToK(enveloped, 30);
|
|
314
|
+
assert(envPreview.includes("..."), "Preview should contain '...'");
|
|
315
|
+
|
|
316
|
+
// Ensure early termination: evaluating deep chain with limit 20
|
|
317
|
+
let hugeVal = SDK.Value.product({});
|
|
318
|
+
for (let i = 0; i < 50000; i++) {
|
|
319
|
+
hugeVal = SDK.Value.variant("v", hugeVal);
|
|
320
|
+
}
|
|
321
|
+
const tEarly0 = performance.now();
|
|
322
|
+
const earlyPreview = valueToK(hugeVal, 20);
|
|
323
|
+
const tEarlyElapsed = performance.now() - tEarly0;
|
|
324
|
+
assert(earlyPreview.endsWith("..."));
|
|
325
|
+
assert(
|
|
326
|
+
tEarlyElapsed < 50,
|
|
327
|
+
`Early termination took ${tEarlyElapsed}ms (expected < 50ms)`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 9. DJSON Family Singleton Pattern Invariants & Round-Trip
|
|
332
|
+
console.log("9. Verifying DJSON family singleton pattern invariants...");
|
|
333
|
+
{
|
|
334
|
+
assert(Array.isArray(DjsonCodec.DJSON_PATTERN));
|
|
335
|
+
assert.equal(DjsonCodec.DJSON_PATTERN.length, 29);
|
|
336
|
+
assert(Array.isArray(DjsonBtrieCodec.DJSON_BTRIE_PATTERN));
|
|
337
|
+
assert.equal(DjsonBtrieCodec.DJSON_BTRIE_PATTERN.length, 27);
|
|
338
|
+
assert(Array.isArray(DjsonPtrieCodec.DJSON_PTRIE_PATTERN));
|
|
339
|
+
assert.equal(DjsonPtrieCodec.DJSON_PTRIE_PATTERN.length, 33);
|
|
340
|
+
|
|
341
|
+
const samples = [
|
|
342
|
+
null,
|
|
343
|
+
true,
|
|
344
|
+
false,
|
|
345
|
+
123.456,
|
|
346
|
+
"Hello world!",
|
|
347
|
+
[],
|
|
348
|
+
[1, "text", false, null],
|
|
349
|
+
{},
|
|
350
|
+
{ key: "value", nested: [1, 2, { deep: true }] }
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
for (const [label, codec, pat, relPath] of [
|
|
354
|
+
["djson", DjsonCodec, DjsonCodec.DJSON_PATTERN, "codecs/djson.mjs"],
|
|
355
|
+
[
|
|
356
|
+
"djson-btrie",
|
|
357
|
+
DjsonBtrieCodec,
|
|
358
|
+
DjsonBtrieCodec.DJSON_BTRIE_PATTERN,
|
|
359
|
+
"codecs/djson-btrie.mjs"
|
|
360
|
+
],
|
|
361
|
+
[
|
|
362
|
+
"djson-ptrie",
|
|
363
|
+
DjsonPtrieCodec,
|
|
364
|
+
DjsonPtrieCodec.DJSON_PTRIE_PATTERN,
|
|
365
|
+
"codecs/djson-ptrie.mjs"
|
|
366
|
+
]
|
|
367
|
+
]) {
|
|
368
|
+
for (const sample of samples) {
|
|
369
|
+
const jsonStr = JSON.stringify(sample);
|
|
370
|
+
const enveloped = codec.parse(jsonStr);
|
|
371
|
+
assert.strictEqual(
|
|
372
|
+
enveloped.pattern,
|
|
373
|
+
pat,
|
|
374
|
+
`Every ${label} value must share the exact singleton pattern`
|
|
375
|
+
);
|
|
376
|
+
const wire = SDK.encodeToWire(enveloped, enveloped.pattern);
|
|
377
|
+
const decoded = SDK.decodeWire(wire);
|
|
378
|
+
const restored = codec.print(decoded.value);
|
|
379
|
+
assert.deepEqual(
|
|
380
|
+
JSON.parse(restored),
|
|
381
|
+
sample,
|
|
382
|
+
`${label} round-trip mismatch for: ${jsonStr}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const cliSample = JSON.stringify({ a: 1, b: [true, "ok"] });
|
|
386
|
+
const parseRes = runCLI(relPath, ["--parse"], cliSample);
|
|
387
|
+
assert.equal(parseRes.status, 0);
|
|
388
|
+
const printRes = runCLI(relPath, ["--print"], parseRes.stdout);
|
|
389
|
+
assert.equal(printRes.status, 0);
|
|
390
|
+
assert.deepEqual(
|
|
391
|
+
JSON.parse(printRes.stdout.toString("utf8")),
|
|
392
|
+
JSON.parse(cliSample)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
186
397
|
console.log("OK - All codec tests passed.");
|