@fraczak/k 7.1.0 → 7.2.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 +33 -0
- package/DOCS/CODECS.md +37 -2
- package/DOCS/CODEC_ARCHITECTURE.md +12 -5
- package/DOCS/REPL.md +11 -3
- package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
- package/README.md +2 -1
- package/Value.mjs +28 -3
- package/backend-api.mjs +5 -1
- package/browser/tree-view.mjs +47 -8
- package/browser/vfs-data.mjs +6 -5
- package/codecs/README.md +1 -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/package.json +2 -1
- package/repl.mjs +57 -6
- package/scripts/build-repl-html.mjs +8 -1
- package/tests/test-codecs.mjs +168 -0
- package/tests/test-repl-html.mjs +1 -0
- package/tests/test-repl.mjs +33 -0
|
@@ -6,7 +6,11 @@ function pLabel(label) {
|
|
|
6
6
|
return nameRE.test(label) ? ` ${label}` : ` ${JSON.stringify(label)}`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function propertyListToFilter(
|
|
9
|
+
function propertyListToFilter(
|
|
10
|
+
propertyList,
|
|
11
|
+
varPrefix = "X",
|
|
12
|
+
limit = Infinity
|
|
13
|
+
) {
|
|
10
14
|
if (!propertyList || propertyList.length === 0) return "(...)";
|
|
11
15
|
|
|
12
16
|
const refCount = new Array(propertyList.length).fill(0);
|
|
@@ -22,9 +26,15 @@ function propertyListToFilter(propertyList, varPrefix = "X") {
|
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
const defined = new Set();
|
|
29
|
+
let curLen = 0;
|
|
25
30
|
|
|
26
31
|
function fmt(nodeId) {
|
|
27
|
-
if (
|
|
32
|
+
if (curLen >= limit) return "...";
|
|
33
|
+
if (varNames.has(nodeId) && defined.has(nodeId)) {
|
|
34
|
+
const name = varNames.get(nodeId);
|
|
35
|
+
curLen += name.length;
|
|
36
|
+
return name;
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
const [kind, edges] = propertyList[nodeId];
|
|
30
40
|
const hasVar = varNames.has(nodeId);
|
|
@@ -32,16 +42,35 @@ function propertyListToFilter(propertyList, varPrefix = "X") {
|
|
|
32
42
|
|
|
33
43
|
const suffix = hasVar ? `=${varNames.get(nodeId)}` : "";
|
|
34
44
|
|
|
35
|
-
if (kind === "any")
|
|
45
|
+
if (kind === "any") {
|
|
46
|
+
const s = (hasVar ? varNames.get(nodeId) : "(...)") + suffix;
|
|
47
|
+
curLen += s.length;
|
|
48
|
+
return s;
|
|
49
|
+
}
|
|
36
50
|
|
|
37
51
|
const isOpen = kind.startsWith("open-");
|
|
38
52
|
const isProduct = kind.endsWith("product");
|
|
39
53
|
const open = isProduct ? "{" : "<";
|
|
40
54
|
const close = isProduct ? "}" : ">";
|
|
41
55
|
|
|
42
|
-
if (edges.length === 0 && !isOpen)
|
|
56
|
+
if (edges.length === 0 && !isOpen) {
|
|
57
|
+
const s = `${open}${close}${suffix}`;
|
|
58
|
+
curLen += s.length;
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
43
61
|
|
|
44
|
-
const fields =
|
|
62
|
+
const fields = [];
|
|
63
|
+
for (let i = 0; i < edges.length; i++) {
|
|
64
|
+
if (curLen >= limit) {
|
|
65
|
+
fields.push("...");
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const [label, target] = edges[i];
|
|
69
|
+
const targetStr = fmt(target);
|
|
70
|
+
const labelStr = pLabel(label);
|
|
71
|
+
fields.push(`${targetStr}${labelStr}`);
|
|
72
|
+
curLen += targetStr.length + labelStr.length + 2;
|
|
73
|
+
}
|
|
45
74
|
if (isOpen) fields.push("...");
|
|
46
75
|
return `${open}${fields.join(", ")}${close}${suffix}`;
|
|
47
76
|
}
|
|
@@ -49,81 +78,94 @@ function propertyListToFilter(propertyList, varPrefix = "X") {
|
|
|
49
78
|
return fmt(0);
|
|
50
79
|
}
|
|
51
80
|
|
|
52
|
-
function valueToK(root) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
assign(s) { result = s; }
|
|
57
|
-
}];
|
|
81
|
+
function valueToK(root, limit = Infinity) {
|
|
82
|
+
const out = [];
|
|
83
|
+
let curLen = 0;
|
|
84
|
+
const stack = [root];
|
|
58
85
|
|
|
59
86
|
while (stack.length > 0) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
if (curLen >= limit) {
|
|
88
|
+
out.push("...");
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
const item = stack.pop();
|
|
92
|
+
if (typeof item === "string") {
|
|
93
|
+
if (curLen + item.length > limit) {
|
|
94
|
+
out.push(item.slice(0, Math.max(0, limit - curLen)));
|
|
95
|
+
out.push("...");
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
out.push(item);
|
|
99
|
+
curLen += item.length;
|
|
63
100
|
continue;
|
|
64
101
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
102
|
+
|
|
103
|
+
if (item == null) {
|
|
104
|
+
const s = String(item);
|
|
105
|
+
if (curLen + s.length > limit) {
|
|
106
|
+
out.push(s.slice(0, Math.max(0, limit - curLen)));
|
|
107
|
+
out.push("...");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
out.push(s);
|
|
111
|
+
curLen += s.length;
|
|
69
112
|
continue;
|
|
70
113
|
}
|
|
71
114
|
|
|
72
|
-
|
|
73
|
-
if (isVariant(v)) {
|
|
115
|
+
if (isVariant(item)) {
|
|
74
116
|
const tags = [];
|
|
75
|
-
let curr =
|
|
117
|
+
let curr = item;
|
|
76
118
|
while (isVariant(curr)) {
|
|
77
119
|
tags.push(pLabel(curr.tag).trimStart());
|
|
78
120
|
curr = curr.value;
|
|
121
|
+
if (tags.length > limit) break;
|
|
79
122
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
assign: frame.assign
|
|
85
|
-
};
|
|
86
|
-
stack.push(finishFrame);
|
|
87
|
-
stack.push({
|
|
88
|
-
val: curr,
|
|
89
|
-
assign(s) { finishFrame.base = s; }
|
|
90
|
-
});
|
|
123
|
+
if (tags.length > 0) {
|
|
124
|
+
stack.push("|" + tags.reverse().join("|"));
|
|
125
|
+
}
|
|
126
|
+
stack.push(curr);
|
|
91
127
|
continue;
|
|
92
128
|
}
|
|
93
129
|
|
|
94
|
-
if (isProduct(
|
|
95
|
-
const keys = Object.keys(
|
|
130
|
+
if (isProduct(item)) {
|
|
131
|
+
const keys = Object.keys(item.product);
|
|
96
132
|
if (keys.length === 0) {
|
|
97
|
-
|
|
133
|
+
out.push("{}");
|
|
134
|
+
curLen += 2;
|
|
98
135
|
continue;
|
|
99
136
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
parts,
|
|
104
|
-
assign: frame.assign
|
|
105
|
-
};
|
|
106
|
-
stack.push(finishFrame);
|
|
137
|
+
out.push("{");
|
|
138
|
+
curLen += 1;
|
|
139
|
+
stack.push("}");
|
|
107
140
|
for (let i = keys.length - 1; i >= 0; i--) {
|
|
108
141
|
const k = keys[i];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
if (i < keys.length - 1) {
|
|
143
|
+
stack.push(", ");
|
|
144
|
+
}
|
|
145
|
+
stack.push(pLabel(k));
|
|
146
|
+
stack.push(item.product[k]);
|
|
114
147
|
}
|
|
115
148
|
continue;
|
|
116
149
|
}
|
|
117
150
|
|
|
118
|
-
|
|
151
|
+
const s = String(item);
|
|
152
|
+
if (curLen + s.length > limit) {
|
|
153
|
+
out.push(s.slice(0, Math.max(0, limit - curLen)));
|
|
154
|
+
out.push("...");
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
out.push(s);
|
|
158
|
+
curLen += s.length;
|
|
119
159
|
}
|
|
120
160
|
|
|
121
|
-
return
|
|
161
|
+
return out.join("");
|
|
122
162
|
}
|
|
123
163
|
|
|
124
|
-
function valueWithEnvelopeToK(value) {
|
|
164
|
+
function valueWithEnvelopeToK(value, limit = Infinity) {
|
|
125
165
|
if (value === undefined) return "... undefined";
|
|
126
|
-
|
|
166
|
+
const valStr = valueToK(value, limit);
|
|
167
|
+
const patStr = propertyListToFilter(value.pattern, "X", limit);
|
|
168
|
+
return `${valStr} ?${patStr}`;
|
|
127
169
|
}
|
|
128
170
|
|
|
129
171
|
export { propertyListToFilter, valueToK, valueWithEnvelopeToK };
|
|
@@ -3,6 +3,15 @@ import { Value, isProduct, isVariant } from "../../Value.mjs";
|
|
|
3
3
|
const UNIT = Value.product({});
|
|
4
4
|
const BIT0 = Value.variant("0", UNIT);
|
|
5
5
|
const BIT1 = Value.variant("1", UNIT);
|
|
6
|
+
const BIT_KEYS = [
|
|
7
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
let lazyStringDecoder = null;
|
|
11
|
+
|
|
12
|
+
function setLazyStringDecoder(fn) {
|
|
13
|
+
lazyStringDecoder = fn;
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
function bitValue(bit) {
|
|
8
17
|
return bit === 0 ? BIT0 : BIT1;
|
|
@@ -25,7 +34,8 @@ function requireVariant(value, where) {
|
|
|
25
34
|
function bitsProduct(width, n) {
|
|
26
35
|
const product = {};
|
|
27
36
|
for (let i = width - 1; i >= 0; i--) {
|
|
28
|
-
|
|
37
|
+
const key = i < BIT_KEYS.length ? BIT_KEYS[i] : String(i);
|
|
38
|
+
product[key] = bitValue((n >> i) & 1);
|
|
29
39
|
}
|
|
30
40
|
return Value.product(product);
|
|
31
41
|
}
|
|
@@ -34,7 +44,8 @@ function parseBitsProduct(value, maxBit, where) {
|
|
|
34
44
|
const product = requireProduct(value, where);
|
|
35
45
|
let n = 0;
|
|
36
46
|
for (let i = maxBit; i >= 0; i--) {
|
|
37
|
-
const
|
|
47
|
+
const key = i < BIT_KEYS.length ? BIT_KEYS[i] : String(i);
|
|
48
|
+
const entry = requireVariant(product[key], `${where}.bit${i}`);
|
|
38
49
|
if (entry.tag !== "0" && entry.tag !== "1") {
|
|
39
50
|
throw new Error(`${where}.bit${i}: expected tag 0 or 1`);
|
|
40
51
|
}
|
|
@@ -194,20 +205,31 @@ function textToStringValue(text) {
|
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
function stringValueToText(value) {
|
|
197
|
-
|
|
208
|
+
if (lazyStringDecoder) {
|
|
209
|
+
const fast = lazyStringDecoder(value);
|
|
210
|
+
if (fast !== null) return fast;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const chunks = [];
|
|
214
|
+
let chunk = "";
|
|
198
215
|
let cursor = value;
|
|
199
216
|
|
|
200
217
|
while (true) {
|
|
201
218
|
const list = requireVariant(cursor, "string");
|
|
202
219
|
if (list.tag === "nil") {
|
|
203
220
|
requireProduct(list.value, "string.nil");
|
|
204
|
-
|
|
221
|
+
if (chunk.length > 0) chunks.push(chunk);
|
|
222
|
+
return chunks.join("");
|
|
205
223
|
}
|
|
206
224
|
if (list.tag !== "cons") {
|
|
207
225
|
throw new Error(`Unsupported string list tag: ${list.tag}`);
|
|
208
226
|
}
|
|
209
227
|
const pair = requireProduct(list.value, "string.cons");
|
|
210
|
-
|
|
228
|
+
chunk += String.fromCodePoint(unicodeValueToCodePoint(pair.car));
|
|
229
|
+
if (chunk.length >= 8192) {
|
|
230
|
+
chunks.push(chunk);
|
|
231
|
+
chunk = "";
|
|
232
|
+
}
|
|
211
233
|
cursor = pair.cdr;
|
|
212
234
|
}
|
|
213
235
|
}
|
|
@@ -216,5 +238,6 @@ export {
|
|
|
216
238
|
codePointToUnicodeValue,
|
|
217
239
|
unicodeValueToCodePoint,
|
|
218
240
|
textToStringValue,
|
|
219
|
-
stringValueToText
|
|
241
|
+
stringValueToText,
|
|
242
|
+
setLazyStringDecoder
|
|
220
243
|
};
|
package/codecs/show.mjs
CHANGED
|
@@ -51,6 +51,10 @@ async function main() {
|
|
|
51
51
|
else { usage(); process.exit(1); }
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
stdout.on("error", (err) => {
|
|
55
|
+
if (err.code === "EPIPE") process.exit(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
54
58
|
const buffer = fileArg ? fs.readFileSync(fileArg) : await readAll(stdin);
|
|
55
59
|
|
|
56
60
|
// forward unchanged to stdout
|
package/codecs/utf16.mjs
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
export const doc = `
|
|
11
11
|
UTF-16 text codec (BOM-aware).
|
|
12
12
|
|
|
13
|
-
Reads UTF-16 text (LE/BE with BOM or LE without BOM),
|
|
13
|
+
Reads UTF-16 text (LE/BE with BOM or LE without BOM),
|
|
14
|
+
outputs UTF-16LE with BOM.
|
|
14
15
|
`;
|
|
15
16
|
|
|
16
17
|
function decodeUtf16Input(buf) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "k — a language for content-addressable schemas and data transformations over algebraic data types (products and tagged unions); includes runtime, contract derivation, REPL, and codec toolchain",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"k-wasm": "backends/wasm/bin/k-wasm.mjs",
|
|
59
59
|
"k-wasm-compile": "backends/wasm/bin/k-wasm-compile.mjs",
|
|
60
60
|
"k-wasm-run": "backends/wasm/bin/k-wasm-run.mjs",
|
|
61
|
+
"k-djson": "codecs/djson.mjs",
|
|
61
62
|
"k-ieee": "codecs/ieee.mjs",
|
|
62
63
|
"k-int": "codecs/int.mjs",
|
|
63
64
|
"k-json": "codecs/json.mjs",
|
package/repl.mjs
CHANGED
|
@@ -50,7 +50,7 @@ const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
|
50
50
|
const COMMAND_NAMES = [
|
|
51
51
|
"help", "rel",
|
|
52
52
|
"codecs", "rels", "codec", "input", "reset", "klib", "ko", "load",
|
|
53
|
-
"engine", "wasm", "pkir", "js",
|
|
53
|
+
"engine", "wasm", "pkir", "js", "limit",
|
|
54
54
|
"quit", "exit"
|
|
55
55
|
];
|
|
56
56
|
const CODEC_COMMAND_NAMES = ["load", "unload", "list"];
|
|
@@ -95,7 +95,8 @@ function createState(options = {}) {
|
|
|
95
95
|
lastResult: null,
|
|
96
96
|
lastMain: null,
|
|
97
97
|
lastTiming: null,
|
|
98
|
-
showTiming: true
|
|
98
|
+
showTiming: true,
|
|
99
|
+
printLimit: options.printLimit ?? Infinity
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
|
|
@@ -354,7 +355,8 @@ function formatCodecOutput(output) {
|
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
function printValue(value, state = null) {
|
|
357
|
-
const
|
|
358
|
+
const limit = state?.printLimit ?? Infinity;
|
|
359
|
+
const lines = [valueWithEnvelopeToK(value, limit)];
|
|
358
360
|
if (!state || value === undefined) return lines[0];
|
|
359
361
|
|
|
360
362
|
const store = state.codecs || {};
|
|
@@ -363,7 +365,11 @@ function printValue(value, state = null) {
|
|
|
363
365
|
try {
|
|
364
366
|
const output = codec.print(value, { state });
|
|
365
367
|
if (output != null) {
|
|
366
|
-
|
|
368
|
+
let text = formatCodecOutput(output);
|
|
369
|
+
if (Number.isFinite(limit) && text.length > limit) {
|
|
370
|
+
text = text.slice(0, limit) + "...";
|
|
371
|
+
}
|
|
372
|
+
lines.push(`${codec.name}: ${text}`);
|
|
367
373
|
}
|
|
368
374
|
} catch {
|
|
369
375
|
// Codec cannot format this value; silently skip
|
|
@@ -1265,9 +1271,34 @@ async function evaluateCommand(line, state) {
|
|
|
1265
1271
|
state.engine = "js";
|
|
1266
1272
|
return ["engine set to js"];
|
|
1267
1273
|
}
|
|
1274
|
+
case "limit": {
|
|
1275
|
+
const mode = (arg || "").trim().toLowerCase();
|
|
1276
|
+
if (!mode) {
|
|
1277
|
+
const current = Number.isFinite(state.printLimit)
|
|
1278
|
+
? state.printLimit
|
|
1279
|
+
: "none";
|
|
1280
|
+
return [`Current output limit: ${current}`];
|
|
1281
|
+
}
|
|
1282
|
+
if (mode === "none" || mode === "infinity" || mode === "off") {
|
|
1283
|
+
state.printLimit = Infinity;
|
|
1284
|
+
return ["output limit disabled"];
|
|
1285
|
+
}
|
|
1286
|
+
const n = parseInt(mode, 10);
|
|
1287
|
+
if (Number.isNaN(n) || n < 0) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`Invalid limit '${arg.trim()}'. Usage: :limit [num|none]`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
state.printLimit = n;
|
|
1293
|
+
return [`output limit set to ${n}`];
|
|
1294
|
+
}
|
|
1268
1295
|
case "reset": {
|
|
1269
1296
|
const currentEngine = state.engine;
|
|
1270
|
-
const
|
|
1297
|
+
const currentLimit = state.printLimit;
|
|
1298
|
+
const fresh = createState({
|
|
1299
|
+
engine: currentEngine,
|
|
1300
|
+
printLimit: currentLimit
|
|
1301
|
+
});
|
|
1271
1302
|
Object.assign(state, fresh);
|
|
1272
1303
|
return ["reset"];
|
|
1273
1304
|
}
|
|
@@ -1302,6 +1333,7 @@ function helpText() {
|
|
|
1302
1333
|
":engine [wasm|pkir|js] display or switch evaluation engine",
|
|
1303
1334
|
":rel name show relation definition",
|
|
1304
1335
|
":rels list relation aliases",
|
|
1336
|
+
":limit [num|none] set maximum characters for value output",
|
|
1305
1337
|
":codec load file load a codec module from file (e.g. :codec load codecs/int.mjs)",
|
|
1306
1338
|
":codec unload name unload a registered codec",
|
|
1307
1339
|
":codec list list loaded codecs (or simply :codecs)",
|
|
@@ -1336,6 +1368,7 @@ function cliUsage() {
|
|
|
1336
1368
|
console.log(" --pkir Shortcut for --engine=pkir.");
|
|
1337
1369
|
console.log(" --wasm Shortcut for --engine=wasm.");
|
|
1338
1370
|
console.log(" --js Shortcut for --engine=js.");
|
|
1371
|
+
console.log(" --limit=<num> Set maximum characters for output.");
|
|
1339
1372
|
console.log(" -h, --help Show this help.");
|
|
1340
1373
|
}
|
|
1341
1374
|
|
|
@@ -1431,6 +1464,7 @@ function startRepl(options = {}) {
|
|
|
1431
1464
|
if (isMainEntrypoint()) {
|
|
1432
1465
|
const args = argv.slice(2);
|
|
1433
1466
|
let engine = "wasm";
|
|
1467
|
+
let printLimit = Infinity;
|
|
1434
1468
|
for (let i = 0; i < args.length; i++) {
|
|
1435
1469
|
const arg = args[i];
|
|
1436
1470
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -1461,13 +1495,30 @@ if (isMainEntrypoint()) {
|
|
|
1461
1495
|
exit(1);
|
|
1462
1496
|
}
|
|
1463
1497
|
engine = val;
|
|
1498
|
+
} else if (arg.startsWith("--limit=")) {
|
|
1499
|
+
const val = parseInt(arg.slice("--limit=".length), 10);
|
|
1500
|
+
if (Number.isNaN(val) || val < 0) {
|
|
1501
|
+
console.error(
|
|
1502
|
+
`Invalid limit '${arg.slice("--limit=".length)}'.`
|
|
1503
|
+
);
|
|
1504
|
+
exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
printLimit = val;
|
|
1507
|
+
} else if (arg === "--limit") {
|
|
1508
|
+
i++;
|
|
1509
|
+
const val = parseInt(args[i] || "", 10);
|
|
1510
|
+
if (Number.isNaN(val) || val < 0) {
|
|
1511
|
+
console.error(`Invalid limit '${args[i]}'.`);
|
|
1512
|
+
exit(1);
|
|
1513
|
+
}
|
|
1514
|
+
printLimit = val;
|
|
1464
1515
|
} else {
|
|
1465
1516
|
console.error(`Unknown option '${arg}'.`);
|
|
1466
1517
|
cliUsage();
|
|
1467
1518
|
exit(1);
|
|
1468
1519
|
}
|
|
1469
1520
|
}
|
|
1470
|
-
startRepl({ engine });
|
|
1521
|
+
startRepl({ engine, printLimit });
|
|
1471
1522
|
}
|
|
1472
1523
|
|
|
1473
1524
|
export {
|
|
@@ -91,7 +91,14 @@ for (const filename of verifiedExamples) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// Bundle verified codecs for browser VFS as 100% self-contained ES modules
|
|
94
|
-
const verifiedCodecs = [
|
|
94
|
+
const verifiedCodecs = [
|
|
95
|
+
"json.mjs",
|
|
96
|
+
"djson.mjs",
|
|
97
|
+
"utf8.mjs",
|
|
98
|
+
"int.mjs",
|
|
99
|
+
"unit.mjs",
|
|
100
|
+
"ieee.mjs"
|
|
101
|
+
];
|
|
95
102
|
const codecsDir = path.join(projectRoot, "codecs");
|
|
96
103
|
|
|
97
104
|
const browserCodecSdkShim = `
|
package/tests/test-codecs.mjs
CHANGED
|
@@ -8,10 +8,15 @@ 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";
|
|
11
12
|
import * as Utf8Codec from "../codecs/utf8.mjs";
|
|
12
13
|
import * as Utf16Codec from "../codecs/utf16.mjs";
|
|
13
14
|
import * as IeeeCodec from "../codecs/ieee.mjs";
|
|
14
15
|
import * as UnitCodec from "../codecs/unit.mjs";
|
|
16
|
+
import {
|
|
17
|
+
valueToK,
|
|
18
|
+
valueWithEnvelopeToK
|
|
19
|
+
} from "../codecs/runtime/show-value.mjs";
|
|
15
20
|
|
|
16
21
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
22
|
|
|
@@ -49,6 +54,7 @@ console.log("2. Verifying in-memory parse -> enveloped value (P, v)...");
|
|
|
49
54
|
for (const [name, codec, sampleInput] of [
|
|
50
55
|
["int.mjs", IntCodec, "42"],
|
|
51
56
|
["json.mjs", JsonCodec, '{"x": 1}'],
|
|
57
|
+
["djson.mjs", DjsonCodec, '{"x": 1, "arr": [true, null, "hi"]}'],
|
|
52
58
|
["utf8.mjs", Utf8Codec, "hello"],
|
|
53
59
|
["utf16.mjs", Utf16Codec, "hello"],
|
|
54
60
|
["ieee.mjs", IeeeCodec, "3.14"],
|
|
@@ -68,6 +74,7 @@ console.log("3. Verifying CLI --help flag...");
|
|
|
68
74
|
for (const relPath of [
|
|
69
75
|
"codecs/int.mjs",
|
|
70
76
|
"codecs/json.mjs",
|
|
77
|
+
"codecs/djson.mjs",
|
|
71
78
|
"codecs/utf8.mjs",
|
|
72
79
|
"codecs/utf16.mjs",
|
|
73
80
|
"codecs/ieee.mjs",
|
|
@@ -111,6 +118,24 @@ console.log("4. Verifying CLI --parse and --print pipelines...");
|
|
|
111
118
|
assert.deepEqual(JSON.parse(printRes.stdout.toString("utf8")), JSON.parse(jsonInput));
|
|
112
119
|
}
|
|
113
120
|
|
|
121
|
+
// djson.mjs: dynamic JSON AST with singleton pattern
|
|
122
|
+
{
|
|
123
|
+
const djsonInput = JSON.stringify({
|
|
124
|
+
name: "k",
|
|
125
|
+
count: 42.5,
|
|
126
|
+
tags: ["fast", "typed"],
|
|
127
|
+
meta: { active: true, extra: null }
|
|
128
|
+
});
|
|
129
|
+
const parseRes = runCLI("codecs/djson.mjs", ["--parse"], djsonInput);
|
|
130
|
+
assert.equal(parseRes.status, 0);
|
|
131
|
+
const printRes = runCLI("codecs/djson.mjs", ["--print"], parseRes.stdout);
|
|
132
|
+
assert.equal(printRes.status, 0);
|
|
133
|
+
assert.deepEqual(
|
|
134
|
+
JSON.parse(printRes.stdout.toString("utf8")),
|
|
135
|
+
JSON.parse(djsonInput)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
114
139
|
// utf8.mjs: unicode text
|
|
115
140
|
{
|
|
116
141
|
const utf8Input = "Hello 🌍, ąęćłó! \nSecond line.";
|
|
@@ -183,4 +208,147 @@ console.log("6. Verifying pattern derivation via k type system (patternFromFilte
|
|
|
183
208
|
assert.throws(() => SDK.patternFromFilter(".field"), /Main expression must be a filter or a type name/);
|
|
184
209
|
}
|
|
185
210
|
|
|
211
|
+
// 7. Lazy Prefix-Free Wire Decoding
|
|
212
|
+
console.log("7. Verifying lazy prefix-free wire decoding...");
|
|
213
|
+
{
|
|
214
|
+
assert.equal(typeof SDK.decodeWireLazy, "function");
|
|
215
|
+
|
|
216
|
+
// Lazy decoding should instantiate root in sub-millisecond time
|
|
217
|
+
const sampleStr = "a".repeat(20000);
|
|
218
|
+
const parsedStr = Utf8Codec.parse(sampleStr);
|
|
219
|
+
const wireStr = SDK.encodeToWire(parsedStr, parsedStr.pattern);
|
|
220
|
+
|
|
221
|
+
const t0 = performance.now();
|
|
222
|
+
const decodedLazy = SDK.decodeWire(wireStr);
|
|
223
|
+
const constructMs = performance.now() - t0;
|
|
224
|
+
assert(
|
|
225
|
+
constructMs < 50,
|
|
226
|
+
`Lazy decode took ${constructMs}ms (expected <50ms)`
|
|
227
|
+
);
|
|
228
|
+
assert.equal(decodedLazy.value.tag, "cons");
|
|
229
|
+
|
|
230
|
+
// On-demand traversal through print()
|
|
231
|
+
const printedStr = Utf8Codec.print(decodedLazy.value);
|
|
232
|
+
assert.equal(printedStr, sampleStr);
|
|
233
|
+
|
|
234
|
+
// Lazy list of integers
|
|
235
|
+
const ints = Array.from({ length: 5000 }, (_, i) => i).join(",");
|
|
236
|
+
const sampleList = `[${ints}]`;
|
|
237
|
+
const parsedList = IntCodec.parse(sampleList);
|
|
238
|
+
const wireList = SDK.encodeToWire(parsedList, parsedList.pattern);
|
|
239
|
+
const decodedList = SDK.decodeWireLazy(wireList);
|
|
240
|
+
assert.equal(IntCodec.print(decodedList.value), sampleList);
|
|
241
|
+
|
|
242
|
+
// Verify stack-safe toJSON on deep variant chains
|
|
243
|
+
let deepVal = SDK.Value.product({});
|
|
244
|
+
for (let i = 0; i < 15000; i++) {
|
|
245
|
+
deepVal = SDK.Value.variant("step", deepVal);
|
|
246
|
+
}
|
|
247
|
+
const json = deepVal.toJSON();
|
|
248
|
+
assert(json && typeof json === "object");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 8. High-Speed Direct Bitstream String Decoding & Bounded Rendering
|
|
252
|
+
console.log("8. Verifying fast string decoding & bounded rendering...");
|
|
253
|
+
{
|
|
254
|
+
const testStrings = [
|
|
255
|
+
"hello world",
|
|
256
|
+
"Zażółć gęślą jaźń",
|
|
257
|
+
"Unicode: 🚀🎉 𝒦-lang π ≈ 3.14159",
|
|
258
|
+
"a".repeat(50000),
|
|
259
|
+
""
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
for (const s of testStrings) {
|
|
263
|
+
const parsed = Utf8Codec.parse(s);
|
|
264
|
+
const wire = SDK.encodeToWire(parsed, parsed.pattern);
|
|
265
|
+
const decoded = SDK.decodeWire(wire);
|
|
266
|
+
const restored = Utf8Codec.print(decoded.value);
|
|
267
|
+
assert.equal(
|
|
268
|
+
restored,
|
|
269
|
+
s,
|
|
270
|
+
`String round-trip failed for "${s.slice(0, 20)}..."`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Fast decoding performance: 50,000 ASCII chars in < 500ms
|
|
275
|
+
const bigAscii = "x".repeat(50000);
|
|
276
|
+
const bigParsed = Utf8Codec.parse(bigAscii);
|
|
277
|
+
const bigWire = SDK.encodeToWire(bigParsed, bigParsed.pattern);
|
|
278
|
+
const tStart = performance.now();
|
|
279
|
+
const bigDecoded = SDK.decodeWire(bigWire);
|
|
280
|
+
const bigRestored = Utf8Codec.print(bigDecoded.value);
|
|
281
|
+
const elapsed = performance.now() - tStart;
|
|
282
|
+
assert.equal(bigRestored.length, 50000);
|
|
283
|
+
assert(
|
|
284
|
+
elapsed < 500,
|
|
285
|
+
`Fast string decoding took ${elapsed}ms (expected < 500ms)`
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Bounded rendering: valueToK with limit
|
|
289
|
+
let deepVal = SDK.Value.product({});
|
|
290
|
+
for (let i = 0; i < 1000; i++) {
|
|
291
|
+
deepVal = SDK.Value.variant("item", deepVal);
|
|
292
|
+
}
|
|
293
|
+
const preview = valueToK(deepVal, 30);
|
|
294
|
+
assert(preview.endsWith("..."), "Output should end with '...'");
|
|
295
|
+
assert(preview.length <= 35, "Preview length should stay bounded");
|
|
296
|
+
|
|
297
|
+
// valueWithEnvelopeToK with limit
|
|
298
|
+
const enveloped = SDK.withPattern(deepVal, [["any", []]]);
|
|
299
|
+
const envPreview = valueWithEnvelopeToK(enveloped, 30);
|
|
300
|
+
assert(envPreview.includes("..."), "Preview should contain '...'");
|
|
301
|
+
|
|
302
|
+
// Ensure early termination: evaluating deep chain with limit 20
|
|
303
|
+
let hugeVal = SDK.Value.product({});
|
|
304
|
+
for (let i = 0; i < 50000; i++) {
|
|
305
|
+
hugeVal = SDK.Value.variant("v", hugeVal);
|
|
306
|
+
}
|
|
307
|
+
const tEarly0 = performance.now();
|
|
308
|
+
const earlyPreview = valueToK(hugeVal, 20);
|
|
309
|
+
const tEarlyElapsed = performance.now() - tEarly0;
|
|
310
|
+
assert(earlyPreview.endsWith("..."));
|
|
311
|
+
assert(
|
|
312
|
+
tEarlyElapsed < 50,
|
|
313
|
+
`Early termination took ${tEarlyElapsed}ms (expected < 50ms)`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 9. DJSON Singleton Pattern Invariant & Round-Trip
|
|
318
|
+
console.log("9. Verifying DJSON singleton pattern invariant...");
|
|
319
|
+
{
|
|
320
|
+
assert(Array.isArray(DjsonCodec.DJSON_PATTERN));
|
|
321
|
+
assert.equal(DjsonCodec.DJSON_PATTERN.length, 29);
|
|
322
|
+
|
|
323
|
+
const samples = [
|
|
324
|
+
null,
|
|
325
|
+
true,
|
|
326
|
+
false,
|
|
327
|
+
123.456,
|
|
328
|
+
"Hello world!",
|
|
329
|
+
[],
|
|
330
|
+
[1, "text", false, null],
|
|
331
|
+
{},
|
|
332
|
+
{ key: "value", nested: [1, 2, { deep: true }] }
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
for (const sample of samples) {
|
|
336
|
+
const jsonStr = JSON.stringify(sample);
|
|
337
|
+
const enveloped = DjsonCodec.parse(jsonStr);
|
|
338
|
+
assert.strictEqual(
|
|
339
|
+
enveloped.pattern,
|
|
340
|
+
DjsonCodec.DJSON_PATTERN,
|
|
341
|
+
"Every DJSON value must share the exact singleton pattern"
|
|
342
|
+
);
|
|
343
|
+
const wire = SDK.encodeToWire(enveloped, enveloped.pattern);
|
|
344
|
+
const decoded = SDK.decodeWire(wire);
|
|
345
|
+
const restored = DjsonCodec.print(decoded.value);
|
|
346
|
+
assert.deepEqual(
|
|
347
|
+
JSON.parse(restored),
|
|
348
|
+
sample,
|
|
349
|
+
`DJSON round-trip mismatch for: ${jsonStr}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
186
354
|
console.log("OK - All codec tests passed.");
|