@fraczak/k 6.3.2 → 6.4.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 (156) hide show
  1. package/DOCS/CODECS.md +258 -0
  2. package/DOCS/DICTIONARY.md +1172 -0
  3. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  4. package/DOCS/KIR_V1.md +221 -0
  5. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  6. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  7. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  8. package/DOCS/REPL.md +76 -14
  9. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  10. package/DOCS/dependency-graph.md +144 -0
  11. package/Examples/arithmetics.k +51 -48
  12. package/Examples/bnat.k +1 -1
  13. package/Examples/buda.k +44 -0
  14. package/Examples/ieee.k +300 -8671
  15. package/Examples/poly.k +67 -0
  16. package/README.md +68 -19
  17. package/augmentation.mjs +9 -7
  18. package/backend-api.mjs +11 -2
  19. package/backends/arm64/README.md +104 -0
  20. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  21. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  22. package/backends/arm64/bin/k-arm64.mjs +88 -0
  23. package/backends/arm64/scripts/run-tests.mjs +41 -0
  24. package/backends/arm64/src/arm64.mjs +229 -0
  25. package/backends/arm64/src/cli.mjs +155 -0
  26. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  27. package/backends/arm64/src/runtime.c +728 -0
  28. package/backends/arm64/tests/integration.sh +112 -0
  29. package/backends/arm64/tests/test-arm64.mjs +240 -0
  30. package/backends/llvm/README.md +135 -0
  31. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  32. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  33. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  34. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  35. package/backends/llvm/runtime/krt.c +1508 -0
  36. package/backends/llvm/runtime/krt.h +109 -0
  37. package/backends/llvm/scripts/conformance.mjs +76 -0
  38. package/backends/llvm/scripts/run-tests.mjs +28 -0
  39. package/backends/llvm/src/executable.mjs +562 -0
  40. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  41. package/backends/llvm/src/llvm.mjs +1092 -0
  42. package/backends/llvm/tests/composition-driver.c +22 -0
  43. package/backends/llvm/tests/identity-driver.c +27 -0
  44. package/backends/llvm/tests/integration.sh +153 -0
  45. package/backends/llvm/tests/perf-support.mjs +735 -0
  46. package/backends/llvm/tests/product-driver.c +23 -0
  47. package/backends/llvm/tests/projection-driver.c +19 -0
  48. package/backends/llvm/tests/relation-driver.c +29 -0
  49. package/backends/llvm/tests/test-llvm.mjs +240 -0
  50. package/backends/llvm/tests/union-driver.c +31 -0
  51. package/backends/llvm/tests/variant-driver.c +19 -0
  52. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  53. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  54. package/backends/wasm/README.md +97 -0
  55. package/backends/wasm/TECHNICAL-DEBT.md +60 -0
  56. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  57. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  58. package/backends/wasm/bin/k-wasm.mjs +49 -0
  59. package/backends/wasm/demo_ieee.mjs +319 -0
  60. package/backends/wasm/runtime.wat +102 -0
  61. package/backends/wasm/scripts/run-tests.mjs +41 -0
  62. package/backends/wasm/src/cli.mjs +133 -0
  63. package/backends/wasm/src/kvm2wasm.mjs +661 -0
  64. package/backends/wasm/src/wasm.mjs +1010 -0
  65. package/backends/wasm/tests/integration.sh +182 -0
  66. package/backends/wasm/tests/perf-support.mjs +336 -0
  67. package/backends/wasm/tests/test-wasm.mjs +617 -0
  68. package/browser/repl-browser.mjs +1584 -0
  69. package/browser/shims/assert.mjs +19 -0
  70. package/browser/shims/buffer.mjs +224 -0
  71. package/browser/shims/crypto.mjs +103 -0
  72. package/browser/shims/fs.mjs +159 -0
  73. package/browser/shims/os.mjs +17 -0
  74. package/browser/shims/path.mjs +54 -0
  75. package/browser/shims/process.mjs +44 -0
  76. package/browser/shims/readline.mjs +11 -0
  77. package/browser/shims/url.mjs +12 -0
  78. package/browser/vfs-data.mjs +19 -0
  79. package/codecs/README.md +3 -0
  80. package/codecs/json.mjs +16 -2
  81. package/codecs/runtime/show-value.mjs +2 -2
  82. package/conformance/helper-reuse/case.json +8 -0
  83. package/conformance/helper-reuse/expected.kv +1 -0
  84. package/conformance/helper-reuse/input.kv +1 -0
  85. package/conformance/helper-reuse/program.k +2 -0
  86. package/conformance/identity/case.json +8 -0
  87. package/conformance/identity/expected.kv +1 -0
  88. package/conformance/identity/input.kv +1 -0
  89. package/conformance/identity/program.k +1 -0
  90. package/conformance/product-projection/case.json +8 -0
  91. package/conformance/product-projection/expected.kv +1 -0
  92. package/conformance/product-projection/input.kv +1 -0
  93. package/conformance/product-projection/program.k +1 -0
  94. package/conformance/run.mjs +149 -0
  95. package/conformance/variant-construction/case.json +8 -0
  96. package/conformance/variant-construction/expected.kv +1 -0
  97. package/conformance/variant-construction/input.kv +1 -0
  98. package/conformance/variant-construction/program.k +1 -0
  99. package/harness/adapters/arm64.mjs +105 -0
  100. package/harness/adapters/kvm.mjs +144 -0
  101. package/harness/adapters/llvm.mjs +118 -0
  102. package/harness/adapters/native.mjs +77 -0
  103. package/harness/adapters/wasm.mjs +169 -0
  104. package/harness/backend.mjs +95 -0
  105. package/harness/cli.mjs +130 -0
  106. package/harness/format.mjs +95 -0
  107. package/harness/index.mjs +25 -0
  108. package/harness/persistent.mjs +121 -0
  109. package/harness/registry.mjs +44 -0
  110. package/harness/suite.mjs +257 -0
  111. package/harness/suites/poly.mjs +156 -0
  112. package/harness/trace.mjs +50 -0
  113. package/harness/util.mjs +92 -0
  114. package/intrinsics.mjs +41 -0
  115. package/kir.mjs +399 -0
  116. package/kvm.mjs +585 -81
  117. package/objects/README.md +37 -3
  118. package/objects/compile.mjs +15 -22
  119. package/objects/inspect.mjs +96 -0
  120. package/objects/validate.mjs +415 -0
  121. package/package.json +51 -19
  122. package/repl-codecs.mjs +133 -20
  123. package/repl.mjs +408 -59
  124. package/run.mjs +2 -7
  125. package/scripts/benchmark.mjs +406 -22
  126. package/scripts/build-repl-html.mjs +1784 -0
  127. package/scripts/perf-ieee.mjs +586 -0
  128. package/scripts/perf-int.mjs +531 -0
  129. package/scripts/perf-poly.mjs +945 -0
  130. package/scripts/render-benchmark.mjs +12 -8
  131. package/scripts/run-tests.mjs +6 -0
  132. package/tests/integration.sh +19 -2
  133. package/tests/test-arithmetics.mjs +54 -0
  134. package/tests/test-ieee-arithmetic.mjs +6 -2
  135. package/tests/test-kir.mjs +85 -0
  136. package/tests/test-kvm-polymorphic.mjs +278 -0
  137. package/tests/test-kvm.mjs +25 -6
  138. package/tests/test-poly.mjs +129 -0
  139. package/tests/test-repl-html.mjs +303 -0
  140. package/tests/test-repl.mjs +175 -1
  141. package/tests/test-validate-object.mjs +40 -0
  142. package/a.klib +0 -1
  143. package/a.kvm +0 -142676
  144. package/a.wasm +0 -0
  145. package/benchmark-results.html +0 -532
  146. package/benchmark-results.json +0 -480
  147. package/k-vscode/.vscode/launch.json +0 -14
  148. package/k-vscode/README.md +0 -75
  149. package/k-vscode/extension.js +0 -273
  150. package/k-vscode/language-configuration.json +0 -42
  151. package/k-vscode/package.json +0 -79
  152. package/k-vscode/snippets/k.json +0 -92
  153. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  154. package/k-vscode/test/background-ranges.test.js +0 -50
  155. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  156. 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,8 +36,13 @@ import {
28
36
  codeHashToPattern,
29
37
  listCodecs,
30
38
  loadCodecModule,
39
+ normalizeCodecModule,
40
+ registerCodec,
31
41
  resolveCodec,
32
- singletonPatternToCodeHash,
42
+ unregisterCodec,
43
+ BUILTIN_CODECS,
44
+ closedPatternToCodeHash,
45
+ UNIVERSAL_CODE,
33
46
  valueForCode
34
47
  } from "./repl-codecs.mjs";
35
48
 
@@ -38,13 +51,22 @@ const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
38
51
  const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
39
52
  const COMMAND_NAMES = [
40
53
  "help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
41
- "codes", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
54
+ "codes", "codecs", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
55
+ "timing", "time",
42
56
  "quit", "exit"
43
57
  ];
44
- const CODEC_COMMAND_NAMES = ["load", "list"];
58
+ const CODEC_COMMAND_NAMES = ["load", "unload", "list", "define"];
45
59
  const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
60
+ const INPUT_TYPE_NAME = "__input__";
46
61
  const initialCodes = codes.dump();
47
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
+
48
70
  function cloneJSON(value) {
49
71
  return JSON.parse(JSON.stringify(value));
50
72
  }
@@ -61,10 +83,13 @@ function createState() {
61
83
  relAliases: {},
62
84
  typeAliases: {},
63
85
  codecs: {},
86
+ loadedFiles: new Set(),
64
87
  pendingInput: null,
65
88
  meta: {},
66
89
  value: emptyValue(),
67
- lastMain: null
90
+ lastMain: null,
91
+ lastTiming: null,
92
+ showTiming: false
68
93
  };
69
94
  }
70
95
 
@@ -209,6 +234,16 @@ function mergeLibrary(state, lib, source = "<load>", options = {}) {
209
234
  }
210
235
  }
211
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
+ }
212
247
  }
213
248
  restoreCodes(state);
214
249
  }
@@ -323,14 +358,19 @@ function printValue(value, state = null) {
323
358
  const lines = [valueWithEnvelopeToK(value)];
324
359
  if (!state || value === undefined) return lines[0];
325
360
 
326
- const codeHash = singletonPatternToCodeHash(value.pattern);
361
+ const codeHash = closedPatternToCodeHash(value.pattern);
327
362
  if (!codeHash) return lines[0];
328
363
 
329
- for (const codec of state.codecs?.[codeHash] || []) {
364
+ const codecs = [
365
+ ...(state.codecs?.[codeHash] || []),
366
+ ...(state.codecs?.[UNIVERSAL_CODE] || [])
367
+ ];
368
+ for (const codec of codecs) {
330
369
  if (typeof codec.print !== "function") continue;
331
370
  try {
332
371
  lines.push(`${codec.name}: ${formatCodecOutput(codec.print(value, { codeHash, state }))}`);
333
372
  } catch (error) {
373
+ if (codec.universal) continue;
334
374
  lines.push(`${codec.name}: <error: ${error.message || String(error)}>`);
335
375
  }
336
376
  }
@@ -590,15 +630,33 @@ function codecLoadPathCompletionStart(argStart, arg) {
590
630
  return argStart + leading.length + "load".length + 1 + restLeadingWhitespace + completionTokenStart(rest.trimStart());
591
631
  }
592
632
 
593
- function completeCodecCommand(line, argStart, arg) {
633
+ function completeCodecCommand(line, argStart, arg, state) {
594
634
  const trimmed = arg.trimStart();
595
635
  if (trimmed === "" || !/\s/.test(trimmed)) {
596
636
  return completeCommandWord(line, argStart, arg, CODEC_COMMAND_NAMES);
597
637
  }
598
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
+
599
650
  const tokenStart = codecLoadPathCompletionStart(argStart, arg);
600
651
  if (tokenStart == null) return [[], line];
601
- 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];
602
660
  }
603
661
 
604
662
  function resolveTypeHash(state, rawName) {
@@ -609,6 +667,40 @@ function resolveTypeHash(state, rawName) {
609
667
  return hash;
610
668
  }
611
669
 
670
+ function isSimpleTypeReference(rawType) {
671
+ return /^\$?[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/.test(rawType.trim());
672
+ }
673
+
674
+ function resolveTypeExpressionHash(state, rawType) {
675
+ restoreCodes(state);
676
+ const preamble = aliasPreamble(state, INPUT_TYPE_NAME);
677
+ const source = [
678
+ preamble,
679
+ ensureSemicolon(`$ ${INPUT_TYPE_NAME} = ${rawType.trim()}`),
680
+ "()"
681
+ ].filter(Boolean).join("\n");
682
+
683
+ try {
684
+ const annotated = annotate(source, { libraries: [stateLibrary(state)] });
685
+ const hash = annotated.representatives[INPUT_TYPE_NAME];
686
+ if (!hash) throw new Error(":input type expression did not produce a code hash");
687
+ state.codes = codes.dump();
688
+ return hash;
689
+ } catch (error) {
690
+ restoreCodes(state);
691
+ throw remapError(error, preambleLineCount(preamble));
692
+ }
693
+ }
694
+
695
+ function resolveInputTypeHash(state, rawType) {
696
+ try {
697
+ return resolveTypeHash(state, rawType);
698
+ } catch (error) {
699
+ if (isSimpleTypeReference(rawType)) throw error;
700
+ }
701
+ return resolveTypeExpressionHash(state, rawType);
702
+ }
703
+
612
704
  function completeInputCommand(line, state, argStart, arg) {
613
705
  const match = arg.match(/^(\s*)(\S+)?(\s+)?(\S*)?$/);
614
706
  if (!match) return [[], line];
@@ -648,8 +740,8 @@ function completeCommandArgument(line, state) {
648
740
  const argStart = 1 + firstSpace + 1;
649
741
  const arg = line.slice(argStart);
650
742
 
651
- if (command === "codec") {
652
- return completeCodecCommand(line, argStart, arg);
743
+ if (command === "codec" || command === "codecs") {
744
+ return completeCodecCommand(line, argStart, arg, state);
653
745
  }
654
746
 
655
747
  if (command === "input") {
@@ -725,6 +817,102 @@ function parseLoadArgs(arg, usagePrefix = ":") {
725
817
  return { path: arg.trim(), loadAliases: true };
726
818
  }
727
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
+
728
916
  function lineForContinuation(line) {
729
917
  return line.replace(/\\\s*$/, "");
730
918
  }
@@ -842,29 +1030,82 @@ async function defineRelation(input, state) {
842
1030
  return [`${name} = ${hash}`];
843
1031
  }
844
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
+
845
1068
  async function runExpression(input, state) {
846
1069
  const expression = input.trim();
847
1070
  if (!expression) throw new Error(":run requires an expression");
848
1071
 
849
1072
  restoreCodes(state);
850
1073
  const preamble = aliasPreamble(state);
1074
+ const lineOffset = preambleLineCount(preamble);
851
1075
  const source = [preamble, expression].filter(Boolean).join("\n");
1076
+ const compileStart = performance.now();
852
1077
  let annotated;
853
1078
  try {
854
1079
  annotated = annotate(source, { libraries: [stateLibrary(state)] });
855
1080
  } catch (error) {
856
- throw remapError(error, preambleLineCount(preamble));
1081
+ throw remapError(error, lineOffset);
857
1082
  }
858
- const mainRel = annotated.rels.__main__;
859
- run.defs = annotated;
860
- let result;
1083
+ const snippetCompileMs = performance.now() - compileStart;
1084
+
1085
+ let execRes;
861
1086
  try {
862
- result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
1087
+ execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
863
1088
  } catch (error) {
864
- throw remapError(error, preambleLineCount(preamble));
1089
+ throw remapError(error, lineOffset);
865
1090
  }
1091
+ state.codes = codes.dump();
866
1092
  restoreCodes(state);
867
- 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;
868
1109
  }
869
1110
 
870
1111
  async function runSnippet(input, state, options = {}) {
@@ -873,53 +1114,133 @@ async function runSnippet(input, state, options = {}) {
873
1114
 
874
1115
  const explicitTerminated = options.explicitTerminated ?? explicitSnippetTerminated(input);
875
1116
  const analysis = analyzeAcceptedSnippet(snippet, explicitTerminated);
1117
+ const compileStart = performance.now();
876
1118
  const { annotated, lib, lineOffset } = compileSnippetArtifacts(snippet, state);
1119
+ const snippetCompileMs = performance.now() - compileStart;
877
1120
  mergeLibrary(state, lib, "<repl>");
878
1121
 
879
1122
  if (analysis.kind === "definitionsOnly") {
880
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
+ }
881
1134
  return [];
882
1135
  }
883
1136
 
884
- const mainRel = annotated.rels.__main__;
885
- run.defs = annotated;
886
- let result;
1137
+ let execRes;
887
1138
  try {
888
- result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
1139
+ execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
889
1140
  } catch (error) {
890
1141
  throw remapError(error, lineOffset);
891
1142
  }
1143
+ state.codes = codes.dump();
892
1144
  restoreCodes(state);
893
- 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;
894
1161
  }
895
1162
 
896
1163
  async function loadCodec(input, state) {
897
- 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+/);
898
1170
  switch (subcommand) {
899
1171
  case "load": {
900
1172
  const filePath = rest.join(" ");
901
- 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);
902
1175
  const registered = await loadCodecModule(state, expandHome(filePath));
903
- return registered.map(({ name, codeHash }) => `loaded codec ${name} for ${codeHash}`);
1176
+ return registered.map(({ name, 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}`
1206
+ );
904
1207
  }
905
1208
  case "list":
906
1209
  if (rest.length > 0) throw new Error(":codec list does not accept arguments");
907
1210
  return [listCodecs(state)];
908
- default:
909
- 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
+ }
910
1218
  }
911
1219
  }
912
1220
 
913
1221
  async function requestCodecInput(input, state) {
914
- const parts = input.trim().split(/\s+/).filter(Boolean);
915
- if (parts.length === 0 || parts.length > 2) {
916
- throw new Error(":input requires a singleton type and optional codec name");
1222
+ const trimmed = input.trim();
1223
+ if (!trimmed) {
1224
+ throw new Error(":input requires a type and optional codec name");
917
1225
  }
918
1226
 
919
- const codeHash = resolveTypeHash(state, parts[0]);
920
- const codecName = parts[1] || null;
921
- resolveCodec(state, codeHash, codecName, "parse");
922
- state.pendingInput = { codeHash, codecName };
1227
+ let codeHash;
1228
+ let codecName = null;
1229
+ try {
1230
+ codeHash = resolveInputTypeHash(state, trimmed);
1231
+ } catch (wholeError) {
1232
+ const split = trimmed.match(/^(.*\S)\s+([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)$/);
1233
+ if (!split) throw wholeError;
1234
+ try {
1235
+ codeHash = resolveInputTypeHash(state, split[1]);
1236
+ codecName = split[2];
1237
+ } catch (typeError) {
1238
+ throw typeError;
1239
+ }
1240
+ }
1241
+
1242
+ const codec = resolveCodec(state, codeHash, codecName, "parse");
1243
+ state.pendingInput = { codeHash, codecName, promptName: codec.name };
923
1244
  return [`input ${codeHash}${codecName ? ` using ${codecName}` : ""}: enter value text`];
924
1245
  }
925
1246
 
@@ -942,6 +1263,7 @@ async function consumeCodecInput(input, state) {
942
1263
  }
943
1264
 
944
1265
  async function evaluateInput(input, state) {
1266
+ state.lastTiming = null;
945
1267
  if (state.pendingInput) {
946
1268
  return consumeCodecInput(input, state);
947
1269
  }
@@ -972,6 +1294,29 @@ async function evaluateCommand(line, state) {
972
1294
  exit(0);
973
1295
  case "help":
974
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
+ }
975
1320
  case "type": {
976
1321
  if (!arg) throw new Error(":type requires a name or definition");
977
1322
  if (arg.includes("=")) {
@@ -999,6 +1344,7 @@ async function evaluateCommand(line, state) {
999
1344
  JSON.stringify(state.value, null, 2)
1000
1345
  ];
1001
1346
  case "codec":
1347
+ case "codecs":
1002
1348
  return loadCodec(arg, state);
1003
1349
  case "input":
1004
1350
  return requestCodecInput(arg, state);
@@ -1025,26 +1371,7 @@ async function evaluateCommand(line, state) {
1025
1371
  }
1026
1372
  case "load": {
1027
1373
  const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
1028
- if (loadPath.endsWith(".klib")) {
1029
- const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
1030
- mergeLibrary(state, lib, loadPath, { loadAliases });
1031
- } else {
1032
- restoreCodes(state);
1033
- const source = fs.readFileSync(loadPath, "utf8");
1034
- const preamble = aliasPreamble(state);
1035
- const fullSource = [preamble, source].filter(Boolean).join("\n");
1036
- const options = {
1037
- source: loadPath,
1038
- libraries: [stateLibrary(state)]
1039
- };
1040
- try {
1041
- const lib = compileWithOptionalIdentity(fullSource, options);
1042
- const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
1043
- mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
1044
- } catch (error) {
1045
- throw remapError(error, preambleLineCount(preamble));
1046
- }
1047
- }
1374
+ loadSourceOrKlib(state, loadPath, { loadAliases });
1048
1375
  return [`loaded ${loadPath}`];
1049
1376
  }
1050
1377
  case "t": {
@@ -1075,13 +1402,17 @@ function helpText() {
1075
1402
  ":type name = <...> define a type",
1076
1403
  ":rel name = expr define a relation",
1077
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",
1078
1407
  ":t name show relation type",
1079
1408
  ":d name show relation definition",
1080
1409
  ":type name show type definition",
1081
1410
  ":codes list type aliases",
1082
1411
  ":rels list relation aliases",
1083
- ":codec load file load a REPL codec module",
1084
- ":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)",
1085
1416
  ":input type [codec] read next line as codec input",
1086
1417
  ":load [--no-alias] file",
1087
1418
  " load .k source or .klib",
@@ -1097,6 +1428,11 @@ function helpText() {
1097
1428
  ].join("\n");
1098
1429
  }
1099
1430
 
1431
+ function promptForState(state) {
1432
+ const promptName = state.pendingInput?.promptName;
1433
+ return promptName ? `${promptName}> ` : "> ";
1434
+ }
1435
+
1100
1436
  function cliUsage() {
1101
1437
  console.log("Usage: k-repl");
1102
1438
  console.log(" k-repl -h");
@@ -1151,6 +1487,7 @@ function startRepl() {
1151
1487
  } catch (error) {
1152
1488
  console.error(error.message || String(error));
1153
1489
  }
1490
+ rl.setPrompt(promptForState(state));
1154
1491
  if (!closed) rl.prompt();
1155
1492
  return;
1156
1493
  }
@@ -1222,7 +1559,19 @@ export {
1222
1559
  isMainEntrypoint,
1223
1560
  lineHasExplicitContinuation,
1224
1561
  lineTerminatesSnippet,
1562
+ listCodecs,
1563
+ loadCodecModule,
1564
+ registerCodec,
1565
+ unregisterCodec,
1566
+ resolveCodec,
1567
+ codeHashToPattern,
1568
+ BUILTIN_CODECS,
1225
1569
  printValue,
1570
+ promptForState,
1226
1571
  propertyListToFilter,
1227
- valueToK
1572
+ savedLibrary,
1573
+ loadSourceOrKlib,
1574
+ ensureCodecDependencies,
1575
+ valueToK,
1576
+ formatDuration
1228
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();