@fraczak/k 7.2.0 → 8.1.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 +90 -0
- package/DOCS/BIG_PICTURE.md +54 -7
- package/DOCS/CODECS.md +21 -13
- package/DOCS/CODEC_ARCHITECTURE.md +1 -1
- package/DOCS/DICTIONARY.md +13 -4
- package/DOCS/FILE_FORMATS.md +3 -2
- package/DOCS/JSON_APPROACHES_COMPARISON.md +302 -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 +3 -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.k +789 -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 +40 -16
- package/browser/repl-browser.mjs +2 -0
- package/browser/tree-view.mjs +35 -16
- package/browser/vfs-data.mjs +10 -8
- package/codecs/README.md +1 -1
- package/codecs/djson.mjs +375 -84
- package/core.k +79 -115
- package/index.mjs +3 -0
- package/named-patterns.mjs +267 -0
- package/object.mjs +31 -3
- package/package.json +1 -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 +58 -4
- package/scripts/run-tests.mjs +10 -0
- package/symbol-table.mjs +20 -2
- package/tests/test-codecs.mjs +30 -17
- package/tests/test-djson-methods.mjs +285 -0
- package/tests/test-named-patterns.mjs +189 -0
- package/tests/test-repl-html.mjs +47 -4
- package/tests/test-repl.mjs +149 -0
package/repl.mjs
CHANGED
|
@@ -21,7 +21,7 @@ if (typeof globalThis !== "undefined") {
|
|
|
21
21
|
globalThis.isVariant = isVariant;
|
|
22
22
|
globalThis.patternFromFilter = patternFromFilter;
|
|
23
23
|
}
|
|
24
|
-
import { prettyCode } from "./pretty.mjs";
|
|
24
|
+
import { prettyCode, prettyFilter } from "./pretty.mjs";
|
|
25
25
|
import {
|
|
26
26
|
compileObject,
|
|
27
27
|
compileLibrary,
|
|
@@ -32,7 +32,11 @@ import {
|
|
|
32
32
|
loadLibrary,
|
|
33
33
|
prettyRelation
|
|
34
34
|
} from "./object.mjs";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
propertyListToFilter,
|
|
37
|
+
valueToK,
|
|
38
|
+
valueWithEnvelopeToK
|
|
39
|
+
} from "./codecs/runtime/show-value.mjs";
|
|
36
40
|
import {
|
|
37
41
|
codecNames,
|
|
38
42
|
codeHashToPattern,
|
|
@@ -48,7 +52,7 @@ import {
|
|
|
48
52
|
|
|
49
53
|
const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
50
54
|
const COMMAND_NAMES = [
|
|
51
|
-
"help", "rel",
|
|
55
|
+
"help", "rel", "pattern", "pat", "patterns", "pats",
|
|
52
56
|
"codecs", "rels", "codec", "input", "reset", "klib", "ko", "load",
|
|
53
57
|
"engine", "wasm", "pkir", "js", "limit",
|
|
54
58
|
"quit", "exit"
|
|
@@ -87,6 +91,7 @@ function createState(options = {}) {
|
|
|
87
91
|
rels: {},
|
|
88
92
|
relAliases: {},
|
|
89
93
|
typeAliases: {},
|
|
94
|
+
patterns: {},
|
|
90
95
|
codecs: {},
|
|
91
96
|
loadedFiles: new Set(),
|
|
92
97
|
pendingInput: null,
|
|
@@ -106,6 +111,7 @@ function stateLibrary(state) {
|
|
|
106
111
|
codes: state.codes,
|
|
107
112
|
rels: state.rels,
|
|
108
113
|
relAlias: state.relAliases,
|
|
114
|
+
patterns: state.patterns,
|
|
109
115
|
meta: state.meta,
|
|
110
116
|
main: null
|
|
111
117
|
};
|
|
@@ -227,6 +233,11 @@ function mergeLibrary(state, lib, source = "<load>", options = {}) {
|
|
|
227
233
|
const loadAliases = options.loadAliases ?? true;
|
|
228
234
|
state.codes = { ...state.codes, ...(lib.codes || {}) };
|
|
229
235
|
state.rels = { ...state.rels, ...(lib.rels || {}) };
|
|
236
|
+
state.patterns = {
|
|
237
|
+
...state.patterns,
|
|
238
|
+
...(lib.patterns || lib.sourceDefs?.patterns ||
|
|
239
|
+
lib.defs?.patterns || {})
|
|
240
|
+
};
|
|
230
241
|
mergeMeta(state, lib.meta);
|
|
231
242
|
|
|
232
243
|
if (loadAliases) {
|
|
@@ -310,6 +321,7 @@ function savedLibrary(state) {
|
|
|
310
321
|
rels,
|
|
311
322
|
relAlias: state.relAliases,
|
|
312
323
|
compileStats: { sccs: [], sccCount: 0 },
|
|
324
|
+
patterns: state.patterns || {},
|
|
313
325
|
meta: Object.fromEntries(
|
|
314
326
|
Object.entries(meta).filter(([hash]) => storedHashes.has(hash))
|
|
315
327
|
),
|
|
@@ -320,7 +332,10 @@ function savedLibrary(state) {
|
|
|
320
332
|
function executableObject(state, mainExpression) {
|
|
321
333
|
const main = (mainExpression || state.lastMain || "").trim();
|
|
322
334
|
if (!main) {
|
|
323
|
-
throw new Error(
|
|
335
|
+
throw new Error(
|
|
336
|
+
":ko file expr requires a main expression unless one has " +
|
|
337
|
+
"already been evaluated or defined"
|
|
338
|
+
);
|
|
324
339
|
}
|
|
325
340
|
|
|
326
341
|
restoreCodes(state);
|
|
@@ -328,7 +343,8 @@ function executableObject(state, mainExpression) {
|
|
|
328
343
|
try {
|
|
329
344
|
return compileObject([preamble, main].filter(Boolean).join("\n"), {
|
|
330
345
|
source: "<repl>",
|
|
331
|
-
libraries: [stateLibrary(state)]
|
|
346
|
+
libraries: [stateLibrary(state)],
|
|
347
|
+
patterns: state.patterns
|
|
332
348
|
});
|
|
333
349
|
} catch (error) {
|
|
334
350
|
throw remapError(error, preambleLineCount(preamble));
|
|
@@ -343,11 +359,72 @@ function resolveRel(state, name) {
|
|
|
343
359
|
}
|
|
344
360
|
|
|
345
361
|
function listAliases(aliases) {
|
|
346
|
-
const entries = Object.entries(aliases).sort(([a], [b]) =>
|
|
362
|
+
const entries = Object.entries(aliases).sort(([a], [b]) =>
|
|
363
|
+
a.localeCompare(b)
|
|
364
|
+
);
|
|
347
365
|
if (entries.length === 0) return "(none)";
|
|
348
366
|
return entries.map(([name, hash]) => `${name} = ${hash}`).join("\n");
|
|
349
367
|
}
|
|
350
368
|
|
|
369
|
+
function resolvePatternCanonicalName(state, name, pat) {
|
|
370
|
+
if (!state || !pat) return null;
|
|
371
|
+
if (pat._canonical) return pat._canonical;
|
|
372
|
+
const params = pat.params || [];
|
|
373
|
+
const source = `? ${name}[${params.join(", ")}]`;
|
|
374
|
+
try {
|
|
375
|
+
restoreCodes(state);
|
|
376
|
+
const annotated = annotate(source, {
|
|
377
|
+
libraries: [stateLibrary(state)],
|
|
378
|
+
patterns: state.patterns
|
|
379
|
+
});
|
|
380
|
+
const mainRel = annotated.rels.__main__;
|
|
381
|
+
if (
|
|
382
|
+
mainRel?.typePatternGraph &&
|
|
383
|
+
mainRel?.def?.patterns?.[0] != null
|
|
384
|
+
) {
|
|
385
|
+
const rootId = mainRel.typePatternGraph.find(
|
|
386
|
+
mainRel.def.patterns[0]
|
|
387
|
+
);
|
|
388
|
+
const propList = patternToPropertyList(
|
|
389
|
+
exportPatternGraph(mainRel.typePatternGraph, rootId)
|
|
390
|
+
);
|
|
391
|
+
const codeHash = closedPatternToCodeHash(propList);
|
|
392
|
+
if (codeHash) {
|
|
393
|
+
state.codes = codes.dump();
|
|
394
|
+
pat._canonical = codeHash;
|
|
395
|
+
return codeHash;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const relHash = annotated.relAlias?.__main__ || null;
|
|
399
|
+
if (relHash) {
|
|
400
|
+
pat._canonical = relHash;
|
|
401
|
+
return relHash;
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
restoreCodes(state);
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function prettyPattern(name, pat, state = null) {
|
|
411
|
+
const params = (pat?.params || []).join(", ");
|
|
412
|
+
const filterStr = pat?.filter ? prettyFilter(pat.filter) : "()";
|
|
413
|
+
const canonical = resolvePatternCanonicalName(state, name, pat);
|
|
414
|
+
const suffix = canonical ? ` -- ${canonical}` : "";
|
|
415
|
+
return `${name}[${params}] = ${filterStr};${suffix}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function listPatterns(patterns) {
|
|
419
|
+
const entries = Object.entries(patterns || {}).sort(([a], [b]) =>
|
|
420
|
+
a.localeCompare(b)
|
|
421
|
+
);
|
|
422
|
+
if (entries.length === 0) return "(none)";
|
|
423
|
+
return entries
|
|
424
|
+
.map(([name, pat]) => `${name}[${(pat?.params || []).join(", ")}]`)
|
|
425
|
+
.join("\n");
|
|
426
|
+
}
|
|
427
|
+
|
|
351
428
|
function formatCodecOutput(output) {
|
|
352
429
|
if (typeof Buffer !== "undefined" && Buffer.isBuffer(output)) return output.toString("utf8");
|
|
353
430
|
if (typeof Uint8Array !== "undefined" && output instanceof Uint8Array) return new TextDecoder().decode(output);
|
|
@@ -644,9 +721,16 @@ function resolveTypeExpressionHash(state, rawType) {
|
|
|
644
721
|
].filter(Boolean).join("\n");
|
|
645
722
|
|
|
646
723
|
try {
|
|
647
|
-
const annotated = annotate(source, {
|
|
724
|
+
const annotated = annotate(source, {
|
|
725
|
+
libraries: [stateLibrary(state)],
|
|
726
|
+
patterns: state.patterns
|
|
727
|
+
});
|
|
648
728
|
const hash = annotated.representatives[INPUT_TYPE_NAME];
|
|
649
|
-
if (!hash)
|
|
729
|
+
if (!hash) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
":input type expression did not produce a code hash"
|
|
732
|
+
);
|
|
733
|
+
}
|
|
650
734
|
state.codes = codes.dump();
|
|
651
735
|
return hash;
|
|
652
736
|
} catch (error) {
|
|
@@ -672,7 +756,10 @@ function resolvePatternExpression(state, patternExpr) {
|
|
|
672
756
|
patternExpr
|
|
673
757
|
].filter(Boolean).join("\n");
|
|
674
758
|
try {
|
|
675
|
-
const annotated = annotate(source, {
|
|
759
|
+
const annotated = annotate(source, {
|
|
760
|
+
libraries: [stateLibrary(state)],
|
|
761
|
+
patterns: state.patterns
|
|
762
|
+
});
|
|
676
763
|
const mainRel = annotated.rels.__main__;
|
|
677
764
|
if (!mainRel || !mainRel.typePatternGraph) {
|
|
678
765
|
throw new Error(`Invalid pattern expression '${patternExpr}'`);
|
|
@@ -771,6 +858,25 @@ function completeCommandArgument(line, state) {
|
|
|
771
858
|
const argStart = 1 + firstSpace + 1;
|
|
772
859
|
const arg = line.slice(argStart);
|
|
773
860
|
|
|
861
|
+
if (command === "pattern" || command === "pat") {
|
|
862
|
+
const trimmed = arg.trimStart();
|
|
863
|
+
const leadingWhitespace = arg.length - trimmed.length;
|
|
864
|
+
const prefix = line.slice(0, argStart + leadingWhitespace);
|
|
865
|
+
const names = Object.keys(state.patterns || {}).sort();
|
|
866
|
+
const matches = names
|
|
867
|
+
.filter((name) => name.startsWith(trimmed))
|
|
868
|
+
.map((name) => `${prefix}${name}`);
|
|
869
|
+
if (trimmed.startsWith("@")) {
|
|
870
|
+
for (const [name, p] of Object.entries(state.patterns || {})) {
|
|
871
|
+
const hash = resolvePatternCanonicalName(state, name, p);
|
|
872
|
+
if (hash && hash.startsWith(trimmed)) {
|
|
873
|
+
matches.push(`${prefix}${hash}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return [matches, line];
|
|
878
|
+
}
|
|
879
|
+
|
|
774
880
|
if (command === "codec" || command === "codecs") {
|
|
775
881
|
return completeCodecCommand(line, argStart, arg, state);
|
|
776
882
|
}
|
|
@@ -877,7 +983,10 @@ function resolveKFilePath(filePath) {
|
|
|
877
983
|
if (fs.existsSync(strippedEx)) return strippedEx;
|
|
878
984
|
} catch {}
|
|
879
985
|
try {
|
|
880
|
-
const fromRepoStripped = path.resolve(
|
|
986
|
+
const fromRepoStripped = path.resolve(
|
|
987
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
988
|
+
strippedEx
|
|
989
|
+
);
|
|
881
990
|
if (fs.existsSync(fromRepoStripped)) return fromRepoStripped;
|
|
882
991
|
} catch {}
|
|
883
992
|
return filePath;
|
|
@@ -908,7 +1017,8 @@ function loadSourceOrKlib(state, targetPath, options = {}) {
|
|
|
908
1017
|
const fullSource = [preamble, source].filter(Boolean).join("\n");
|
|
909
1018
|
const compileOptions = {
|
|
910
1019
|
source: targetPath,
|
|
911
|
-
libraries: [stateLibrary(state)]
|
|
1020
|
+
libraries: [stateLibrary(state)],
|
|
1021
|
+
patterns: state.patterns
|
|
912
1022
|
};
|
|
913
1023
|
try {
|
|
914
1024
|
const lib = compileWithOptionalIdentity(fullSource, compileOptions);
|
|
@@ -972,15 +1082,29 @@ function compileSnippetArtifacts(source, state, sourceName = "<repl>") {
|
|
|
972
1082
|
const fullSource = [preamble, source].filter(Boolean).join("\n");
|
|
973
1083
|
const options = {
|
|
974
1084
|
source: sourceName,
|
|
975
|
-
libraries: [stateLibrary(state)]
|
|
1085
|
+
libraries: [stateLibrary(state)],
|
|
1086
|
+
patterns: state.patterns
|
|
976
1087
|
};
|
|
977
1088
|
try {
|
|
978
1089
|
const lib = compileWithOptionalIdentity(fullSource, options);
|
|
979
|
-
const { relAlias, meta } = libraryOriginsFromSource(
|
|
1090
|
+
const { relAlias, meta } = libraryOriginsFromSource(
|
|
1091
|
+
source,
|
|
1092
|
+
fullSource,
|
|
1093
|
+
lib,
|
|
1094
|
+
options
|
|
1095
|
+
);
|
|
980
1096
|
const annotated = annotateWithOptionalIdentity(fullSource, options);
|
|
981
1097
|
return {
|
|
982
1098
|
annotated,
|
|
983
|
-
lib: {
|
|
1099
|
+
lib: {
|
|
1100
|
+
...lib,
|
|
1101
|
+
relAlias,
|
|
1102
|
+
meta,
|
|
1103
|
+
patterns: {
|
|
1104
|
+
...(state.patterns || {}),
|
|
1105
|
+
...(annotated.sourceDefs?.patterns || {})
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
984
1108
|
lineOffset: preambleLineCount(preamble)
|
|
985
1109
|
};
|
|
986
1110
|
} catch (error) {
|
|
@@ -1233,14 +1357,49 @@ async function evaluateCommand(line, state) {
|
|
|
1233
1357
|
case "rel": {
|
|
1234
1358
|
if (!arg) throw new Error(`${usagePrefix}rel requires a relation name`);
|
|
1235
1359
|
if (arg.includes("=")) {
|
|
1236
|
-
throw new Error(
|
|
1360
|
+
throw new Error(
|
|
1361
|
+
"Relation definitions use syntax: name = relExpr; " +
|
|
1362
|
+
"Use :rel <name> to show a relation definition"
|
|
1363
|
+
);
|
|
1237
1364
|
}
|
|
1238
1365
|
const { hash, rel } = resolveRel(state, arg);
|
|
1239
1366
|
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
1240
|
-
|
|
1367
|
+
const prettyRelStr = prettyRelation(
|
|
1368
|
+
rel,
|
|
1369
|
+
displayAliases(state),
|
|
1370
|
+
state.relAliases
|
|
1371
|
+
);
|
|
1372
|
+
return [`${arg} = ${prettyRelStr}; -- ${hash}`];
|
|
1241
1373
|
}
|
|
1242
1374
|
case "rels":
|
|
1243
1375
|
return [listAliases(state.relAliases)];
|
|
1376
|
+
case "pattern":
|
|
1377
|
+
case "pat": {
|
|
1378
|
+
if (!arg) {
|
|
1379
|
+
throw new Error(`${usagePrefix}pattern requires a pattern name`);
|
|
1380
|
+
}
|
|
1381
|
+
const rawName = arg.trim();
|
|
1382
|
+
let patternName = rawName.replace(/\[.*\]$/, "").trim();
|
|
1383
|
+
let pat = state.patterns?.[patternName];
|
|
1384
|
+
if (!pat && patternName.startsWith("@")) {
|
|
1385
|
+
for (const [name, p] of Object.entries(state.patterns || {})) {
|
|
1386
|
+
if (
|
|
1387
|
+
resolvePatternCanonicalName(state, name, p) === patternName
|
|
1388
|
+
) {
|
|
1389
|
+
patternName = name;
|
|
1390
|
+
pat = p;
|
|
1391
|
+
break;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
if (!pat) {
|
|
1396
|
+
throw new Error(`Unknown pattern '${rawName}'`);
|
|
1397
|
+
}
|
|
1398
|
+
return [prettyPattern(patternName, pat, state)];
|
|
1399
|
+
}
|
|
1400
|
+
case "patterns":
|
|
1401
|
+
case "pats":
|
|
1402
|
+
return [listPatterns(state.patterns)];
|
|
1244
1403
|
case "codec":
|
|
1245
1404
|
case "codecs":
|
|
1246
1405
|
return loadCodec(arg, state);
|
|
@@ -1333,19 +1492,23 @@ function helpText() {
|
|
|
1333
1492
|
":engine [wasm|pkir|js] display or switch evaluation engine",
|
|
1334
1493
|
":rel name show relation definition",
|
|
1335
1494
|
":rels list relation aliases",
|
|
1495
|
+
":pattern name show named pattern definition (alias :pat)",
|
|
1496
|
+
":patterns list defined named patterns (alias :pats)",
|
|
1336
1497
|
":limit [num|none] set maximum characters for value output",
|
|
1337
|
-
":codec load file load a codec module from file
|
|
1498
|
+
":codec load file load a codec module from file",
|
|
1338
1499
|
":codec unload name unload a registered codec",
|
|
1339
1500
|
":codec list list loaded codecs (or simply :codecs)",
|
|
1340
|
-
":input <codec
|
|
1341
|
-
":load [--no-alias]
|
|
1342
|
-
" load .k source or .klib",
|
|
1501
|
+
":input <codec> [txt] parse input using specified codec",
|
|
1502
|
+
":load [--no-alias] f load .k source or .klib",
|
|
1343
1503
|
":klib file export state as a library",
|
|
1344
1504
|
":ko file expr export executable .ko using expr as main",
|
|
1345
1505
|
":reset clear state",
|
|
1346
1506
|
":help show this help",
|
|
1347
1507
|
"",
|
|
1348
|
-
"Raw k input
|
|
1508
|
+
"Raw k input:",
|
|
1509
|
+
" name = expr; define relation alias",
|
|
1510
|
+
" name[P1, ...] = filter; define named pattern template",
|
|
1511
|
+
" ? name[...] evaluate named pattern filter",
|
|
1349
1512
|
"A line ending with ';' plus only spaces closes the snippet.",
|
|
1350
1513
|
"Use '\\' to force continuation."
|
|
1351
1514
|
].join("\n");
|
|
@@ -1553,5 +1716,8 @@ export {
|
|
|
1553
1716
|
formatDuration,
|
|
1554
1717
|
codecNames,
|
|
1555
1718
|
ensureEnveloped,
|
|
1556
|
-
patternFromFilter
|
|
1719
|
+
patternFromFilter,
|
|
1720
|
+
prettyPattern,
|
|
1721
|
+
resolvePatternCanonicalName,
|
|
1722
|
+
listPatterns
|
|
1557
1723
|
};
|
|
@@ -78,8 +78,14 @@ if (fs.existsSync(coreKPath)) {
|
|
|
78
78
|
vfsFiles["core.k"] = fs.readFileSync(coreKPath, "utf8");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Add verified k-code files
|
|
82
|
-
const verifiedExamples = [
|
|
81
|
+
// Add verified k-code files
|
|
82
|
+
const verifiedExamples = [
|
|
83
|
+
"arithmetics.k",
|
|
84
|
+
"ieee.k",
|
|
85
|
+
"poly.k",
|
|
86
|
+
"karatsuba-mult.k",
|
|
87
|
+
"djson.k"
|
|
88
|
+
];
|
|
83
89
|
const examplesDir = path.join(projectRoot, "Examples");
|
|
84
90
|
for (const filename of verifiedExamples) {
|
|
85
91
|
const fullPath = path.join(examplesDir, filename);
|
|
@@ -645,6 +651,12 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
645
651
|
text-overflow: ellipsis;
|
|
646
652
|
white-space: nowrap;
|
|
647
653
|
flex: 1;
|
|
654
|
+
min-width: 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.k-btn-copy {
|
|
658
|
+
flex-shrink: 0;
|
|
659
|
+
margin-left: auto;
|
|
648
660
|
}
|
|
649
661
|
|
|
650
662
|
.k-tree-section-body {
|
|
@@ -1681,6 +1693,7 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1681
1693
|
<option value="ieee.k">ieee.k</option>
|
|
1682
1694
|
<option value="poly.k">poly.k</option>
|
|
1683
1695
|
<option value="karatsuba-mult.k">karatsuba-mult.k</option>
|
|
1696
|
+
<option value="djson.k">djson.k</option>
|
|
1684
1697
|
</optgroup>
|
|
1685
1698
|
<optgroup label="Quick Expressions">
|
|
1686
1699
|
<option value="expr:{} | ok">{} | ok</option>
|
|
@@ -1700,9 +1713,11 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1700
1713
|
<option value="expr::codec load int.mjs">:codec load int.mjs</option>
|
|
1701
1714
|
<option value="expr::codec load utf8.mjs">:codec load utf8.mjs</option>
|
|
1702
1715
|
<option value="expr::codec load json.mjs">:codec load json.mjs</option>
|
|
1716
|
+
<option value="expr::codec load djson.mjs">:codec load djson.mjs</option>
|
|
1703
1717
|
<option value="expr::codec load ieee.mjs">:codec load ieee.mjs</option>
|
|
1704
1718
|
<option value="expr::codec load unit.mjs">:codec load unit.mjs</option>
|
|
1705
1719
|
<option value="expr::input json.mjs {"a":12,"b":"hello"}">:input json.mjs {"a":12,"b":"hello"}</option>
|
|
1720
|
+
<option value="expr::input djson.mjs {"a":12,"b":"hello"}">:input djson.mjs {"a":12,"b":"hello"}</option>
|
|
1706
1721
|
<option value="expr::input int.mjs [0,1,2]">:input int.mjs [0,1,2]</option>
|
|
1707
1722
|
</optgroup>
|
|
1708
1723
|
</select>
|
|
@@ -1837,6 +1852,16 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1837
1852
|
<td><code>name = relExpr;</code></td>
|
|
1838
1853
|
<td><b>Relation Alias:</b> Defines a local alias for a relation expression.<br><i>e.g.</i> <code>swap = { . y x, . x y };</code></td>
|
|
1839
1854
|
</tr>
|
|
1855
|
+
<tr>
|
|
1856
|
+
<td><code>name[P1, ..., Pn] = filter;</code></td>
|
|
1857
|
+
<td>
|
|
1858
|
+
<b>Named Pattern Template:</b> Defines a reusable
|
|
1859
|
+
pattern template.<br>
|
|
1860
|
+
<i>e.g.</i><br>
|
|
1861
|
+
<code>bit[] = < {} 0, {} 1 >;</code> or<br>
|
|
1862
|
+
<code>list[X] = < {} nil, ... > = Y;</code>
|
|
1863
|
+
</td>
|
|
1864
|
+
</tr>
|
|
1840
1865
|
</tbody>
|
|
1841
1866
|
</table>
|
|
1842
1867
|
|
|
@@ -1872,8 +1897,15 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1872
1897
|
<td><b>Variant Constructor:</b> Wraps document into variant <code>tag</code></td>
|
|
1873
1898
|
</tr>
|
|
1874
1899
|
<tr>
|
|
1875
|
-
<td
|
|
1876
|
-
|
|
1900
|
+
<td>
|
|
1901
|
+
<code>? filterExpr</code> /
|
|
1902
|
+
<code>? name[...]</code>
|
|
1903
|
+
</td>
|
|
1904
|
+
<td>
|
|
1905
|
+
<b>Pattern Filter:</b> Asserts input matches pattern;
|
|
1906
|
+
acts as partial identity and inductive witness
|
|
1907
|
+
(<i>e.g.</i> <code>? list[X]</code>)
|
|
1908
|
+
</td>
|
|
1877
1909
|
</tr>
|
|
1878
1910
|
<tr>
|
|
1879
1911
|
<td><code>name</code> / <code>@hash</code></td>
|
|
@@ -1888,6 +1920,20 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1888
1920
|
<table class="help-table">
|
|
1889
1921
|
<tr><td><code>:engine [wasm|pkir|js]</code></td><td>Display or switch evaluation engine (<code>wasm</code>, <code>pkir</code>, or <code>js</code>)</td></tr>
|
|
1890
1922
|
<tr><td><code>:load <file></code></td><td>Load a <code>.k</code> source or <code>.klib</code> library into current state</td></tr>
|
|
1923
|
+
<tr>
|
|
1924
|
+
<td><code>:pattern <name></code></td>
|
|
1925
|
+
<td>
|
|
1926
|
+
Display definition of named pattern
|
|
1927
|
+
<code><name></code> (alias <code>:pat</code>)
|
|
1928
|
+
</td>
|
|
1929
|
+
</tr>
|
|
1930
|
+
<tr>
|
|
1931
|
+
<td><code>:patterns</code></td>
|
|
1932
|
+
<td>
|
|
1933
|
+
List defined named pattern templates
|
|
1934
|
+
(alias <code>:pats</code>)
|
|
1935
|
+
</td>
|
|
1936
|
+
</tr>
|
|
1891
1937
|
<tr><td><code>:rel <name></code></td><td>Display definition of relation <code><name></code></td></tr>
|
|
1892
1938
|
<tr><td><code>:rels</code></td><td>List all relation aliases</td></tr>
|
|
1893
1939
|
<tr><td><code>:codecs</code></td><td>List all loaded format codecs</td></tr>
|
|
@@ -1908,6 +1954,14 @@ const htmlContent = `<!DOCTYPE html>
|
|
|
1908
1954
|
<li><code>not = < / true | false, / false | true >;</code> — Polymorphic negation via branch projections and ordered choice</li>
|
|
1909
1955
|
<li><code>{} | true not</code> — Compose constant relation <code>{} | true</code> with <code>not</code> (evaluates to <code>{} | false</code>)</li>
|
|
1910
1956
|
<li><code>bool? = ?< {} true, {} false >;</code> — Define a boolean pattern filter relation</li>
|
|
1957
|
+
<li>
|
|
1958
|
+
<code>bit[] = < {} 0, {} 1 >;</code>
|
|
1959
|
+
— Define a 0-arity bit pattern
|
|
1960
|
+
</li>
|
|
1961
|
+
<li>
|
|
1962
|
+
<code>? list[bool[]]</code>
|
|
1963
|
+
— Instantiated pattern filter matching booleans
|
|
1964
|
+
</li>
|
|
1911
1965
|
<li><code>not = bool? ?X < / true | false, {} | true > ?X;</code> — Filtered boolean negation: guarded by <code>bool?</code>, defaulting to <code>{} | true</code></li>
|
|
1912
1966
|
<li><code>swap = { . y x, . x y };</code> — Define a product field-swapping relation</li>
|
|
1913
1967
|
<li><code>{ {} | ok x, {} | nil y } swap</code> — Construct a product and pass it through <code>swap</code></li>
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -32,12 +32,22 @@ 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"]],
|
|
38
43
|
["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
|
|
39
44
|
["tests/test-structural-values.mjs", node, ["tests/test-structural-values.mjs"]],
|
|
40
45
|
["tests/test-codecs.mjs", node, ["tests/test-codecs.mjs"]],
|
|
46
|
+
[
|
|
47
|
+
"tests/test-djson-methods.mjs",
|
|
48
|
+
node,
|
|
49
|
+
["tests/test-djson-methods.mjs"]
|
|
50
|
+
],
|
|
41
51
|
["tests/test-deep-wire-value.mjs", node, ["tests/test-deep-wire-value.mjs"]],
|
|
42
52
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
43
53
|
["tests/test-kir.mjs", node, ["tests/test-kir.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
|
}
|
package/tests/test-codecs.mjs
CHANGED
|
@@ -314,11 +314,11 @@ console.log("8. Verifying fast string decoding & bounded rendering...");
|
|
|
314
314
|
);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
// 9. DJSON Singleton Pattern
|
|
318
|
-
console.log("9. Verifying DJSON singleton pattern
|
|
317
|
+
// 9. DJSON Singleton Pattern Invariants & Round-Trip
|
|
318
|
+
console.log("9. Verifying DJSON singleton pattern invariants...");
|
|
319
319
|
{
|
|
320
320
|
assert(Array.isArray(DjsonCodec.DJSON_PATTERN));
|
|
321
|
-
assert.equal(DjsonCodec.DJSON_PATTERN.length,
|
|
321
|
+
assert.equal(DjsonCodec.DJSON_PATTERN.length, 27);
|
|
322
322
|
|
|
323
323
|
const samples = [
|
|
324
324
|
null,
|
|
@@ -332,21 +332,34 @@ console.log("9. Verifying DJSON singleton pattern invariant...");
|
|
|
332
332
|
{ key: "value", nested: [1, 2, { deep: true }] }
|
|
333
333
|
];
|
|
334
334
|
|
|
335
|
-
for (const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
335
|
+
for (const [label, codec, pat, relPath] of [
|
|
336
|
+
["djson", DjsonCodec, DjsonCodec.DJSON_PATTERN, "codecs/djson.mjs"]
|
|
337
|
+
]) {
|
|
338
|
+
for (const sample of samples) {
|
|
339
|
+
const jsonStr = JSON.stringify(sample);
|
|
340
|
+
const enveloped = codec.parse(jsonStr);
|
|
341
|
+
assert.strictEqual(
|
|
342
|
+
enveloped.pattern,
|
|
343
|
+
pat,
|
|
344
|
+
`Every ${label} value must share the exact singleton pattern`
|
|
345
|
+
);
|
|
346
|
+
const wire = SDK.encodeToWire(enveloped, enveloped.pattern);
|
|
347
|
+
const decoded = SDK.decodeWire(wire);
|
|
348
|
+
const restored = codec.print(decoded.value);
|
|
349
|
+
assert.deepEqual(
|
|
350
|
+
JSON.parse(restored),
|
|
351
|
+
sample,
|
|
352
|
+
`${label} round-trip mismatch for: ${jsonStr}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const cliSample = JSON.stringify({ a: 1, b: [true, "ok"] });
|
|
356
|
+
const parseRes = runCLI(relPath, ["--parse"], cliSample);
|
|
357
|
+
assert.equal(parseRes.status, 0);
|
|
358
|
+
const printRes = runCLI(relPath, ["--print"], parseRes.stdout);
|
|
359
|
+
assert.equal(printRes.status, 0);
|
|
346
360
|
assert.deepEqual(
|
|
347
|
-
JSON.parse(
|
|
348
|
-
|
|
349
|
-
`DJSON round-trip mismatch for: ${jsonStr}`
|
|
361
|
+
JSON.parse(printRes.stdout.toString("utf8")),
|
|
362
|
+
JSON.parse(cliSample)
|
|
350
363
|
);
|
|
351
364
|
}
|
|
352
365
|
}
|