@fraczak/k 6.3.3 → 6.5.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 (157) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/DOCS/CODECS.md +258 -0
  3. package/DOCS/DICTIONARY.md +1172 -0
  4. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  5. package/DOCS/KIR_V1.md +221 -0
  6. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  7. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  8. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  9. package/DOCS/REPL.md +61 -4
  10. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  11. package/DOCS/dependency-graph.md +144 -0
  12. package/Examples/arithmetics.k +108 -82
  13. package/Examples/bnat.k +1 -1
  14. package/Examples/buda.k +44 -0
  15. package/Examples/ieee.k +300 -8671
  16. package/Examples/poly.k +67 -0
  17. package/README.md +68 -19
  18. package/Value.mjs +8 -2
  19. package/augmentation.mjs +9 -7
  20. package/backend-api.mjs +11 -2
  21. package/backends/arm64/README.md +104 -0
  22. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  23. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  24. package/backends/arm64/bin/k-arm64.mjs +88 -0
  25. package/backends/arm64/scripts/run-tests.mjs +41 -0
  26. package/backends/arm64/src/arm64.mjs +229 -0
  27. package/backends/arm64/src/cli.mjs +155 -0
  28. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  29. package/backends/arm64/src/runtime.c +728 -0
  30. package/backends/arm64/tests/integration.sh +112 -0
  31. package/backends/arm64/tests/test-arm64.mjs +240 -0
  32. package/backends/llvm/README.md +135 -0
  33. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  34. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  35. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  36. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  37. package/backends/llvm/runtime/krt.c +1508 -0
  38. package/backends/llvm/runtime/krt.h +109 -0
  39. package/backends/llvm/scripts/conformance.mjs +76 -0
  40. package/backends/llvm/scripts/run-tests.mjs +28 -0
  41. package/backends/llvm/src/executable.mjs +562 -0
  42. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  43. package/backends/llvm/src/llvm.mjs +1092 -0
  44. package/backends/llvm/tests/composition-driver.c +22 -0
  45. package/backends/llvm/tests/identity-driver.c +27 -0
  46. package/backends/llvm/tests/integration.sh +153 -0
  47. package/backends/llvm/tests/perf-support.mjs +735 -0
  48. package/backends/llvm/tests/product-driver.c +23 -0
  49. package/backends/llvm/tests/projection-driver.c +19 -0
  50. package/backends/llvm/tests/relation-driver.c +29 -0
  51. package/backends/llvm/tests/test-llvm.mjs +240 -0
  52. package/backends/llvm/tests/union-driver.c +31 -0
  53. package/backends/llvm/tests/variant-driver.c +19 -0
  54. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  55. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  56. package/backends/wasm/README.md +97 -0
  57. package/backends/wasm/TECHNICAL-DEBT.md +58 -0
  58. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  59. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  60. package/backends/wasm/bin/k-wasm.mjs +49 -0
  61. package/backends/wasm/demo_ieee.mjs +319 -0
  62. package/backends/wasm/runtime.wat +475 -0
  63. package/backends/wasm/scripts/run-tests.mjs +41 -0
  64. package/backends/wasm/src/cli.mjs +133 -0
  65. package/backends/wasm/src/kvm2wasm.mjs +697 -0
  66. package/backends/wasm/src/wasm.mjs +1054 -0
  67. package/backends/wasm/tests/integration.sh +182 -0
  68. package/backends/wasm/tests/perf-support.mjs +336 -0
  69. package/backends/wasm/tests/test-wasm.mjs +625 -0
  70. package/browser/repl-browser.mjs +1584 -0
  71. package/browser/shims/assert.mjs +19 -0
  72. package/browser/shims/buffer.mjs +224 -0
  73. package/browser/shims/crypto.mjs +103 -0
  74. package/browser/shims/fs.mjs +159 -0
  75. package/browser/shims/os.mjs +17 -0
  76. package/browser/shims/path.mjs +54 -0
  77. package/browser/shims/process.mjs +44 -0
  78. package/browser/shims/readline.mjs +11 -0
  79. package/browser/shims/url.mjs +12 -0
  80. package/browser/vfs-data.mjs +19 -0
  81. package/codecs/README.md +3 -0
  82. package/codecs/runtime/show-value.mjs +12 -3
  83. package/conformance/helper-reuse/case.json +8 -0
  84. package/conformance/helper-reuse/expected.kv +1 -0
  85. package/conformance/helper-reuse/input.kv +1 -0
  86. package/conformance/helper-reuse/program.k +2 -0
  87. package/conformance/identity/case.json +8 -0
  88. package/conformance/identity/expected.kv +1 -0
  89. package/conformance/identity/input.kv +1 -0
  90. package/conformance/identity/program.k +1 -0
  91. package/conformance/product-projection/case.json +8 -0
  92. package/conformance/product-projection/expected.kv +1 -0
  93. package/conformance/product-projection/input.kv +1 -0
  94. package/conformance/product-projection/program.k +1 -0
  95. package/conformance/run.mjs +149 -0
  96. package/conformance/variant-construction/case.json +8 -0
  97. package/conformance/variant-construction/expected.kv +1 -0
  98. package/conformance/variant-construction/input.kv +1 -0
  99. package/conformance/variant-construction/program.k +1 -0
  100. package/harness/adapters/arm64.mjs +105 -0
  101. package/harness/adapters/kvm.mjs +144 -0
  102. package/harness/adapters/llvm.mjs +118 -0
  103. package/harness/adapters/native.mjs +77 -0
  104. package/harness/adapters/wasm.mjs +169 -0
  105. package/harness/backend.mjs +95 -0
  106. package/harness/cli.mjs +130 -0
  107. package/harness/format.mjs +95 -0
  108. package/harness/index.mjs +25 -0
  109. package/harness/persistent.mjs +121 -0
  110. package/harness/registry.mjs +44 -0
  111. package/harness/suite.mjs +257 -0
  112. package/harness/suites/poly.mjs +156 -0
  113. package/harness/trace.mjs +50 -0
  114. package/harness/util.mjs +92 -0
  115. package/intrinsics.mjs +41 -0
  116. package/kir.mjs +399 -0
  117. package/kvm.mjs +585 -81
  118. package/objects/README.md +37 -3
  119. package/objects/compile.mjs +15 -22
  120. package/objects/inspect.mjs +96 -0
  121. package/objects/validate.mjs +415 -0
  122. package/package.json +51 -19
  123. package/repl-codecs.mjs +105 -6
  124. package/repl.mjs +335 -49
  125. package/run.mjs +2 -7
  126. package/scripts/benchmark.mjs +406 -22
  127. package/scripts/build-repl-html.mjs +1784 -0
  128. package/scripts/perf-ieee.mjs +586 -0
  129. package/scripts/perf-int.mjs +531 -0
  130. package/scripts/perf-poly.mjs +945 -0
  131. package/scripts/render-benchmark.mjs +12 -8
  132. package/scripts/run-tests.mjs +6 -0
  133. package/tests/integration.sh +19 -2
  134. package/tests/test-arithmetics.mjs +54 -0
  135. package/tests/test-ieee-arithmetic.mjs +4 -0
  136. package/tests/test-kir.mjs +85 -0
  137. package/tests/test-kvm-polymorphic.mjs +278 -0
  138. package/tests/test-kvm.mjs +25 -6
  139. package/tests/test-poly.mjs +129 -0
  140. package/tests/test-repl-html.mjs +303 -0
  141. package/tests/test-repl.mjs +144 -1
  142. package/tests/test-validate-object.mjs +40 -0
  143. package/a.klib +0 -1
  144. package/a.kvm +0 -142676
  145. package/a.wasm +0 -0
  146. package/benchmark-results.html +0 -532
  147. package/benchmark-results.json +0 -480
  148. package/k-vscode/.vscode/launch.json +0 -14
  149. package/k-vscode/README.md +0 -75
  150. package/k-vscode/extension.js +0 -273
  151. package/k-vscode/language-configuration.json +0 -42
  152. package/k-vscode/package.json +0 -79
  153. package/k-vscode/snippets/k.json +0 -92
  154. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  155. package/k-vscode/test/background-ranges.test.js +0 -50
  156. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  157. package/scripts/git-hooks/pre-commit +0 -10
package/repl.mjs CHANGED
@@ -8,9 +8,17 @@ import { fileURLToPath } from "node:url";
8
8
  import { argv, exit, stdin, stdout } from "node:process";
9
9
 
10
10
  import { annotate, parse } from "./index.mjs";
11
- import run from "./run.mjs";
11
+ import { constrainWithPattern } from "./run.mjs";
12
+ import { compileWasmArtifactFromObject, instantiateWasmArtifact } from "./backends/wasm/src/wasm.mjs";
13
+ import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
14
+ import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
12
15
  import codes from "./codes.mjs";
13
- import { Value } from "./Value.mjs";
16
+ import { Value, isProduct, isVariant } from "./Value.mjs";
17
+ if (typeof globalThis !== "undefined") {
18
+ globalThis.Value = Value;
19
+ globalThis.isProduct = isProduct;
20
+ globalThis.isVariant = isVariant;
21
+ }
14
22
  import { patterns2filters, prettyCode, prettyRel } from "./pretty.mjs";
15
23
  import {
16
24
  compileObject,
@@ -28,7 +36,11 @@ import {
28
36
  codeHashToPattern,
29
37
  listCodecs,
30
38
  loadCodecModule,
39
+ normalizeCodecModule,
40
+ registerCodec,
31
41
  resolveCodec,
42
+ unregisterCodec,
43
+ BUILTIN_CODECS,
32
44
  closedPatternToCodeHash,
33
45
  UNIVERSAL_CODE,
34
46
  valueForCode
@@ -39,14 +51,22 @@ const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
39
51
  const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
40
52
  const COMMAND_NAMES = [
41
53
  "help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
42
- "codes", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
54
+ "codes", "codecs", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
55
+ "timing", "time",
43
56
  "quit", "exit"
44
57
  ];
45
- const CODEC_COMMAND_NAMES = ["load", "list"];
58
+ const CODEC_COMMAND_NAMES = ["load", "unload", "list", "define"];
46
59
  const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
47
60
  const INPUT_TYPE_NAME = "__input__";
48
61
  const initialCodes = codes.dump();
49
62
 
63
+ function formatDuration(ms) {
64
+ if (ms < 1000) {
65
+ return `${ms < 10 ? ms.toFixed(1) : Math.round(ms)}ms`;
66
+ }
67
+ return `${(ms / 1000).toFixed(2)}s`;
68
+ }
69
+
50
70
  function cloneJSON(value) {
51
71
  return JSON.parse(JSON.stringify(value));
52
72
  }
@@ -63,10 +83,13 @@ function createState() {
63
83
  relAliases: {},
64
84
  typeAliases: {},
65
85
  codecs: {},
86
+ loadedFiles: new Set(),
66
87
  pendingInput: null,
67
88
  meta: {},
68
89
  value: emptyValue(),
69
- lastMain: null
90
+ lastMain: null,
91
+ lastTiming: null,
92
+ showTiming: false
70
93
  };
71
94
  }
72
95
 
@@ -211,6 +234,16 @@ function mergeLibrary(state, lib, source = "<load>", options = {}) {
211
234
  }
212
235
  }
213
236
  recoverAliasesFromMeta(state, lib);
237
+ if (state.typeAliases.string && !state.typeAliases.utf8) {
238
+ state.typeAliases.utf8 = state.typeAliases.string;
239
+ } else if (state.typeAliases.utf8 && !state.typeAliases.string) {
240
+ state.typeAliases.string = state.typeAliases.utf8;
241
+ }
242
+ if (state.typeAliases.float64 && !state.typeAliases.ieee) {
243
+ state.typeAliases.ieee = state.typeAliases.float64;
244
+ } else if (state.typeAliases.ieee && !state.typeAliases.float64) {
245
+ state.typeAliases.float64 = state.typeAliases.ieee;
246
+ }
214
247
  }
215
248
  restoreCodes(state);
216
249
  }
@@ -597,15 +630,33 @@ function codecLoadPathCompletionStart(argStart, arg) {
597
630
  return argStart + leading.length + "load".length + 1 + restLeadingWhitespace + completionTokenStart(rest.trimStart());
598
631
  }
599
632
 
600
- function completeCodecCommand(line, argStart, arg) {
633
+ function completeCodecCommand(line, argStart, arg, state) {
601
634
  const trimmed = arg.trimStart();
602
635
  if (trimmed === "" || !/\s/.test(trimmed)) {
603
636
  return completeCommandWord(line, argStart, arg, CODEC_COMMAND_NAMES);
604
637
  }
605
638
 
639
+ const unloadMatch = arg.match(/^(\s*)unload(?:\s+(.*))?$/);
640
+ if (unloadMatch) {
641
+ const [, leading, partial = ""] = unloadMatch;
642
+ const prefix = line.slice(0, argStart + leading.length + "unload".length + 1);
643
+ const names = state ? codecNames(state) : [];
644
+ const matches = names
645
+ .filter((name) => name.startsWith(partial.trim()))
646
+ .map((name) => `${prefix}${name}`);
647
+ return [matches, line];
648
+ }
649
+
606
650
  const tokenStart = codecLoadPathCompletionStart(argStart, arg);
607
651
  if (tokenStart == null) return [[], line];
608
- return completePath(line, tokenStart);
652
+ const [pathMatches] = completePath(line, tokenStart);
653
+ const match = arg.match(/^(\s*)load(?:\s+(.*))?$/);
654
+ const partial = match && match[2] != null ? match[2].trim() : "";
655
+ const prefix = line.slice(0, tokenStart);
656
+ const builtinMatches = ["int", "utf8", "json", "ieee"]
657
+ .filter((name) => name.startsWith(partial))
658
+ .map((name) => `${prefix}${name}`);
659
+ return [[...builtinMatches, ...pathMatches], line];
609
660
  }
610
661
 
611
662
  function resolveTypeHash(state, rawName) {
@@ -689,8 +740,8 @@ function completeCommandArgument(line, state) {
689
740
  const argStart = 1 + firstSpace + 1;
690
741
  const arg = line.slice(argStart);
691
742
 
692
- if (command === "codec") {
693
- return completeCodecCommand(line, argStart, arg);
743
+ if (command === "codec" || command === "codecs") {
744
+ return completeCodecCommand(line, argStart, arg, state);
694
745
  }
695
746
 
696
747
  if (command === "input") {
@@ -766,6 +817,102 @@ function parseLoadArgs(arg, usagePrefix = ":") {
766
817
  return { path: arg.trim(), loadAliases: true };
767
818
  }
768
819
 
820
+ const CODEC_FILES = {
821
+ int: "arithmetics.k",
822
+ utf8: "core.k",
823
+ json: "core.k",
824
+ ieee: "ieee.k",
825
+ unit: "core.k"
826
+ };
827
+
828
+ function normalizeFilePath(p) {
829
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
830
+ }
831
+
832
+ function resolveKFilePath(filePath) {
833
+ try {
834
+ if (fs.existsSync(filePath)) return filePath;
835
+ } catch {}
836
+ try {
837
+ const fromRepo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), filePath);
838
+ if (fs.existsSync(fromRepo)) return fromRepo;
839
+ } catch {}
840
+ const withEx = filePath.startsWith("Examples/") ? filePath : `Examples/${filePath}`;
841
+ try {
842
+ if (fs.existsSync(withEx)) return withEx;
843
+ } catch {}
844
+ try {
845
+ const fromRepoEx = path.resolve(path.dirname(fileURLToPath(import.meta.url)), withEx);
846
+ if (fs.existsSync(fromRepoEx)) return fromRepoEx;
847
+ } catch {}
848
+ const strippedEx = filePath.startsWith("Examples/") ? filePath.slice("Examples/".length) : filePath;
849
+ try {
850
+ if (fs.existsSync(strippedEx)) return strippedEx;
851
+ } catch {}
852
+ try {
853
+ const fromRepoStripped = path.resolve(path.dirname(fileURLToPath(import.meta.url)), strippedEx);
854
+ if (fs.existsSync(fromRepoStripped)) return fromRepoStripped;
855
+ } catch {}
856
+ return filePath;
857
+ }
858
+
859
+ function loadSourceOrKlib(state, targetPath, options = {}) {
860
+ const loadAliases = options.loadAliases ?? true;
861
+ const normKey = normalizeFilePath(targetPath);
862
+ if (!state.loadedFiles) state.loadedFiles = new Set();
863
+
864
+ // If loading poly.k, auto-load arithmetics.k first if not already loaded
865
+ if (normKey === "Examples/poly.k" || normKey.endsWith("/poly.k") || normKey === "poly.k") {
866
+ const arithKey = "arithmetics.k";
867
+ const alreadyHasArith = Array.from(state.loadedFiles).some(f => path.basename(f) === "arithmetics.k");
868
+ if (!alreadyHasArith) {
869
+ loadSourceOrKlib(state, arithKey, { loadAliases });
870
+ }
871
+ }
872
+
873
+ const loadPath = resolveKFilePath(targetPath);
874
+ if (loadPath.endsWith(".klib")) {
875
+ const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
876
+ mergeLibrary(state, lib, targetPath, { loadAliases });
877
+ } else {
878
+ restoreCodes(state);
879
+ const source = fs.readFileSync(loadPath, "utf8");
880
+ const preamble = aliasPreamble(state);
881
+ const fullSource = [preamble, source].filter(Boolean).join("\n");
882
+ const compileOptions = {
883
+ source: targetPath,
884
+ libraries: [stateLibrary(state)]
885
+ };
886
+ try {
887
+ const lib = compileWithOptionalIdentity(fullSource, compileOptions);
888
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, compileOptions);
889
+ mergeLibrary(state, { ...lib, relAlias, meta }, targetPath, { loadAliases });
890
+ } catch (error) {
891
+ throw remapError(error, preambleLineCount(preamble));
892
+ }
893
+ }
894
+ state.loadedFiles.add(normKey);
895
+ return targetPath;
896
+ }
897
+
898
+ function ensureCodecDependencies(state, codecTarget) {
899
+ const norm = normalizeFilePath(codecTarget).toLowerCase();
900
+ const base = path.basename(norm, path.extname(norm));
901
+ const depFile = CODEC_FILES[base] || CODEC_FILES[norm];
902
+ if (!depFile) return null;
903
+ if (!state.loadedFiles) state.loadedFiles = new Set();
904
+ const depNormKey = normalizeFilePath(depFile);
905
+ const baseName = path.basename(depNormKey);
906
+ const alreadyLoaded = state.loadedFiles.has(depNormKey) ||
907
+ state.loadedFiles.has(`Examples/${depNormKey}`) ||
908
+ Array.from(state.loadedFiles).some(f => path.basename(f) === baseName);
909
+ if (!alreadyLoaded) {
910
+ loadSourceOrKlib(state, depFile);
911
+ return depFile;
912
+ }
913
+ return null;
914
+ }
915
+
769
916
  function lineForContinuation(line) {
770
917
  return line.replace(/\\\s*$/, "");
771
918
  }
@@ -883,29 +1030,82 @@ async function defineRelation(input, state) {
883
1030
  return [`${name} = ${hash}`];
884
1031
  }
885
1032
 
1033
+ async function executeExpressionWithWasm(annotated, state, lineOffset = 0) {
1034
+ const mainRel = annotated.rels.__main__;
1035
+ if (!mainRel) return { result: undefined, wasmCompileMs: 0, executeMs: 0 };
1036
+
1037
+ let inputValue = state.value ?? emptyValue();
1038
+ if (mainRel.typePatternGraph && mainRel.def.patterns) {
1039
+ const graph = mainRel.typePatternGraph;
1040
+ const nodeId = graph.find(mainRel.def.patterns[0]);
1041
+ const inputPattern = patternToPropertyList(exportPatternGraph(graph, nodeId));
1042
+ inputValue = constrainWithPattern(inputValue, inputPattern, mainRel.def);
1043
+ }
1044
+
1045
+ const obj = {
1046
+ format: "k-object",
1047
+ codes: { ...state.codes, ...codes.dump() },
1048
+ rels: annotated.rels,
1049
+ relAlias: annotated.relAlias,
1050
+ compileStats: annotated.compileStats,
1051
+ main: "__main__"
1052
+ };
1053
+
1054
+ const wasmCompileStart = performance.now();
1055
+ const wasmBytes = await compileWasmArtifactFromObject(obj, {
1056
+ inputEnvelopePattern: inputValue?.pattern
1057
+ });
1058
+ const runner = await instantiateWasmArtifact(wasmBytes);
1059
+ const wasmCompileMs = performance.now() - wasmCompileStart;
1060
+
1061
+ const executeStart = performance.now();
1062
+ const result = runner.executeValue(inputValue);
1063
+ const executeMs = performance.now() - executeStart;
1064
+
1065
+ return { result, wasmCompileMs, executeMs };
1066
+ }
1067
+
886
1068
  async function runExpression(input, state) {
887
1069
  const expression = input.trim();
888
1070
  if (!expression) throw new Error(":run requires an expression");
889
1071
 
890
1072
  restoreCodes(state);
891
1073
  const preamble = aliasPreamble(state);
1074
+ const lineOffset = preambleLineCount(preamble);
892
1075
  const source = [preamble, expression].filter(Boolean).join("\n");
1076
+ const compileStart = performance.now();
893
1077
  let annotated;
894
1078
  try {
895
1079
  annotated = annotate(source, { libraries: [stateLibrary(state)] });
896
1080
  } catch (error) {
897
- throw remapError(error, preambleLineCount(preamble));
1081
+ throw remapError(error, lineOffset);
898
1082
  }
899
- const mainRel = annotated.rels.__main__;
900
- run.defs = annotated;
901
- let result;
1083
+ const snippetCompileMs = performance.now() - compileStart;
1084
+
1085
+ let execRes;
902
1086
  try {
903
- result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
1087
+ execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
904
1088
  } catch (error) {
905
- throw remapError(error, preambleLineCount(preamble));
1089
+ throw remapError(error, lineOffset);
906
1090
  }
1091
+ state.codes = codes.dump();
907
1092
  restoreCodes(state);
908
- return commitResult(state, result, expression);
1093
+
1094
+ const compileMs = snippetCompileMs + execRes.wasmCompileMs;
1095
+ const executeMs = execRes.executeMs;
1096
+ state.lastTiming = {
1097
+ compileMs,
1098
+ executeMs,
1099
+ totalMs: compileMs + executeMs,
1100
+ snippetCompileMs,
1101
+ wasmCompileMs: execRes.wasmCompileMs
1102
+ };
1103
+
1104
+ const committed = commitResult(state, execRes.result, expression);
1105
+ if (state.showTiming) {
1106
+ committed.push(`/* comp: ${formatDuration(compileMs)}, exec: ${formatDuration(executeMs)} */`);
1107
+ }
1108
+ return committed;
909
1109
  }
910
1110
 
911
1111
  async function runSnippet(input, state, options = {}) {
@@ -914,42 +1114,107 @@ async function runSnippet(input, state, options = {}) {
914
1114
 
915
1115
  const explicitTerminated = options.explicitTerminated ?? explicitSnippetTerminated(input);
916
1116
  const analysis = analyzeAcceptedSnippet(snippet, explicitTerminated);
1117
+ const compileStart = performance.now();
917
1118
  const { annotated, lib, lineOffset } = compileSnippetArtifacts(snippet, state);
1119
+ const snippetCompileMs = performance.now() - compileStart;
918
1120
  mergeLibrary(state, lib, "<repl>");
919
1121
 
920
1122
  if (analysis.kind === "definitionsOnly") {
921
1123
  restoreCodes(state);
1124
+ state.lastTiming = {
1125
+ compileMs: snippetCompileMs,
1126
+ executeMs: 0,
1127
+ totalMs: snippetCompileMs,
1128
+ snippetCompileMs,
1129
+ wasmCompileMs: 0
1130
+ };
1131
+ if (state.showTiming) {
1132
+ return [`/* comp: ${formatDuration(snippetCompileMs)} */`];
1133
+ }
922
1134
  return [];
923
1135
  }
924
1136
 
925
- const mainRel = annotated.rels.__main__;
926
- run.defs = annotated;
927
- let result;
1137
+ let execRes;
928
1138
  try {
929
- result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
1139
+ execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
930
1140
  } catch (error) {
931
1141
  throw remapError(error, lineOffset);
932
1142
  }
1143
+ state.codes = codes.dump();
933
1144
  restoreCodes(state);
934
- return commitResult(state, result, snippet);
1145
+
1146
+ const compileMs = snippetCompileMs + execRes.wasmCompileMs;
1147
+ const executeMs = execRes.executeMs;
1148
+ state.lastTiming = {
1149
+ compileMs,
1150
+ executeMs,
1151
+ totalMs: compileMs + executeMs,
1152
+ snippetCompileMs,
1153
+ wasmCompileMs: execRes.wasmCompileMs
1154
+ };
1155
+
1156
+ const committed = commitResult(state, execRes.result, snippet);
1157
+ if (state.showTiming) {
1158
+ committed.push(`/* comp: ${formatDuration(compileMs)}, exec: ${formatDuration(executeMs)} */`);
1159
+ }
1160
+ return committed;
935
1161
  }
936
1162
 
937
1163
  async function loadCodec(input, state) {
938
- const [subcommand, ...rest] = input.trim().split(/\s+/);
1164
+ const trimmed = input.trim();
1165
+ if (!trimmed || trimmed === "list") {
1166
+ return [listCodecs(state)];
1167
+ }
1168
+
1169
+ const [subcommand, ...rest] = trimmed.split(/\s+/);
939
1170
  switch (subcommand) {
940
1171
  case "load": {
941
1172
  const filePath = rest.join(" ");
942
- if (!filePath) throw new Error(":codec load requires a file path");
1173
+ if (!filePath) throw new Error(":codec load requires a file path or codec name");
1174
+ const autoLoaded = ensureCodecDependencies(state, filePath);
943
1175
  const registered = await loadCodecModule(state, expandHome(filePath));
944
1176
  return registered.map(({ name, codeHash }) =>
945
- `loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}`
1177
+ `loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}${autoLoaded ? ` (auto-loaded ${autoLoaded})` : ""}`
1178
+ );
1179
+ }
1180
+ case "unload": {
1181
+ const codecName = rest.join(" ").trim();
1182
+ if (!codecName) throw new Error(":codec unload requires a codec name");
1183
+ const removed = unregisterCodec(state, codecName);
1184
+ if (removed === 0) throw new Error(`Codec '${codecName}' is not loaded`);
1185
+ return [`unloaded codec ${codecName}`];
1186
+ }
1187
+ case "define": {
1188
+ const match = rest.join(" ").match(/^([a-zA-Z0-9_+-]+)\s+(\S+)\s+([\s\S]+)$/);
1189
+ if (!match) {
1190
+ throw new Error(":codec define requires: name type { parse: ..., print: ... }");
1191
+ }
1192
+ const [, name, rawType, bodyStr] = match;
1193
+ const fn = new Function("Value", "isProduct", "isVariant", "state", `return (${bodyStr});`);
1194
+ const obj = fn(Value, isProduct, isVariant, state);
1195
+ const codeHash = rawType === "*" ? UNIVERSAL_CODE : resolveInputTypeHash(state, rawType);
1196
+ const codec = normalizeCodecModule({
1197
+ name,
1198
+ codes: [codeHash],
1199
+ universal: codeHash === UNIVERSAL_CODE,
1200
+ parse: obj.parse,
1201
+ print: obj.print
1202
+ }, name);
1203
+ const registered = registerCodec(state, codec, "<inline>");
1204
+ return registered.map(({ name, codeHash }) =>
1205
+ `defined codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}`
946
1206
  );
947
1207
  }
948
1208
  case "list":
949
1209
  if (rest.length > 0) throw new Error(":codec list does not accept arguments");
950
1210
  return [listCodecs(state)];
951
- default:
952
- throw new Error(":codec requires 'load' or 'list'");
1211
+ default: {
1212
+ const autoLoaded = ensureCodecDependencies(state, trimmed);
1213
+ const registered = await loadCodecModule(state, expandHome(trimmed));
1214
+ return registered.map(({ name, codeHash }) =>
1215
+ `loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}${autoLoaded ? ` (auto-loaded ${autoLoaded})` : ""}`
1216
+ );
1217
+ }
953
1218
  }
954
1219
  }
955
1220
 
@@ -998,6 +1263,7 @@ async function consumeCodecInput(input, state) {
998
1263
  }
999
1264
 
1000
1265
  async function evaluateInput(input, state) {
1266
+ state.lastTiming = null;
1001
1267
  if (state.pendingInput) {
1002
1268
  return consumeCodecInput(input, state);
1003
1269
  }
@@ -1028,6 +1294,29 @@ async function evaluateCommand(line, state) {
1028
1294
  exit(0);
1029
1295
  case "help":
1030
1296
  return [helpText()];
1297
+ case "timing": {
1298
+ const trimmed = arg.trim().toLowerCase();
1299
+ if (trimmed === "on" || trimmed === "true" || trimmed === "1") {
1300
+ state.showTiming = true;
1301
+ } else if (trimmed === "off" || trimmed === "false" || trimmed === "0") {
1302
+ state.showTiming = false;
1303
+ } else if (!trimmed) {
1304
+ state.showTiming = !state.showTiming;
1305
+ } else {
1306
+ throw new Error(":timing accepts 'on', 'off', or no arguments to toggle");
1307
+ }
1308
+ return [`timing reporting ${state.showTiming ? "enabled" : "disabled"}`];
1309
+ }
1310
+ case "time": {
1311
+ if (!arg) throw new Error(":time requires an expression");
1312
+ const wasTiming = state.showTiming;
1313
+ state.showTiming = true;
1314
+ try {
1315
+ return await evaluateInput(arg, state);
1316
+ } finally {
1317
+ state.showTiming = wasTiming;
1318
+ }
1319
+ }
1031
1320
  case "type": {
1032
1321
  if (!arg) throw new Error(":type requires a name or definition");
1033
1322
  if (arg.includes("=")) {
@@ -1055,6 +1344,7 @@ async function evaluateCommand(line, state) {
1055
1344
  JSON.stringify(state.value, null, 2)
1056
1345
  ];
1057
1346
  case "codec":
1347
+ case "codecs":
1058
1348
  return loadCodec(arg, state);
1059
1349
  case "input":
1060
1350
  return requestCodecInput(arg, state);
@@ -1081,26 +1371,7 @@ async function evaluateCommand(line, state) {
1081
1371
  }
1082
1372
  case "load": {
1083
1373
  const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
1084
- if (loadPath.endsWith(".klib")) {
1085
- const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
1086
- mergeLibrary(state, lib, loadPath, { loadAliases });
1087
- } else {
1088
- restoreCodes(state);
1089
- const source = fs.readFileSync(loadPath, "utf8");
1090
- const preamble = aliasPreamble(state);
1091
- const fullSource = [preamble, source].filter(Boolean).join("\n");
1092
- const options = {
1093
- source: loadPath,
1094
- libraries: [stateLibrary(state)]
1095
- };
1096
- try {
1097
- const lib = compileWithOptionalIdentity(fullSource, options);
1098
- const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
1099
- mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
1100
- } catch (error) {
1101
- throw remapError(error, preambleLineCount(preamble));
1102
- }
1103
- }
1374
+ loadSourceOrKlib(state, loadPath, { loadAliases });
1104
1375
  return [`loaded ${loadPath}`];
1105
1376
  }
1106
1377
  case "t": {
@@ -1131,13 +1402,17 @@ function helpText() {
1131
1402
  ":type name = <...> define a type",
1132
1403
  ":rel name = expr define a relation",
1133
1404
  ":run expr run an expression on the current value",
1405
+ ":time expr run an expression and report compilation/execution time",
1406
+ ":timing [on|off] toggle timing reporting after every evaluation",
1134
1407
  ":t name show relation type",
1135
1408
  ":d name show relation definition",
1136
1409
  ":type name show type definition",
1137
1410
  ":codes list type aliases",
1138
1411
  ":rels list relation aliases",
1139
- ":codec load file load a REPL codec module",
1140
- ":codec list list loaded codecs",
1412
+ ":codec load file load a REPL codec module (or built-in: int, utf8, json, ieee)",
1413
+ ":codec define n t b define a custom codec: name type { parse: ..., print: ... }",
1414
+ ":codec unload name unload a registered codec",
1415
+ ":codec list list loaded codecs (or simply :codecs)",
1141
1416
  ":input type [codec] read next line as codec input",
1142
1417
  ":load [--no-alias] file",
1143
1418
  " load .k source or .klib",
@@ -1284,8 +1559,19 @@ export {
1284
1559
  isMainEntrypoint,
1285
1560
  lineHasExplicitContinuation,
1286
1561
  lineTerminatesSnippet,
1562
+ listCodecs,
1563
+ loadCodecModule,
1564
+ registerCodec,
1565
+ unregisterCodec,
1566
+ resolveCodec,
1567
+ codeHashToPattern,
1568
+ BUILTIN_CODECS,
1287
1569
  printValue,
1288
1570
  promptForState,
1289
1571
  propertyListToFilter,
1290
- valueToK
1572
+ savedLibrary,
1573
+ loadSourceOrKlib,
1574
+ ensureCodecDependencies,
1575
+ valueToK,
1576
+ formatDuration
1291
1577
  };
package/run.mjs CHANGED
@@ -2,14 +2,9 @@ import assert from "assert";
2
2
  import { Value, edgeSubpattern, composePattern, withPattern, isProduct, isVariant } from "./Value.mjs"
3
3
  import { exportPatternGraph, intersectPropertyListPatterns } from "./codecs/runtime/codec.mjs";
4
4
  import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
5
+ import { jsIntrinsicFunctions } from "./intrinsics.mjs";
5
6
 
6
- const builtin = {
7
- "_log!": (arg) => {
8
- console.error(`_log!: ${arg}`);
9
-
10
- return arg;
11
- }
12
- };
7
+ const builtin = jsIntrinsicFunctions;
13
8
 
14
9
  const codes = { };
15
10
  const staticPatternCache = new WeakMap();