@fraczak/k 6.1.1 → 6.1.3

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 (79) hide show
  1. package/.codex +0 -0
  2. package/Code-derivation-tests/pattern-envelope.mjs +26 -0
  3. package/DOCS/BIG_PICTURE.md +10 -10
  4. package/DOCS/DIVERGENCE_DETECTION.md +1 -1
  5. package/DOCS/REPL.md +33 -0
  6. package/DOCS/TYPE_DERIVATION.md +12 -12
  7. package/DOCS/a.html +398 -0
  8. package/DOCS/book/01-introduction.md +2 -2
  9. package/DOCS/book/02-syntax-and-values.md +8 -8
  10. package/DOCS/book/03-types-as-finite-automata.md +2 -2
  11. package/DOCS/book/04-partial-functions-and-composition.md +2 -2
  12. package/DOCS/book/05-typing-filters-and-normalization.md +3 -3
  13. package/DOCS/book/06-values-in-memory.md +3 -3
  14. package/DOCS/book/07-the-partial-function-abi.md +1 -1
  15. package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
  16. package/DOCS/book/09-compiler-architecture.md +2 -2
  17. package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +1 -0
  18. package/DOCS/dependency-graph.html +140 -0
  19. package/DOCS/dependency-graph.md +144 -0
  20. package/NAT.k~ +452 -0
  21. package/README.md +7 -8
  22. package/UU/ta.k +1 -0
  23. package/UU/ta.k~ +0 -0
  24. package/UU/tb.k +0 -0
  25. package/UU/tc.k +0 -0
  26. package/a.k +138 -0
  27. package/a.klib +1 -0
  28. package/codecs/BINARY_FORMAT.html +471 -0
  29. package/codecs/BINARY_FORMAT.md +1 -1
  30. package/codecs/POLYMORPHIC_BINARY_FORMAT.html +355 -0
  31. package/codecs/POLYMORPHIC_BINARY_FORMAT.md +1 -1
  32. package/codecs/ieee.mjs +19 -4
  33. package/codecs/int.mjs +11 -4
  34. package/codecs/json.mjs +7 -4
  35. package/codecs/k-parse.mjs +7 -4
  36. package/codecs/k-print.mjs +7 -4
  37. package/codecs/runtime/cli-entry.mjs +13 -0
  38. package/codecs/show.mjs +7 -4
  39. package/codecs/unit.mjs +23 -8
  40. package/codecs/utf16.mjs +27 -5
  41. package/codecs/utf8.mjs +27 -5
  42. package/codes.mjs +14 -33
  43. package/core.json +1 -0
  44. package/core.klib +1 -0
  45. package/core.klib~ +0 -0
  46. package/export.mjs +3 -3
  47. package/ieee.klib +1 -0
  48. package/k-vscode/package-lock.json +4176 -0
  49. package/kkk.k +197 -0
  50. package/kkk.klib +0 -0
  51. package/kkk.klib~ +0 -0
  52. package/nak.klib +1 -0
  53. package/nak2.klib +1 -0
  54. package/nat.k +10 -0
  55. package/nat2.k +9 -0
  56. package/object.mjs +20 -7
  57. package/p.json +5475 -0
  58. package/package.json +3 -2
  59. package/repl-codecs.mjs +173 -0
  60. package/repl.mjs +215 -5
  61. package/run.mjs +16 -2
  62. package/scripts/benchmark-idempotency.mjs +72 -0
  63. package/test-ieee-arithmetic.mjs +93 -0
  64. package/test-k-object.mjs +19 -0
  65. package/test-repl.mjs +60 -1
  66. package/tests.sh +10 -1
  67. package/type_registry/registry.mjs~ +105 -0
  68. package/type_registry/typeParser.jison~ +132 -0
  69. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  70. package/.idea/copilot.data.migration.agent.xml +0 -6
  71. package/.idea/copilot.data.migration.ask.xml +0 -6
  72. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  73. package/.idea/copilot.data.migration.edit.xml +0 -6
  74. package/.idea/k.iml +0 -9
  75. package/.idea/misc.xml +0 -6
  76. package/.idea/modules.xml +0 -8
  77. package/.idea/vcs.xml +0 -6
  78. package/k_compiler/runtime.c +0 -205
  79. package/k_compiler/runtime.h +0 -34
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
4
4
  "description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
7
7
  "prepare": "jiwson -m es6 parser.jison -o parser.mjs; jiwson -m es6 valueParser.jison -o valueParser.mjs; ",
8
- "test": "node test.mjs; for f in Code-derivation-tests/*.mjs; do echo $f; node $f; done; node test-fingerprint.mjs; node test-hash-fuzz.mjs; node test-hash-normalization.mjs; node test-k-object.mjs; node test-repl.mjs; ./tests.sh",
8
+ "test": "node test.mjs; for f in Code-derivation-tests/*.mjs; do echo $f; node $f; done; node test-fingerprint.mjs; node test-hash-fuzz.mjs; node test-hash-normalization.mjs; node test-k-object.mjs; node test-repl.mjs; node test-ieee-arithmetic.mjs; ./tests.sh",
9
+ "perf:idempotency": "node scripts/benchmark-idempotency.mjs",
9
10
  "deps:graph": "node scripts/generate-dependency-graph.mjs"
10
11
  },
11
12
  "repository": {
@@ -0,0 +1,173 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ import { encodeToWire, decodeWire } from "./codecs/runtime/prefix-codec.mjs";
6
+ import { deriveClosedPattern } from "./codecs/runtime/codec.mjs";
7
+ import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
8
+ import { finalize } from "./codes.mjs";
9
+
10
+ function isCanonicalCodeName(name) {
11
+ return typeof name === "string" && name.startsWith("@");
12
+ }
13
+
14
+ function singletonPatternToCodeHash(pattern) {
15
+ if (!Array.isArray(pattern) || pattern.length === 0) {
16
+ return null;
17
+ }
18
+
19
+ const codeDefs = {};
20
+ for (let i = 0; i < pattern.length; i++) {
21
+ const [kind, edges] = pattern[i] || [];
22
+ if (kind !== "closed-product" && kind !== "closed-union") {
23
+ return null;
24
+ }
25
+ if (!Array.isArray(edges)) {
26
+ throw new Error(`Invalid singleton pattern node ${i}`);
27
+ }
28
+
29
+ const code = kind === "closed-product" ? "product" : "union";
30
+ codeDefs[`C${i}`] = {
31
+ code,
32
+ [code]: Object.fromEntries(edges.map(([label, target]) => {
33
+ if (!Number.isInteger(target) || target < 0 || target >= pattern.length) {
34
+ throw new Error(`Invalid singleton pattern edge target ${target}`);
35
+ }
36
+ return [label, `C${target}`];
37
+ }))
38
+ };
39
+ }
40
+
41
+ return finalize(codeDefs).representatives.C0 || null;
42
+ }
43
+
44
+ function codeHashToPattern(codeHash, findCode) {
45
+ const code = findCode(codeHash);
46
+ if (!code || code.code === "undefined") {
47
+ throw new Error(`Unknown type '${codeHash}'`);
48
+ }
49
+ const resolveType = (name) => {
50
+ const resolved = findCode(name);
51
+ if (!resolved || resolved.code === "undefined") {
52
+ throw new Error(`Unknown type '${name}'`);
53
+ }
54
+ return resolved;
55
+ };
56
+ return patternToPropertyList(deriveClosedPattern(codeHash, code, resolveType));
57
+ }
58
+
59
+ function valueForCode(value, codeHash, findCode) {
60
+ const pattern = codeHashToPattern(codeHash, findCode);
61
+ return decodeWire(encodeToWire(value, pattern)).value;
62
+ }
63
+
64
+ function normalizeCodecModule(mod, fallbackName) {
65
+ const codec = mod.replCodec || mod.codec || mod.default || mod;
66
+ const name = codec.name || mod.name || fallbackName;
67
+ const patterns = codec.patterns || (codec.pattern ? [codec.pattern] : null);
68
+ const codes = codec.codes || codec.codeHashes || (codec.code ? [codec.code] : null) ||
69
+ patterns?.map((pattern, index) => {
70
+ const codeHash = singletonPatternToCodeHash(pattern);
71
+ if (!codeHash) throw new Error(`Codec '${name}' pattern ${index} is not singleton`);
72
+ return codeHash;
73
+ });
74
+ if (!name || typeof name !== "string") {
75
+ throw new Error("Codec module must export a string name");
76
+ }
77
+ if (!Array.isArray(codes) || codes.length === 0) {
78
+ throw new Error(`Codec '${name}' must export a non-empty codes array`);
79
+ }
80
+ if (codec.parse != null && typeof codec.parse !== "function") {
81
+ throw new Error(`Codec '${name}' parse export must be a function`);
82
+ }
83
+ if (codec.print != null && typeof codec.print !== "function") {
84
+ throw new Error(`Codec '${name}' print export must be a function`);
85
+ }
86
+ return {
87
+ name,
88
+ codes,
89
+ parse: codec.parse,
90
+ print: codec.print
91
+ };
92
+ }
93
+
94
+ function codecStore(state) {
95
+ if (!state.codecs) state.codecs = {};
96
+ return state.codecs;
97
+ }
98
+
99
+ function registerCodec(state, codec, source = null) {
100
+ const registered = [];
101
+ const store = codecStore(state);
102
+ for (const codeHash of codec.codes) {
103
+ if (!isCanonicalCodeName(codeHash)) {
104
+ throw new Error(`Codec '${codec.name}' has non-canonical code '${codeHash}'`);
105
+ }
106
+ if (!store[codeHash]) store[codeHash] = [];
107
+ const entry = {
108
+ name: codec.name,
109
+ source,
110
+ parse: codec.parse,
111
+ print: codec.print
112
+ };
113
+ const existingIndex = store[codeHash].findIndex((existing) =>
114
+ existing.name === entry.name && existing.source === entry.source
115
+ );
116
+ if (existingIndex === -1) {
117
+ store[codeHash].push(entry);
118
+ } else {
119
+ store[codeHash][existingIndex] = entry;
120
+ }
121
+ registered.push({ codeHash, ...entry });
122
+ }
123
+ return registered;
124
+ }
125
+
126
+ async function loadCodecModule(state, filePath) {
127
+ const resolved = path.resolve(filePath);
128
+ const url = pathToFileURL(resolved);
129
+ url.searchParams.set("mtime", String(fs.statSync(resolved).mtimeMs));
130
+ const mod = await import(url.href);
131
+ const codec = normalizeCodecModule(mod, path.basename(resolved, path.extname(resolved)));
132
+ return registerCodec(state, codec, resolved);
133
+ }
134
+
135
+ function listCodecs(state) {
136
+ const entries = Object.entries(codecStore(state))
137
+ .flatMap(([codeHash, codecs]) => codecs.map((codec) => ({ codeHash, ...codec })))
138
+ .sort((a, b) => a.name.localeCompare(b.name) || a.codeHash.localeCompare(b.codeHash));
139
+ if (entries.length === 0) return "(none)";
140
+ return entries.map((entry) => `${entry.name} ${entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
141
+ }
142
+
143
+ function codecNames(state, codeHash = null) {
144
+ const store = codecStore(state);
145
+ const codecs = codeHash ? (store[codeHash] || []) : Object.values(store).flat();
146
+ return [...new Set(codecs.map((codec) => codec.name))].sort();
147
+ }
148
+
149
+ function resolveCodec(state, codeHash, codecName = null, capability = null) {
150
+ const matches = (codecStore(state)[codeHash] || []).filter((codec) =>
151
+ (codecName == null || codec.name === codecName) &&
152
+ (capability == null || typeof codec[capability] === "function")
153
+ );
154
+ if (matches.length === 0) {
155
+ const suffix = codecName ? ` named '${codecName}'` : "";
156
+ throw new Error(`No ${capability || "usable"} codec${suffix} for ${codeHash}`);
157
+ }
158
+ if (matches.length > 1 && codecName == null) {
159
+ throw new Error(`Multiple codecs for ${codeHash}; specify one of: ${matches.map((codec) => codec.name).join(", ")}`);
160
+ }
161
+ return matches[0];
162
+ }
163
+
164
+ export {
165
+ codecNames,
166
+ codeHashToPattern,
167
+ listCodecs,
168
+ loadCodecModule,
169
+ registerCodec,
170
+ resolveCodec,
171
+ singletonPatternToCodeHash,
172
+ valueForCode
173
+ };
package/repl.mjs CHANGED
@@ -23,15 +23,25 @@ import {
23
23
  prettyRelation
24
24
  } from "./object.mjs";
25
25
  import { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
26
+ import {
27
+ codecNames,
28
+ codeHashToPattern,
29
+ listCodecs,
30
+ loadCodecModule,
31
+ resolveCodec,
32
+ singletonPatternToCodeHash,
33
+ valueForCode
34
+ } from "./repl-codecs.mjs";
26
35
 
27
36
  const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
28
37
  const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
29
38
  const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
30
39
  const COMMAND_NAMES = [
31
40
  "help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
32
- "codes", "rels", "val", "reset", "klib", "ko", "load",
41
+ "codes", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
33
42
  "quit", "exit"
34
43
  ];
44
+ const CODEC_COMMAND_NAMES = ["load", "list"];
35
45
  const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
36
46
  const initialCodes = codes.dump();
37
47
 
@@ -50,6 +60,8 @@ function createState() {
50
60
  rels: {},
51
61
  relAliases: {},
52
62
  typeAliases: {},
63
+ codecs: {},
64
+ pendingInput: null,
53
65
  meta: {},
54
66
  value: emptyValue(),
55
67
  lastMain: null
@@ -261,8 +273,27 @@ function listAliases(aliases) {
261
273
  return entries.map(([name, hash]) => `${name} = ${hash}`).join("\n");
262
274
  }
263
275
 
264
- function printValue(value) {
265
- return valueWithEnvelopeToK(value);
276
+ function formatCodecOutput(output) {
277
+ if (Buffer.isBuffer(output)) return output.toString("utf8");
278
+ return String(output);
279
+ }
280
+
281
+ function printValue(value, state = null) {
282
+ const lines = [valueWithEnvelopeToK(value)];
283
+ if (!state || value === undefined) return lines[0];
284
+
285
+ const codeHash = singletonPatternToCodeHash(value.pattern);
286
+ if (!codeHash) return lines[0];
287
+
288
+ for (const codec of state.codecs?.[codeHash] || []) {
289
+ if (typeof codec.print !== "function") continue;
290
+ try {
291
+ lines.push(`${codec.name}: ${formatCodecOutput(codec.print(value, { codeHash, state }))}`);
292
+ } catch (error) {
293
+ lines.push(`${codec.name}: <error: ${error.message || String(error)}>`);
294
+ }
295
+ }
296
+ return lines.join("\n");
266
297
  }
267
298
 
268
299
  function commitResult(state, result, lastMain) {
@@ -270,7 +301,7 @@ function commitResult(state, result, lastMain) {
270
301
  state.value = result;
271
302
  state.lastMain = lastMain;
272
303
  }
273
- return [printValue(result)];
304
+ return [printValue(result, state)];
274
305
  }
275
306
 
276
307
  function userDefinedNames(source) {
@@ -348,6 +379,13 @@ function canonicalNames(state) {
348
379
  ])].filter((name) => name.startsWith("@")).sort();
349
380
  }
350
381
 
382
+ function canonicalCodeNames(state) {
383
+ return [...new Set([
384
+ ...Object.keys(state.codes),
385
+ ...Object.values(state.typeAliases)
386
+ ])].filter((name) => name.startsWith("@")).sort();
387
+ }
388
+
351
389
  function aliasNames(state) {
352
390
  return [...new Set([
353
391
  ...Object.keys(state.typeAliases),
@@ -397,6 +435,16 @@ function completeCanonical(line, state) {
397
435
  return [matches, line];
398
436
  }
399
437
 
438
+ function completeCanonicalCode(line, state) {
439
+ const match = line.match(/^(.*?)(@[A-Za-z0-9_?!+-]*)$/);
440
+ if (!match) return [[], line];
441
+ const [, prefix, partial] = match;
442
+ const matches = canonicalCodeNames(state)
443
+ .filter((name) => name.startsWith(partial))
444
+ .map((name) => `${prefix}${name}`);
445
+ return [matches, line];
446
+ }
447
+
400
448
  function completeIdentifier(line, state) {
401
449
  const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
402
450
  if (!match) return [[], line];
@@ -410,6 +458,19 @@ function completeIdentifier(line, state) {
410
458
  return [matches, line];
411
459
  }
412
460
 
461
+ function completeTypeIdentifier(line, state) {
462
+ const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
463
+ if (!match) return [[], line];
464
+ const [, prefix, partial] = match;
465
+ const isTypeToken = partial.startsWith("$");
466
+ const barePartial = isTypeToken ? partial.slice(1) : partial;
467
+ const matches = Object.keys(state.typeAliases)
468
+ .sort()
469
+ .filter((name) => name.startsWith(barePartial))
470
+ .map((name) => `${prefix}${isTypeToken ? "$" : ""}${name}`);
471
+ return [matches, line];
472
+ }
473
+
413
474
  function completePath(line, tokenStart) {
414
475
  const token = line.slice(tokenStart);
415
476
  const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
@@ -468,6 +529,75 @@ function loadPathCompletionStart(argStart, arg) {
468
529
  return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
469
530
  }
470
531
 
532
+ function completeCommandWord(line, argStart, arg, words) {
533
+ const leadingWhitespace = arg.length - arg.trimStart().length;
534
+ const trimmed = arg.trimStart();
535
+ if (/\s/.test(trimmed)) return [[], line];
536
+ const prefix = line.slice(0, argStart + leadingWhitespace);
537
+ const matches = words
538
+ .filter((word) => word.startsWith(trimmed))
539
+ .map((word) => `${prefix}${word}`);
540
+ return [matches, line];
541
+ }
542
+
543
+ function codecLoadPathCompletionStart(argStart, arg) {
544
+ const match = arg.match(/^(\s*)load(?:\s+(.*))?$/);
545
+ if (!match) return null;
546
+ if (match[2] == null) return null;
547
+ const [, leading, rest] = match;
548
+ const restLeadingWhitespace = rest.length - rest.trimStart().length;
549
+ return argStart + leading.length + "load".length + 1 + restLeadingWhitespace + completionTokenStart(rest.trimStart());
550
+ }
551
+
552
+ function completeCodecCommand(line, argStart, arg) {
553
+ const trimmed = arg.trimStart();
554
+ if (trimmed === "" || !/\s/.test(trimmed)) {
555
+ return completeCommandWord(line, argStart, arg, CODEC_COMMAND_NAMES);
556
+ }
557
+
558
+ const tokenStart = codecLoadPathCompletionStart(argStart, arg);
559
+ if (tokenStart == null) return [[], line];
560
+ return completePath(line, tokenStart);
561
+ }
562
+
563
+ function resolveTypeHash(state, rawName) {
564
+ const token = rawName.trim();
565
+ const bareName = token.startsWith("$") ? token.slice(1) : token;
566
+ const hash = state.typeAliases[bareName] || (token.startsWith("@") ? token : null);
567
+ if (!hash || !(hash in state.codes)) throw new Error(`Unknown type '${rawName}'`);
568
+ return hash;
569
+ }
570
+
571
+ function completeInputCommand(line, state, argStart, arg) {
572
+ const match = arg.match(/^(\s*)(\S+)?(\s+)?(\S*)?$/);
573
+ if (!match) return [[], line];
574
+ const [, leading, typeToken = "", afterTypeWhitespace = "", codecPartial = ""] = match;
575
+
576
+ if (!typeToken || !afterTypeWhitespace) {
577
+ if (/@[A-Za-z0-9_?!+-]*$/.test(line)) return completeCanonicalCode(line, state);
578
+ return completeTypeIdentifier(line, state);
579
+ }
580
+
581
+ if (typeToken) {
582
+ const prefix = line.slice(0, argStart + leading.length + typeToken.length + afterTypeWhitespace.length);
583
+ const names = (() => {
584
+ try {
585
+ return codecNames(state, resolveTypeHash(state, typeToken));
586
+ } catch {
587
+ return codecNames(state);
588
+ }
589
+ })();
590
+ return [
591
+ names
592
+ .filter((name) => name.startsWith(codecPartial))
593
+ .map((name) => `${prefix}${name}`),
594
+ line
595
+ ];
596
+ }
597
+
598
+ return [[], line];
599
+ }
600
+
471
601
  function completeCommandArgument(line, state) {
472
602
  const body = line.slice(1);
473
603
  const firstSpace = body.search(/\s/);
@@ -477,6 +607,14 @@ function completeCommandArgument(line, state) {
477
607
  const argStart = 1 + firstSpace + 1;
478
608
  const arg = line.slice(argStart);
479
609
 
610
+ if (command === "codec") {
611
+ return completeCodecCommand(line, argStart, arg);
612
+ }
613
+
614
+ if (command === "input") {
615
+ return completeInputCommand(line, state, argStart, arg);
616
+ }
617
+
480
618
  if (PATH_COMMANDS.has(command)) {
481
619
  const tokenStart = command === "load"
482
620
  ? loadPathCompletionStart(argStart, arg)
@@ -714,7 +852,59 @@ async function runSnippet(input, state, options = {}) {
714
852
  return commitResult(state, result, snippet);
715
853
  }
716
854
 
855
+ async function loadCodec(input, state) {
856
+ const [subcommand, ...rest] = input.trim().split(/\s+/);
857
+ switch (subcommand) {
858
+ case "load": {
859
+ const filePath = rest.join(" ");
860
+ if (!filePath) throw new Error(":codec load requires a file path");
861
+ const registered = await loadCodecModule(state, expandHome(filePath));
862
+ return registered.map(({ name, codeHash }) => `loaded codec ${name} for ${codeHash}`);
863
+ }
864
+ case "list":
865
+ if (rest.length > 0) throw new Error(":codec list does not accept arguments");
866
+ return [listCodecs(state)];
867
+ default:
868
+ throw new Error(":codec requires 'load' or 'list'");
869
+ }
870
+ }
871
+
872
+ async function requestCodecInput(input, state) {
873
+ const parts = input.trim().split(/\s+/).filter(Boolean);
874
+ if (parts.length === 0 || parts.length > 2) {
875
+ throw new Error(":input requires a singleton type and optional codec name");
876
+ }
877
+
878
+ const codeHash = resolveTypeHash(state, parts[0]);
879
+ const codecName = parts[1] || null;
880
+ resolveCodec(state, codeHash, codecName, "parse");
881
+ state.pendingInput = { codeHash, codecName };
882
+ return [`input ${codeHash}${codecName ? ` using ${codecName}` : ""}: enter value text`];
883
+ }
884
+
885
+ async function consumeCodecInput(input, state) {
886
+ const pending = state.pendingInput;
887
+ if (!pending) return null;
888
+ state.pendingInput = null;
889
+
890
+ restoreCodes(state);
891
+ const codec = resolveCodec(state, pending.codeHash, pending.codecName, "parse");
892
+ const pattern = codeHashToPattern(pending.codeHash, codes.find);
893
+ const parsed = await codec.parse(input, {
894
+ codeHash: pending.codeHash,
895
+ pattern,
896
+ state
897
+ });
898
+ const value = valueForCode(parsed, pending.codeHash, codes.find);
899
+ state.value = value;
900
+ return [printValue(value, state)];
901
+ }
902
+
717
903
  async function evaluateInput(input, state) {
904
+ if (state.pendingInput) {
905
+ return consumeCodecInput(input, state);
906
+ }
907
+
718
908
  const line = input.trim();
719
909
  if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
720
910
 
@@ -764,9 +954,13 @@ async function evaluateCommand(line, state) {
764
954
  return [listAliases(state.relAliases)];
765
955
  case "val":
766
956
  return [
767
- printValue(state.value),
957
+ printValue(state.value, state),
768
958
  JSON.stringify(state.value, null, 2)
769
959
  ];
960
+ case "codec":
961
+ return loadCodec(arg, state);
962
+ case "input":
963
+ return requestCodecInput(arg, state);
770
964
  case "reset": {
771
965
  const fresh = createState();
772
966
  Object.assign(state, fresh);
@@ -845,6 +1039,9 @@ function helpText() {
845
1039
  ":type name show type definition",
846
1040
  ":codes list type aliases",
847
1041
  ":rels list relation aliases",
1042
+ ":codec load file load a REPL codec module",
1043
+ ":codec list list loaded codecs",
1044
+ ":input type [codec] read next line as codec input",
848
1045
  ":load [--no-alias] file",
849
1046
  " load .k source or .klib",
850
1047
  ":klib file export state as a library",
@@ -884,6 +1081,19 @@ function startRepl() {
884
1081
  console.log("k interpreter (.klib-backed). Type :help for commands.");
885
1082
  rl.prompt();
886
1083
  async function handleLine(line) {
1084
+ if (state.pendingInput && buffer.length === 0) {
1085
+ rl.setPrompt("> ");
1086
+ try {
1087
+ for (const output of await consumeCodecInput(line, state)) {
1088
+ console.log(output);
1089
+ }
1090
+ } catch (error) {
1091
+ console.error(error.message || String(error));
1092
+ }
1093
+ if (!closed) rl.prompt();
1094
+ return;
1095
+ }
1096
+
887
1097
  if (lineHasExplicitContinuation(line)) {
888
1098
  buffer.push(lineForContinuation(line));
889
1099
  rl.setPrompt(" ");
package/run.mjs CHANGED
@@ -122,6 +122,20 @@ function constrainWithPattern(value, constraint, exp) {
122
122
  return withPattern(value, pattern);
123
123
  }
124
124
 
125
+ function projectionPattern(staticOutputPattern, valuePattern, label, exp) {
126
+ const dynamicOutputPattern = edgeSubpattern(valuePattern, label);
127
+ const pattern = intersectPatterns(staticOutputPattern, dynamicOutputPattern);
128
+ if (!pattern && staticOutputPattern && dynamicOutputPattern) {
129
+ throw new TypeError(
130
+ `Type Error in '${exp.op}'${expLocation(exp)}\n` +
131
+ ` - Projected value envelope does not intersect expression output pattern.\n` +
132
+ ` - output pattern: ${patternPreview(staticOutputPattern)}\n` +
133
+ ` - projected envelope: ${patternPreview(dynamicOutputPattern)}`
134
+ );
135
+ }
136
+ return pattern;
137
+ }
138
+
125
139
  function compiledExp(findCode, exp, typePatternGraph) {
126
140
  const cached = exp._compiledRun;
127
141
  if (cached?.findCode === findCode && cached?.typePatternGraph === typePatternGraph && cached?.defs === run.defs) {
@@ -188,13 +202,13 @@ function compiledExp(findCode, exp, typePatternGraph) {
188
202
  case "dot":
189
203
  fn = (value) => {
190
204
  if (value === undefined) return undefined;
191
- return withPattern(value.product[exp.dot], staticOutputPattern || edgeSubpattern(value.pattern, exp.dot));
205
+ return withPattern(value.product[exp.dot], projectionPattern(staticOutputPattern, value.pattern, exp.dot, exp));
192
206
  };
193
207
  break;
194
208
  case "div":
195
209
  fn = (value) => {
196
210
  if (value === undefined || value.tag !== exp.div) return undefined;
197
- return withPattern(value.value, staticOutputPattern || edgeSubpattern(value.pattern, exp.div));
211
+ return withPattern(value.value, projectionPattern(staticOutputPattern, value.pattern, exp.div, exp));
198
212
  };
199
213
  break;
200
214
  case "comp": {
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { mkdtempSync, statSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { performance } from "node:perf_hooks";
8
+
9
+ const DEFAULT_EXAMPLES = [
10
+ "Examples/nat.k",
11
+ "Examples/byte.k",
12
+ "Examples/bnat.k",
13
+ "Examples/arithmetics.k",
14
+ "Examples/ieee.k"
15
+ ];
16
+
17
+ const iterations = Number(process.env.K_BENCH_ITERATIONS || 5);
18
+ const examples = process.argv.slice(2);
19
+ const benchmarkExamples = examples.length > 0 ? examples : DEFAULT_EXAMPLES;
20
+
21
+ function run(args) {
22
+ const result = spawnSync("node", args, {
23
+ cwd: process.cwd(),
24
+ encoding: "utf8",
25
+ stdio: ["ignore", "pipe", "pipe"]
26
+ });
27
+ if (result.status !== 0) {
28
+ throw new Error(`${args.join(" ")} failed\n${result.stderr || result.stdout}`);
29
+ }
30
+ }
31
+
32
+ function median(values) {
33
+ const sorted = [...values].sort((a, b) => a - b);
34
+ return sorted[Math.floor(sorted.length / 2)];
35
+ }
36
+
37
+ function formatMs(value) {
38
+ return `${Math.round(value)} ms`;
39
+ }
40
+
41
+ function formatBytes(value) {
42
+ if (value >= 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
43
+ if (value >= 1024) return `${Math.round(value / 1024)} KiB`;
44
+ return `${value} B`;
45
+ }
46
+
47
+ console.log(`compile-lib/decompile round-trip benchmark (${iterations} iterations)`);
48
+ console.log("example, median, min, max, object");
49
+
50
+ for (const example of benchmarkExamples) {
51
+ const times = [];
52
+ let size = 0;
53
+
54
+ for (let i = 0; i < iterations; i++) {
55
+ const out = mkdtempSync(join(tmpdir(), `k-idempotency-bench-${example.replaceAll("/", "_")}-`));
56
+ const t0 = performance.now();
57
+ run(["objects/compile-lib.mjs", example, join(out, "1.klib")]);
58
+ run(["objects/decompile.mjs", join(out, "1.klib"), join(out, "1.k")]);
59
+ run(["objects/compile-lib.mjs", join(out, "1.k"), join(out, "2.klib")]);
60
+ run(["objects/decompile.mjs", join(out, "2.klib"), join(out, "2.k")]);
61
+ times.push(performance.now() - t0);
62
+ size = statSync(join(out, "1.klib")).size;
63
+ }
64
+
65
+ console.log([
66
+ example,
67
+ formatMs(median(times)),
68
+ formatMs(Math.min(...times)),
69
+ formatMs(Math.max(...times)),
70
+ formatBytes(size)
71
+ ].join(", "));
72
+ }