@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/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
|
@@ -9,6 +9,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
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";
|
|
12
14
|
import * as Utf8Codec from "../codecs/utf8.mjs";
|
|
13
15
|
import * as Utf16Codec from "../codecs/utf16.mjs";
|
|
14
16
|
import * as IeeeCodec from "../codecs/ieee.mjs";
|
|
@@ -55,6 +57,16 @@ for (const [name, codec, sampleInput] of [
|
|
|
55
57
|
["int.mjs", IntCodec, "42"],
|
|
56
58
|
["json.mjs", JsonCodec, '{"x": 1}'],
|
|
57
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
|
+
],
|
|
58
70
|
["utf8.mjs", Utf8Codec, "hello"],
|
|
59
71
|
["utf16.mjs", Utf16Codec, "hello"],
|
|
60
72
|
["ieee.mjs", IeeeCodec, "3.14"],
|
|
@@ -75,6 +87,8 @@ for (const relPath of [
|
|
|
75
87
|
"codecs/int.mjs",
|
|
76
88
|
"codecs/json.mjs",
|
|
77
89
|
"codecs/djson.mjs",
|
|
90
|
+
"codecs/djson-btrie.mjs",
|
|
91
|
+
"codecs/djson-ptrie.mjs",
|
|
78
92
|
"codecs/utf8.mjs",
|
|
79
93
|
"codecs/utf16.mjs",
|
|
80
94
|
"codecs/ieee.mjs",
|
|
@@ -314,11 +328,15 @@ console.log("8. Verifying fast string decoding & bounded rendering...");
|
|
|
314
328
|
);
|
|
315
329
|
}
|
|
316
330
|
|
|
317
|
-
// 9. DJSON Singleton Pattern
|
|
318
|
-
console.log("9. Verifying DJSON singleton pattern
|
|
331
|
+
// 9. DJSON Family Singleton Pattern Invariants & Round-Trip
|
|
332
|
+
console.log("9. Verifying DJSON family singleton pattern invariants...");
|
|
319
333
|
{
|
|
320
334
|
assert(Array.isArray(DjsonCodec.DJSON_PATTERN));
|
|
321
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);
|
|
322
340
|
|
|
323
341
|
const samples = [
|
|
324
342
|
null,
|
|
@@ -332,21 +350,46 @@ console.log("9. Verifying DJSON singleton pattern invariant...");
|
|
|
332
350
|
{ key: "value", nested: [1, 2, { deep: true }] }
|
|
333
351
|
];
|
|
334
352
|
|
|
335
|
-
for (const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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);
|
|
346
390
|
assert.deepEqual(
|
|
347
|
-
JSON.parse(
|
|
348
|
-
|
|
349
|
-
`DJSON round-trip mismatch for: ${jsonStr}`
|
|
391
|
+
JSON.parse(printRes.stdout.toString("utf8")),
|
|
392
|
+
JSON.parse(cliSample)
|
|
350
393
|
);
|
|
351
394
|
}
|
|
352
395
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// tests/test-named-patterns.mjs
|
|
2
|
+
// Comprehensive test suite for named patterns and type schemes.
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import k from "../index.mjs";
|
|
6
|
+
import { Value } from "../Value.mjs";
|
|
7
|
+
|
|
8
|
+
console.log("==> Running Named Patterns Test Suite");
|
|
9
|
+
|
|
10
|
+
// Test 1: 0-arity pattern and composite product
|
|
11
|
+
console.log("--> Test 1: 0-arity pattern (bit[]) and product (byte[])");
|
|
12
|
+
{
|
|
13
|
+
const script = `
|
|
14
|
+
bit[] = < {}0, {}1 >;
|
|
15
|
+
byte[] = {
|
|
16
|
+
bit[] b0, bit[] b1, bit[] b2, bit[] b3,
|
|
17
|
+
bit[] b4, bit[] b5, bit[] b6, bit[] b7
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
is_byte = ? byte[];
|
|
21
|
+
|
|
22
|
+
sample = {
|
|
23
|
+
{}|0 b0, {}|1 b1, {}|0 b2, {}|1 b3,
|
|
24
|
+
{}|0 b4, {}|1 b5, {}|0 b6, {}|1 b7
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
sample is_byte
|
|
28
|
+
`;
|
|
29
|
+
const fn = k.compile(script);
|
|
30
|
+
const out = fn(Value.product({}));
|
|
31
|
+
assert.equal(out.type, "{}");
|
|
32
|
+
assert.equal(out.product.b0.tag, "0");
|
|
33
|
+
assert.equal(out.product.b1.tag, "1");
|
|
34
|
+
console.log(" Passed: 0-arity patterns and composition.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Test 2: Parameterized list pattern
|
|
38
|
+
console.log("--> Test 2: Parameterized pattern (list[X])");
|
|
39
|
+
{
|
|
40
|
+
const script = `
|
|
41
|
+
bit[] = < {}0, {}1 >;
|
|
42
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
43
|
+
|
|
44
|
+
is_bit_list = ? list[bit[]];
|
|
45
|
+
|
|
46
|
+
empty = {}|nil is_bit_list;
|
|
47
|
+
one = { {}|1 car, {}|nil cdr }|cons is_bit_list;
|
|
48
|
+
two = {
|
|
49
|
+
{}|0 car,
|
|
50
|
+
{ {}|1 car, {}|nil cdr }|cons cdr
|
|
51
|
+
}|cons is_bit_list;
|
|
52
|
+
|
|
53
|
+
{ empty empty, one one, two two }
|
|
54
|
+
`;
|
|
55
|
+
const fn = k.compile(script);
|
|
56
|
+
const out = fn(Value.product({}));
|
|
57
|
+
assert.equal(out.product.empty.tag, "nil");
|
|
58
|
+
assert.equal(out.product.one.tag, "cons");
|
|
59
|
+
assert.equal(out.product.one.value.product.car.tag, "1");
|
|
60
|
+
assert.equal(out.product.two.tag, "cons");
|
|
61
|
+
assert.equal(out.product.two.value.product.car.tag, "0");
|
|
62
|
+
console.log(" Passed: Parameterized list pattern.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 3: Multi-parameter templates and composition
|
|
66
|
+
console.log("--> Test 3: Multi-parameter templates (pair, dict)");
|
|
67
|
+
{
|
|
68
|
+
const script = `
|
|
69
|
+
bit[] = < {}0, {}1 >;
|
|
70
|
+
pair[A, B] = { A key, B val };
|
|
71
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
72
|
+
dict[K, V] = list[pair[K, V]];
|
|
73
|
+
|
|
74
|
+
is_dict = ? dict[bit[], bit[]];
|
|
75
|
+
|
|
76
|
+
entry = { {}|0 key, {}|1 val };
|
|
77
|
+
d = { entry car, {}|nil cdr }|cons is_dict;
|
|
78
|
+
|
|
79
|
+
d
|
|
80
|
+
`;
|
|
81
|
+
const fn = k.compile(script);
|
|
82
|
+
const out = fn(Value.product({}));
|
|
83
|
+
assert.equal(out.tag, "cons");
|
|
84
|
+
assert.equal(out.value.product.car.product.key.tag, "0");
|
|
85
|
+
assert.equal(out.value.product.car.product.val.tag, "1");
|
|
86
|
+
console.log(" Passed: Multi-parameter template composition.");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Test 4: Existential internal variables (type unification across fields)
|
|
90
|
+
console.log("--> Test 4: Existential variables (same_pair[])");
|
|
91
|
+
{
|
|
92
|
+
const scriptOk = `
|
|
93
|
+
same_pair[] = { T first, T second };
|
|
94
|
+
check = ? same_pair[];
|
|
95
|
+
{ {}|0 first, {}|1 second } check
|
|
96
|
+
`;
|
|
97
|
+
const fnOk = k.compile(scriptOk);
|
|
98
|
+
const outOk = fnOk(Value.product({}));
|
|
99
|
+
assert.equal(outOk.product.first.tag, "0");
|
|
100
|
+
assert.equal(outOk.product.second.tag, "1");
|
|
101
|
+
|
|
102
|
+
const scriptBad = `
|
|
103
|
+
same_pair[] = { T first, T second };
|
|
104
|
+
check = ? same_pair[];
|
|
105
|
+
{ {} first, {}|0 second } check
|
|
106
|
+
`;
|
|
107
|
+
assert.throws(
|
|
108
|
+
() => k.compile(scriptBad),
|
|
109
|
+
/Cannot unify/
|
|
110
|
+
);
|
|
111
|
+
console.log(" Passed: Existential variables unified across fields.");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Test 5: Recursive tree with internal knot and list
|
|
115
|
+
console.log("--> Test 5: Recursive tree with knot T (tree[X])");
|
|
116
|
+
{
|
|
117
|
+
const script = `
|
|
118
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
119
|
+
tree[X] = < {}nil, { X val, list[T] children } node > = T;
|
|
120
|
+
|
|
121
|
+
bit[] = < {}0, {}1 >;
|
|
122
|
+
is_tree = ? tree[bit[]];
|
|
123
|
+
|
|
124
|
+
leaf = { {}|1 val, {}|nil children }|node is_tree;
|
|
125
|
+
root = {
|
|
126
|
+
{}|0 val,
|
|
127
|
+
{ leaf car, {}|nil cdr }|cons children
|
|
128
|
+
}|node is_tree;
|
|
129
|
+
|
|
130
|
+
{ leaf leaf, root root }
|
|
131
|
+
`;
|
|
132
|
+
const fn = k.compile(script);
|
|
133
|
+
const out = fn(Value.product({}));
|
|
134
|
+
assert.equal(out.product.leaf.tag, "node");
|
|
135
|
+
assert.equal(out.product.leaf.value.product.val.tag, "1");
|
|
136
|
+
assert.equal(out.product.root.tag, "node");
|
|
137
|
+
assert.equal(out.product.root.value.product.val.tag, "0");
|
|
138
|
+
console.log(" Passed: Recursive tree template with list port.");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Test 6: Polymorphic relation with shared type variable
|
|
142
|
+
console.log("--> Test 6: Polymorphic relation ({ X car, list[X] cdr })");
|
|
143
|
+
{
|
|
144
|
+
const script = `
|
|
145
|
+
list[X] = < {}nil, { X car, Y cdr } cons > = Y;
|
|
146
|
+
head = ? { X car, list[X] cdr } .car;
|
|
147
|
+
|
|
148
|
+
bit[] = < {}0, {}1 >;
|
|
149
|
+
h1 = { {}|1 car, {}|nil cdr } head;
|
|
150
|
+
|
|
151
|
+
h1
|
|
152
|
+
`;
|
|
153
|
+
const fn = k.compile(script);
|
|
154
|
+
const out = fn(Value.product({}));
|
|
155
|
+
assert.equal(out.tag, "1");
|
|
156
|
+
console.log(" Passed: Polymorphic relation head with list[X].");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Test 7: Error handling for templates
|
|
160
|
+
console.log("--> Test 7: Error handling (unknown, arity, cycles, dupes)");
|
|
161
|
+
{
|
|
162
|
+
assert.throws(
|
|
163
|
+
() => k.compile("check = ? missing[]; ()"),
|
|
164
|
+
/Unknown pattern template 'missing\[\]'/
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
assert.throws(
|
|
168
|
+
() => k.compile("list[X] = {}; check = ? list[]; ()"),
|
|
169
|
+
/Pattern 'list' expects 1 argument, got 0/
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
assert.throws(
|
|
173
|
+
() => k.compile("pair[A, B] = {}; check = ? pair[{}]; ()"),
|
|
174
|
+
/Pattern 'pair' expects 2 arguments, got 1/
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
assert.throws(
|
|
178
|
+
() => k.compile("a[] = b[]; b[] = a[]; check = ? a[]; ()"),
|
|
179
|
+
/Cyclic pattern template dependency detected/
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
assert.throws(
|
|
183
|
+
() => k.compile("dupe[X, X] = {}; ()"),
|
|
184
|
+
/Duplicate parameter 'X' in pattern 'dupe'/
|
|
185
|
+
);
|
|
186
|
+
console.log(" Passed: Error conditions correctly handled.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log("==> All Named Patterns Tests Passed Successfully!");
|