@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/DOCS/BIG_PICTURE.md +54 -7
  3. package/DOCS/CODECS.md +47 -2
  4. package/DOCS/CODEC_ARCHITECTURE.md +14 -5
  5. package/DOCS/DICTIONARY.md +13 -4
  6. package/DOCS/FILE_FORMATS.md +3 -2
  7. package/DOCS/JSON_APPROACHES_COMPARISON.md +306 -0
  8. package/DOCS/NAMED_PATTERNS.md +317 -0
  9. package/DOCS/PATTERNS.md +59 -11
  10. package/DOCS/REPL.md +24 -5
  11. package/DOCS/WEB_REPL_REQUIREMENTS.md +4 -2
  12. package/Examples/arithmetics.k +22 -17
  13. package/Examples/bnat.k +21 -11
  14. package/Examples/buda.k +37 -20
  15. package/Examples/byte.k +3 -1
  16. package/Examples/code.k +38 -39
  17. package/Examples/djson-btrie.k +414 -0
  18. package/Examples/djson-ptrie.k +663 -0
  19. package/Examples/djson.k +323 -0
  20. package/Examples/esc.k +1 -1
  21. package/Examples/karatsuba-mult.k +16 -13
  22. package/Examples/list.k +8 -4
  23. package/Examples/nat.k +17 -11
  24. package/Examples/poly.k +11 -3
  25. package/Examples/vhdl_to_k.k +43 -31
  26. package/README.md +42 -15
  27. package/Value.mjs +28 -3
  28. package/backend-api.mjs +5 -1
  29. package/browser/repl-browser.mjs +6 -0
  30. package/browser/tree-view.mjs +82 -24
  31. package/browser/vfs-data.mjs +21 -12
  32. package/codecs/README.md +3 -0
  33. package/codecs/djson-btrie.mjs +560 -0
  34. package/codecs/djson-ptrie.mjs +587 -0
  35. package/codecs/djson.mjs +270 -0
  36. package/codecs/int.mjs +2 -1
  37. package/codecs/json.mjs +7 -2
  38. package/codecs/k-parse.mjs +7 -1
  39. package/codecs/k-print.mjs +4 -0
  40. package/codecs/runtime/codec-sdk.mjs +16 -2
  41. package/codecs/runtime/pattern-json.mjs +80 -1
  42. package/codecs/runtime/prefix-codec.mjs +629 -67
  43. package/codecs/runtime/show-value.mjs +93 -51
  44. package/codecs/runtime/unicode-string.mjs +29 -6
  45. package/codecs/show.mjs +4 -0
  46. package/codecs/utf16.mjs +2 -1
  47. package/core.k +79 -115
  48. package/index.mjs +3 -0
  49. package/named-patterns.mjs +267 -0
  50. package/object.mjs +31 -3
  51. package/package.json +5 -1
  52. package/parser.jison +68 -5
  53. package/parser.mjs +111 -60
  54. package/pretty.mjs +5 -0
  55. package/repl.mjs +245 -28
  56. package/scripts/build-repl-html.mjs +76 -5
  57. package/scripts/perf-djson.mjs +1142 -0
  58. package/scripts/run-tests.mjs +5 -0
  59. package/symbol-table.mjs +20 -2
  60. package/tests/bench-djson-tries.mjs +340 -0
  61. package/tests/test-codecs.mjs +211 -0
  62. package/tests/test-named-patterns.mjs +189 -0
  63. package/tests/test-repl-html.mjs +52 -4
  64. package/tests/test-repl.mjs +223 -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 { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
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,9 +52,9 @@ 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
- "engine", "wasm", "pkir", "js",
57
+ "engine", "wasm", "pkir", "js", "limit",
54
58
  "quit", "exit"
55
59
  ];
56
60
  const CODEC_COMMAND_NAMES = ["load", "unload", "list"];
@@ -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,
@@ -95,7 +100,8 @@ function createState(options = {}) {
95
100
  lastResult: null,
96
101
  lastMain: null,
97
102
  lastTiming: null,
98
- showTiming: true
103
+ showTiming: true,
104
+ printLimit: options.printLimit ?? Infinity
99
105
  };
100
106
  }
101
107
 
@@ -105,6 +111,7 @@ function stateLibrary(state) {
105
111
  codes: state.codes,
106
112
  rels: state.rels,
107
113
  relAlias: state.relAliases,
114
+ patterns: state.patterns,
108
115
  meta: state.meta,
109
116
  main: null
110
117
  };
@@ -226,6 +233,11 @@ function mergeLibrary(state, lib, source = "<load>", options = {}) {
226
233
  const loadAliases = options.loadAliases ?? true;
227
234
  state.codes = { ...state.codes, ...(lib.codes || {}) };
228
235
  state.rels = { ...state.rels, ...(lib.rels || {}) };
236
+ state.patterns = {
237
+ ...state.patterns,
238
+ ...(lib.patterns || lib.sourceDefs?.patterns ||
239
+ lib.defs?.patterns || {})
240
+ };
229
241
  mergeMeta(state, lib.meta);
230
242
 
231
243
  if (loadAliases) {
@@ -309,6 +321,7 @@ function savedLibrary(state) {
309
321
  rels,
310
322
  relAlias: state.relAliases,
311
323
  compileStats: { sccs: [], sccCount: 0 },
324
+ patterns: state.patterns || {},
312
325
  meta: Object.fromEntries(
313
326
  Object.entries(meta).filter(([hash]) => storedHashes.has(hash))
314
327
  ),
@@ -319,7 +332,10 @@ function savedLibrary(state) {
319
332
  function executableObject(state, mainExpression) {
320
333
  const main = (mainExpression || state.lastMain || "").trim();
321
334
  if (!main) {
322
- throw new Error(":ko file expr requires a main expression unless one has already been evaluated or defined");
335
+ throw new Error(
336
+ ":ko file expr requires a main expression unless one has " +
337
+ "already been evaluated or defined"
338
+ );
323
339
  }
324
340
 
325
341
  restoreCodes(state);
@@ -327,7 +343,8 @@ function executableObject(state, mainExpression) {
327
343
  try {
328
344
  return compileObject([preamble, main].filter(Boolean).join("\n"), {
329
345
  source: "<repl>",
330
- libraries: [stateLibrary(state)]
346
+ libraries: [stateLibrary(state)],
347
+ patterns: state.patterns
331
348
  });
332
349
  } catch (error) {
333
350
  throw remapError(error, preambleLineCount(preamble));
@@ -342,11 +359,72 @@ function resolveRel(state, name) {
342
359
  }
343
360
 
344
361
  function listAliases(aliases) {
345
- const entries = Object.entries(aliases).sort(([a], [b]) => a.localeCompare(b));
362
+ const entries = Object.entries(aliases).sort(([a], [b]) =>
363
+ a.localeCompare(b)
364
+ );
346
365
  if (entries.length === 0) return "(none)";
347
366
  return entries.map(([name, hash]) => `${name} = ${hash}`).join("\n");
348
367
  }
349
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
+
350
428
  function formatCodecOutput(output) {
351
429
  if (typeof Buffer !== "undefined" && Buffer.isBuffer(output)) return output.toString("utf8");
352
430
  if (typeof Uint8Array !== "undefined" && output instanceof Uint8Array) return new TextDecoder().decode(output);
@@ -354,7 +432,8 @@ function formatCodecOutput(output) {
354
432
  }
355
433
 
356
434
  function printValue(value, state = null) {
357
- const lines = [valueWithEnvelopeToK(value)];
435
+ const limit = state?.printLimit ?? Infinity;
436
+ const lines = [valueWithEnvelopeToK(value, limit)];
358
437
  if (!state || value === undefined) return lines[0];
359
438
 
360
439
  const store = state.codecs || {};
@@ -363,7 +442,11 @@ function printValue(value, state = null) {
363
442
  try {
364
443
  const output = codec.print(value, { state });
365
444
  if (output != null) {
366
- lines.push(`${codec.name}: ${formatCodecOutput(output)}`);
445
+ let text = formatCodecOutput(output);
446
+ if (Number.isFinite(limit) && text.length > limit) {
447
+ text = text.slice(0, limit) + "...";
448
+ }
449
+ lines.push(`${codec.name}: ${text}`);
367
450
  }
368
451
  } catch {
369
452
  // Codec cannot format this value; silently skip
@@ -638,9 +721,16 @@ function resolveTypeExpressionHash(state, rawType) {
638
721
  ].filter(Boolean).join("\n");
639
722
 
640
723
  try {
641
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
724
+ const annotated = annotate(source, {
725
+ libraries: [stateLibrary(state)],
726
+ patterns: state.patterns
727
+ });
642
728
  const hash = annotated.representatives[INPUT_TYPE_NAME];
643
- if (!hash) throw new Error(":input type expression did not produce a code hash");
729
+ if (!hash) {
730
+ throw new Error(
731
+ ":input type expression did not produce a code hash"
732
+ );
733
+ }
644
734
  state.codes = codes.dump();
645
735
  return hash;
646
736
  } catch (error) {
@@ -666,7 +756,10 @@ function resolvePatternExpression(state, patternExpr) {
666
756
  patternExpr
667
757
  ].filter(Boolean).join("\n");
668
758
  try {
669
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
759
+ const annotated = annotate(source, {
760
+ libraries: [stateLibrary(state)],
761
+ patterns: state.patterns
762
+ });
670
763
  const mainRel = annotated.rels.__main__;
671
764
  if (!mainRel || !mainRel.typePatternGraph) {
672
765
  throw new Error(`Invalid pattern expression '${patternExpr}'`);
@@ -765,6 +858,25 @@ function completeCommandArgument(line, state) {
765
858
  const argStart = 1 + firstSpace + 1;
766
859
  const arg = line.slice(argStart);
767
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
+
768
880
  if (command === "codec" || command === "codecs") {
769
881
  return completeCodecCommand(line, argStart, arg, state);
770
882
  }
@@ -871,7 +983,10 @@ function resolveKFilePath(filePath) {
871
983
  if (fs.existsSync(strippedEx)) return strippedEx;
872
984
  } catch {}
873
985
  try {
874
- const fromRepoStripped = path.resolve(path.dirname(fileURLToPath(import.meta.url)), strippedEx);
986
+ const fromRepoStripped = path.resolve(
987
+ path.dirname(fileURLToPath(import.meta.url)),
988
+ strippedEx
989
+ );
875
990
  if (fs.existsSync(fromRepoStripped)) return fromRepoStripped;
876
991
  } catch {}
877
992
  return filePath;
@@ -902,7 +1017,8 @@ function loadSourceOrKlib(state, targetPath, options = {}) {
902
1017
  const fullSource = [preamble, source].filter(Boolean).join("\n");
903
1018
  const compileOptions = {
904
1019
  source: targetPath,
905
- libraries: [stateLibrary(state)]
1020
+ libraries: [stateLibrary(state)],
1021
+ patterns: state.patterns
906
1022
  };
907
1023
  try {
908
1024
  const lib = compileWithOptionalIdentity(fullSource, compileOptions);
@@ -966,15 +1082,29 @@ function compileSnippetArtifacts(source, state, sourceName = "<repl>") {
966
1082
  const fullSource = [preamble, source].filter(Boolean).join("\n");
967
1083
  const options = {
968
1084
  source: sourceName,
969
- libraries: [stateLibrary(state)]
1085
+ libraries: [stateLibrary(state)],
1086
+ patterns: state.patterns
970
1087
  };
971
1088
  try {
972
1089
  const lib = compileWithOptionalIdentity(fullSource, options);
973
- const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
1090
+ const { relAlias, meta } = libraryOriginsFromSource(
1091
+ source,
1092
+ fullSource,
1093
+ lib,
1094
+ options
1095
+ );
974
1096
  const annotated = annotateWithOptionalIdentity(fullSource, options);
975
1097
  return {
976
1098
  annotated,
977
- lib: { ...lib, relAlias, meta },
1099
+ lib: {
1100
+ ...lib,
1101
+ relAlias,
1102
+ meta,
1103
+ patterns: {
1104
+ ...(state.patterns || {}),
1105
+ ...(annotated.sourceDefs?.patterns || {})
1106
+ }
1107
+ },
978
1108
  lineOffset: preambleLineCount(preamble)
979
1109
  };
980
1110
  } catch (error) {
@@ -1227,14 +1357,49 @@ async function evaluateCommand(line, state) {
1227
1357
  case "rel": {
1228
1358
  if (!arg) throw new Error(`${usagePrefix}rel requires a relation name`);
1229
1359
  if (arg.includes("=")) {
1230
- throw new Error("Relation definitions use syntax: name = relExpr; Use :rel <name> to show a relation definition");
1360
+ throw new Error(
1361
+ "Relation definitions use syntax: name = relExpr; " +
1362
+ "Use :rel <name> to show a relation definition"
1363
+ );
1231
1364
  }
1232
1365
  const { hash, rel } = resolveRel(state, arg);
1233
1366
  if (!rel) throw new Error(`Unknown relation '${arg}'`);
1234
- return [`${arg} = ${prettyRelation(rel, displayAliases(state), state.relAliases)}; -- ${hash}`];
1367
+ const prettyRelStr = prettyRelation(
1368
+ rel,
1369
+ displayAliases(state),
1370
+ state.relAliases
1371
+ );
1372
+ return [`${arg} = ${prettyRelStr}; -- ${hash}`];
1235
1373
  }
1236
1374
  case "rels":
1237
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)];
1238
1403
  case "codec":
1239
1404
  case "codecs":
1240
1405
  return loadCodec(arg, state);
@@ -1265,9 +1430,34 @@ async function evaluateCommand(line, state) {
1265
1430
  state.engine = "js";
1266
1431
  return ["engine set to js"];
1267
1432
  }
1433
+ case "limit": {
1434
+ const mode = (arg || "").trim().toLowerCase();
1435
+ if (!mode) {
1436
+ const current = Number.isFinite(state.printLimit)
1437
+ ? state.printLimit
1438
+ : "none";
1439
+ return [`Current output limit: ${current}`];
1440
+ }
1441
+ if (mode === "none" || mode === "infinity" || mode === "off") {
1442
+ state.printLimit = Infinity;
1443
+ return ["output limit disabled"];
1444
+ }
1445
+ const n = parseInt(mode, 10);
1446
+ if (Number.isNaN(n) || n < 0) {
1447
+ throw new Error(
1448
+ `Invalid limit '${arg.trim()}'. Usage: :limit [num|none]`
1449
+ );
1450
+ }
1451
+ state.printLimit = n;
1452
+ return [`output limit set to ${n}`];
1453
+ }
1268
1454
  case "reset": {
1269
1455
  const currentEngine = state.engine;
1270
- const fresh = createState({ engine: currentEngine });
1456
+ const currentLimit = state.printLimit;
1457
+ const fresh = createState({
1458
+ engine: currentEngine,
1459
+ printLimit: currentLimit
1460
+ });
1271
1461
  Object.assign(state, fresh);
1272
1462
  return ["reset"];
1273
1463
  }
@@ -1302,18 +1492,23 @@ function helpText() {
1302
1492
  ":engine [wasm|pkir|js] display or switch evaluation engine",
1303
1493
  ":rel name show relation definition",
1304
1494
  ":rels list relation aliases",
1305
- ":codec load file load a codec module from file (e.g. :codec load codecs/int.mjs)",
1495
+ ":pattern name show named pattern definition (alias :pat)",
1496
+ ":patterns list defined named patterns (alias :pats)",
1497
+ ":limit [num|none] set maximum characters for value output",
1498
+ ":codec load file load a codec module from file",
1306
1499
  ":codec unload name unload a registered codec",
1307
1500
  ":codec list list loaded codecs (or simply :codecs)",
1308
- ":input <codec.mjs> [text] parse input using specified codec",
1309
- ":load [--no-alias] file",
1310
- " load .k source or .klib",
1501
+ ":input <codec> [txt] parse input using specified codec",
1502
+ ":load [--no-alias] f load .k source or .klib",
1311
1503
  ":klib file export state as a library",
1312
1504
  ":ko file expr export executable .ko using expr as main",
1313
1505
  ":reset clear state",
1314
1506
  ":help show this help",
1315
1507
  "",
1316
- "Raw k input is compiled as a snippet on top of the current state.",
1508
+ "Raw k input:",
1509
+ " name = expr; define relation alias",
1510
+ " name[P1, ...] = filter; define named pattern template",
1511
+ " ? name[...] evaluate named pattern filter",
1317
1512
  "A line ending with ';' plus only spaces closes the snippet.",
1318
1513
  "Use '\\' to force continuation."
1319
1514
  ].join("\n");
@@ -1336,6 +1531,7 @@ function cliUsage() {
1336
1531
  console.log(" --pkir Shortcut for --engine=pkir.");
1337
1532
  console.log(" --wasm Shortcut for --engine=wasm.");
1338
1533
  console.log(" --js Shortcut for --engine=js.");
1534
+ console.log(" --limit=<num> Set maximum characters for output.");
1339
1535
  console.log(" -h, --help Show this help.");
1340
1536
  }
1341
1537
 
@@ -1431,6 +1627,7 @@ function startRepl(options = {}) {
1431
1627
  if (isMainEntrypoint()) {
1432
1628
  const args = argv.slice(2);
1433
1629
  let engine = "wasm";
1630
+ let printLimit = Infinity;
1434
1631
  for (let i = 0; i < args.length; i++) {
1435
1632
  const arg = args[i];
1436
1633
  if (arg === "-h" || arg === "--help") {
@@ -1461,13 +1658,30 @@ if (isMainEntrypoint()) {
1461
1658
  exit(1);
1462
1659
  }
1463
1660
  engine = val;
1661
+ } else if (arg.startsWith("--limit=")) {
1662
+ const val = parseInt(arg.slice("--limit=".length), 10);
1663
+ if (Number.isNaN(val) || val < 0) {
1664
+ console.error(
1665
+ `Invalid limit '${arg.slice("--limit=".length)}'.`
1666
+ );
1667
+ exit(1);
1668
+ }
1669
+ printLimit = val;
1670
+ } else if (arg === "--limit") {
1671
+ i++;
1672
+ const val = parseInt(args[i] || "", 10);
1673
+ if (Number.isNaN(val) || val < 0) {
1674
+ console.error(`Invalid limit '${args[i]}'.`);
1675
+ exit(1);
1676
+ }
1677
+ printLimit = val;
1464
1678
  } else {
1465
1679
  console.error(`Unknown option '${arg}'.`);
1466
1680
  cliUsage();
1467
1681
  exit(1);
1468
1682
  }
1469
1683
  }
1470
- startRepl({ engine });
1684
+ startRepl({ engine, printLimit });
1471
1685
  }
1472
1686
 
1473
1687
  export {
@@ -1502,5 +1716,8 @@ export {
1502
1716
  formatDuration,
1503
1717
  codecNames,
1504
1718
  ensureEnveloped,
1505
- patternFromFilter
1719
+ patternFromFilter,
1720
+ prettyPattern,
1721
+ resolvePatternCanonicalName,
1722
+ listPatterns
1506
1723
  };
@@ -78,8 +78,16 @@ if (fs.existsSync(coreKPath)) {
78
78
  vfsFiles["core.k"] = fs.readFileSync(coreKPath, "utf8");
79
79
  }
80
80
 
81
- // Add verified k-code files: arithmetics.k, ieee.k, poly.k, karatsuba-mult.k
82
- const verifiedExamples = ["arithmetics.k", "ieee.k", "poly.k", "karatsuba-mult.k"];
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
+ "djson-btrie.k",
89
+ "djson-ptrie.k"
90
+ ];
83
91
  const examplesDir = path.join(projectRoot, "Examples");
84
92
  for (const filename of verifiedExamples) {
85
93
  const fullPath = path.join(examplesDir, filename);
@@ -91,7 +99,16 @@ for (const filename of verifiedExamples) {
91
99
  }
92
100
 
93
101
  // Bundle verified codecs for browser VFS as 100% self-contained ES modules
94
- const verifiedCodecs = ["json.mjs", "utf8.mjs", "int.mjs", "unit.mjs", "ieee.mjs"];
102
+ const verifiedCodecs = [
103
+ "json.mjs",
104
+ "djson.mjs",
105
+ "djson-btrie.mjs",
106
+ "djson-ptrie.mjs",
107
+ "utf8.mjs",
108
+ "int.mjs",
109
+ "unit.mjs",
110
+ "ieee.mjs"
111
+ ];
95
112
  const codecsDir = path.join(projectRoot, "codecs");
96
113
 
97
114
  const browserCodecSdkShim = `
@@ -638,6 +655,12 @@ const htmlContent = `<!DOCTYPE html>
638
655
  text-overflow: ellipsis;
639
656
  white-space: nowrap;
640
657
  flex: 1;
658
+ min-width: 0;
659
+ }
660
+
661
+ .k-btn-copy {
662
+ flex-shrink: 0;
663
+ margin-left: auto;
641
664
  }
642
665
 
643
666
  .k-tree-section-body {
@@ -1674,6 +1697,9 @@ const htmlContent = `<!DOCTYPE html>
1674
1697
  <option value="ieee.k">ieee.k</option>
1675
1698
  <option value="poly.k">poly.k</option>
1676
1699
  <option value="karatsuba-mult.k">karatsuba-mult.k</option>
1700
+ <option value="djson.k">djson.k</option>
1701
+ <option value="djson-btrie.k">djson-btrie.k</option>
1702
+ <option value="djson-ptrie.k">djson-ptrie.k</option>
1677
1703
  </optgroup>
1678
1704
  <optgroup label="Quick Expressions">
1679
1705
  <option value="expr:{} | ok">{} | ok</option>
@@ -1693,9 +1719,15 @@ const htmlContent = `<!DOCTYPE html>
1693
1719
  <option value="expr::codec load int.mjs">:codec load int.mjs</option>
1694
1720
  <option value="expr::codec load utf8.mjs">:codec load utf8.mjs</option>
1695
1721
  <option value="expr::codec load json.mjs">:codec load json.mjs</option>
1722
+ <option value="expr::codec load djson.mjs">:codec load djson.mjs</option>
1723
+ <option value="expr::codec load djson-btrie.mjs">:codec load djson-btrie.mjs</option>
1724
+ <option value="expr::codec load djson-ptrie.mjs">:codec load djson-ptrie.mjs</option>
1696
1725
  <option value="expr::codec load ieee.mjs">:codec load ieee.mjs</option>
1697
1726
  <option value="expr::codec load unit.mjs">:codec load unit.mjs</option>
1698
1727
  <option value="expr::input json.mjs {&quot;a&quot;:12,&quot;b&quot;:&quot;hello&quot;}">:input json.mjs {"a":12,"b":"hello"}</option>
1728
+ <option value="expr::input djson.mjs {&quot;a&quot;:12,&quot;b&quot;:&quot;hello&quot;}">:input djson.mjs {"a":12,"b":"hello"}</option>
1729
+ <option value="expr::input djson-btrie.mjs {&quot;a&quot;:12,&quot;b&quot;:&quot;hello&quot;}">:input djson-btrie.mjs {"a":12,"b":"hello"}</option>
1730
+ <option value="expr::input djson-ptrie.mjs {&quot;a&quot;:12,&quot;b&quot;:&quot;hello&quot;}">:input djson-ptrie.mjs {"a":12,"b":"hello"}</option>
1699
1731
  <option value="expr::input int.mjs [0,1,2]">:input int.mjs [0,1,2]</option>
1700
1732
  </optgroup>
1701
1733
  </select>
@@ -1830,6 +1862,16 @@ const htmlContent = `<!DOCTYPE html>
1830
1862
  <td><code>name = relExpr;</code></td>
1831
1863
  <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>
1832
1864
  </tr>
1865
+ <tr>
1866
+ <td><code>name[P1, ..., Pn] = filter;</code></td>
1867
+ <td>
1868
+ <b>Named Pattern Template:</b> Defines a reusable
1869
+ pattern template.<br>
1870
+ <i>e.g.</i><br>
1871
+ <code>bit[] = &lt; {} 0, {} 1 &gt;;</code> or<br>
1872
+ <code>list[X] = &lt; {} nil, ... &gt; = Y;</code>
1873
+ </td>
1874
+ </tr>
1833
1875
  </tbody>
1834
1876
  </table>
1835
1877
 
@@ -1865,8 +1907,15 @@ const htmlContent = `<!DOCTYPE html>
1865
1907
  <td><b>Variant Constructor:</b> Wraps document into variant <code>tag</code></td>
1866
1908
  </tr>
1867
1909
  <tr>
1868
- <td><code>? filterExpr</code></td>
1869
- <td><b>Pattern Filter:</b> Asserts input matches pattern; acts as partial identity and inductive termination witness (<i>e.g.</i> <code>?&lt; {} true, {} false &gt;</code>)</td>
1910
+ <td>
1911
+ <code>? filterExpr</code> /
1912
+ <code>? name[...]</code>
1913
+ </td>
1914
+ <td>
1915
+ <b>Pattern Filter:</b> Asserts input matches pattern;
1916
+ acts as partial identity and inductive witness
1917
+ (<i>e.g.</i> <code>? list[X]</code>)
1918
+ </td>
1870
1919
  </tr>
1871
1920
  <tr>
1872
1921
  <td><code>name</code> / <code>@hash</code></td>
@@ -1881,6 +1930,20 @@ const htmlContent = `<!DOCTYPE html>
1881
1930
  <table class="help-table">
1882
1931
  <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>
1883
1932
  <tr><td><code>:load &lt;file&gt;</code></td><td>Load a <code>.k</code> source or <code>.klib</code> library into current state</td></tr>
1933
+ <tr>
1934
+ <td><code>:pattern &lt;name&gt;</code></td>
1935
+ <td>
1936
+ Display definition of named pattern
1937
+ <code>&lt;name&gt;</code> (alias <code>:pat</code>)
1938
+ </td>
1939
+ </tr>
1940
+ <tr>
1941
+ <td><code>:patterns</code></td>
1942
+ <td>
1943
+ List defined named pattern templates
1944
+ (alias <code>:pats</code>)
1945
+ </td>
1946
+ </tr>
1884
1947
  <tr><td><code>:rel &lt;name&gt;</code></td><td>Display definition of relation <code>&lt;name&gt;</code></td></tr>
1885
1948
  <tr><td><code>:rels</code></td><td>List all relation aliases</td></tr>
1886
1949
  <tr><td><code>:codecs</code></td><td>List all loaded format codecs</td></tr>
@@ -1901,6 +1964,14 @@ const htmlContent = `<!DOCTYPE html>
1901
1964
  <li><code>not = &lt; / true | false, / false | true &gt;;</code> &mdash; Polymorphic negation via branch projections and ordered choice</li>
1902
1965
  <li><code>{} | true not</code> &mdash; Compose constant relation <code>{} | true</code> with <code>not</code> (evaluates to <code>{} | false</code>)</li>
1903
1966
  <li><code>bool? = ?&lt; {} true, {} false &gt;;</code> &mdash; Define a boolean pattern filter relation</li>
1967
+ <li>
1968
+ <code>bit[] = &lt; {} 0, {} 1 &gt;;</code>
1969
+ &mdash; Define a 0-arity bit pattern
1970
+ </li>
1971
+ <li>
1972
+ <code>? list[bool[]]</code>
1973
+ &mdash; Instantiated pattern filter matching booleans
1974
+ </li>
1904
1975
  <li><code>not = bool? ?X &lt; / true | false, {} | true &gt; ?X;</code> &mdash; Filtered boolean negation: guarded by <code>bool?</code>, defaulting to <code>{} | true</code></li>
1905
1976
  <li><code>swap = { . y x, . x y };</code> &mdash; Define a product field-swapping relation</li>
1906
1977
  <li><code>{ {} | ok x, {} | nil y } swap</code> &mdash; Construct a product and pass it through <code>swap</code></li>