@fraczak/k 6.3.3 → 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 (155) 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 +61 -4
  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/runtime/show-value.mjs +2 -2
  81. package/conformance/helper-reuse/case.json +8 -0
  82. package/conformance/helper-reuse/expected.kv +1 -0
  83. package/conformance/helper-reuse/input.kv +1 -0
  84. package/conformance/helper-reuse/program.k +2 -0
  85. package/conformance/identity/case.json +8 -0
  86. package/conformance/identity/expected.kv +1 -0
  87. package/conformance/identity/input.kv +1 -0
  88. package/conformance/identity/program.k +1 -0
  89. package/conformance/product-projection/case.json +8 -0
  90. package/conformance/product-projection/expected.kv +1 -0
  91. package/conformance/product-projection/input.kv +1 -0
  92. package/conformance/product-projection/program.k +1 -0
  93. package/conformance/run.mjs +149 -0
  94. package/conformance/variant-construction/case.json +8 -0
  95. package/conformance/variant-construction/expected.kv +1 -0
  96. package/conformance/variant-construction/input.kv +1 -0
  97. package/conformance/variant-construction/program.k +1 -0
  98. package/harness/adapters/arm64.mjs +105 -0
  99. package/harness/adapters/kvm.mjs +144 -0
  100. package/harness/adapters/llvm.mjs +118 -0
  101. package/harness/adapters/native.mjs +77 -0
  102. package/harness/adapters/wasm.mjs +169 -0
  103. package/harness/backend.mjs +95 -0
  104. package/harness/cli.mjs +130 -0
  105. package/harness/format.mjs +95 -0
  106. package/harness/index.mjs +25 -0
  107. package/harness/persistent.mjs +121 -0
  108. package/harness/registry.mjs +44 -0
  109. package/harness/suite.mjs +257 -0
  110. package/harness/suites/poly.mjs +156 -0
  111. package/harness/trace.mjs +50 -0
  112. package/harness/util.mjs +92 -0
  113. package/intrinsics.mjs +41 -0
  114. package/kir.mjs +399 -0
  115. package/kvm.mjs +585 -81
  116. package/objects/README.md +37 -3
  117. package/objects/compile.mjs +15 -22
  118. package/objects/inspect.mjs +96 -0
  119. package/objects/validate.mjs +415 -0
  120. package/package.json +51 -19
  121. package/repl-codecs.mjs +105 -6
  122. package/repl.mjs +335 -49
  123. package/run.mjs +2 -7
  124. package/scripts/benchmark.mjs +406 -22
  125. package/scripts/build-repl-html.mjs +1784 -0
  126. package/scripts/perf-ieee.mjs +586 -0
  127. package/scripts/perf-int.mjs +531 -0
  128. package/scripts/perf-poly.mjs +945 -0
  129. package/scripts/render-benchmark.mjs +12 -8
  130. package/scripts/run-tests.mjs +6 -0
  131. package/tests/integration.sh +19 -2
  132. package/tests/test-arithmetics.mjs +54 -0
  133. package/tests/test-ieee-arithmetic.mjs +4 -0
  134. package/tests/test-kir.mjs +85 -0
  135. package/tests/test-kvm-polymorphic.mjs +278 -0
  136. package/tests/test-kvm.mjs +25 -6
  137. package/tests/test-poly.mjs +129 -0
  138. package/tests/test-repl-html.mjs +303 -0
  139. package/tests/test-repl.mjs +144 -1
  140. package/tests/test-validate-object.mjs +40 -0
  141. package/a.klib +0 -1
  142. package/a.kvm +0 -142676
  143. package/a.wasm +0 -0
  144. package/benchmark-results.html +0 -532
  145. package/benchmark-results.json +0 -480
  146. package/k-vscode/.vscode/launch.json +0 -14
  147. package/k-vscode/README.md +0 -75
  148. package/k-vscode/extension.js +0 -273
  149. package/k-vscode/language-configuration.json +0 -42
  150. package/k-vscode/package.json +0 -79
  151. package/k-vscode/snippets/k.json +0 -92
  152. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  153. package/k-vscode/test/background-ranges.test.js +0 -50
  154. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  155. package/scripts/git-hooks/pre-commit +0 -10
package/objects/README.md CHANGED
@@ -7,17 +7,24 @@ CLI tools for compiling and inspecting k object files (`.ko`) and library files
7
7
  ### `k-compile`
8
8
 
9
9
  Compiles `.k` source into an executable object file (`.ko`), library file
10
- (`.klib`), or kVM JSON (`.kvm`). Output format is inferred from the output
11
- extension, or from `--format`.
10
+ (`.klib`), or polymorphic kVM backend artifact (`.kvm`). Output format is
11
+ inferred from the output extension, or from `--format`.
12
12
 
13
13
  ```bash
14
- k-compile [--lib lib-file] [--format ko|klib|kvm] [source-snippet | input-file [output-file]]
14
+ k-compile [--lib lib-file] [--export spec]... [--format ko|klib|kvm] [source-snippet | input-file [output-file]]
15
15
  ```
16
16
 
17
17
  Existing input paths are read as files. A non-existing input with `.k`, `.ko`,
18
18
  or `.klib` extension is reported as a missing file; otherwise it is compiled as
19
19
  inline k source, in the same style as `k.mjs`.
20
20
 
21
+ `.kvm` is a polymorphic kVM template artifact (`layer: "KVM-P"`). It carries the
22
+ relational function definitions, their principal input/output pattern graphs, and
23
+ lowered kVM instruction streams ready for runtime specialization when enveloped
24
+ values arrive. Specialization occurs when functions are invoked on concrete values,
25
+ eliminating the need for ahead-of-time input pattern or input type flags. Names from
26
+ a `--lib` dependency can be imported into the entry scope using `--export (oldname:)?newname`.
27
+
21
28
  ### `k-decompile`
22
29
 
23
30
  Decompiles a `.ko` or `.klib` back into human-readable k source.
@@ -36,6 +43,27 @@ alias name and `compiledAt`.
36
43
  k-extract-aliases [object-file [k-file]]
37
44
  ```
38
45
 
46
+ ### `k-inspect-object`
47
+
48
+ Inspects a `.ko` or `.klib`. By default it prints a compact object summary.
49
+ With `--kir`, it prints the current KIR-P export used by backend experiments.
50
+
51
+ ```bash
52
+ k-inspect-object [--summary | --kir] [object-file]
53
+ ```
54
+
55
+ For scripts that only need KIR-P export, `k-kir [object-file]` is the direct
56
+ exporter backed by `kir.mjs`.
57
+
58
+ ### `k-validate-object`
59
+
60
+ Validates a `.ko` or `.klib` object and its derived KIR-P export. With
61
+ `--kir`, it validates an already exported KIR-P artifact.
62
+
63
+ ```bash
64
+ k-validate-object [--kir] [input-file]
65
+ ```
66
+
39
67
  The installed names are `k-` plus the source basename without `.mjs`. Every
40
68
  installed object binary supports `-h` and `--help`.
41
69
 
@@ -50,9 +78,15 @@ installed object binary supports `-h` and `--help`.
50
78
  - Origin entries do not have `kind`; the metadata entry has `type: "code"` or
51
79
  `type: "rel"`.
52
80
  - Stored relation bodies do not include generated input/output boundary filters.
81
+ - KIR-P is available as an inspection/export contract; it does not change the
82
+ stored `.ko` or `.klib` payload.
83
+ - `.kvm` is not a canonical source/object format; it is a polymorphic backend
84
+ template artifact (`layer: "KVM-P"`), lowered from KIR-P for runtime execution
85
+ and JIT specialization.
53
86
 
54
87
  ## Further reading
55
88
 
56
89
  - [DOCS/OBJECT_FILE_AND_PATTERN.md](../DOCS/OBJECT_FILE_AND_PATTERN.md) — object file format and pattern encoding
90
+ - [DOCS/KIR_V1.md](../DOCS/KIR_V1.md) — KIR-P inspection/export contract for backends
57
91
  - [DOCS/TYPE_DERIVATION.md](../DOCS/TYPE_DERIVATION.md) — type derivation (what compilation skips when loading a `.ko`/`.klib`)
58
92
  - [DOCS/CONVERGENCE.md](../DOCS/CONVERGENCE.md) — convergence strategies for type inference
@@ -5,12 +5,12 @@ import path from "node:path";
5
5
  import { argv, exit, stdin, stdout } from "node:process";
6
6
  import {
7
7
  compileObjectBuffer,
8
+ compileLibrary,
8
9
  compileLibraryBuffer,
9
10
  decodeObject,
10
11
  loadLibrary
11
12
  } from "../object.mjs";
12
- import { lowerToKVM } from "../kvm.mjs";
13
- import { annotate } from "../index.mjs";
13
+ import { objectToKVMArtifact } from "../kvm.mjs";
14
14
 
15
15
  function helpText() {
16
16
  return [
@@ -25,7 +25,7 @@ function helpText() {
25
25
  " output-file Output path (.ko, .klib, or .kvm). Writes to stdout when omitted.",
26
26
  "",
27
27
  "Options:",
28
- " --lib file Load one .klib dependency before compiling.",
28
+ " --lib file Load one .klib or .k library dependency before compiling.",
29
29
  " --export spec Export a library alias into the source scope. May be repeated.",
30
30
  " spec is 'name' or 'libname:localname'.",
31
31
  " --format fmt Output format: ko, klib, or kvm. Overrides extension detection.",
@@ -35,15 +35,15 @@ function helpText() {
35
35
  "or .klib extension is reported as a missing file; otherwise it is compiled",
36
36
  "as inline k source.",
37
37
  "",
38
- "When input is .ko and output is .kvm, the object's relations are lowered",
39
- "to kVM. A .klib input can only be copied to .klib output.",
38
+ ".kvm is a polymorphic kVM template artifact (layer: KVM-P).",
39
+ "A .klib input can only be copied to .klib output.",
40
40
  "",
41
41
  "Examples:",
42
42
  ` ${argv[1]} 'x = |x; x x'`,
43
43
  ` ${argv[1]} program.k program.ko`,
44
44
  ` ${argv[1]} program.k program.klib`,
45
45
  ` ${argv[1]} program.k program.kvm`,
46
- ` ${argv[1]} program.ko program.kvm`,
46
+ ` ${argv[1]} --lib Examples/arithmetics.k --export plus:+ --export succ --export int '{succ int x,int y}+' a.kvm`,
47
47
  ` ${argv[1]} --lib core.klib --export add --export mul:times program.k program.ko`
48
48
  ].join("\n");
49
49
  }
@@ -131,21 +131,8 @@ function buildExportPreamble(exports, libraries) {
131
131
  return lines.join("\n") + "\n";
132
132
  }
133
133
 
134
- function compileToKVM(source, options) {
135
- const defs = annotate(source, options);
136
- const kvmProgram = {};
137
- for (const [name, relDef] of Object.entries(defs.rels)) {
138
- kvmProgram[name] = lowerToKVM(relDef, name);
139
- }
140
- return JSON.stringify(kvmProgram, null, 2) + "\n";
141
- }
142
-
143
134
  function objectToKVM(object) {
144
- const kvmProgram = {};
145
- for (const [name, relDef] of Object.entries(object.rels)) {
146
- kvmProgram[name] = lowerToKVM(relDef, name);
147
- }
148
- return JSON.stringify(kvmProgram, null, 2) + "\n";
135
+ return JSON.stringify(objectToKVMArtifact(object), null, 2) + "\n";
149
136
  }
150
137
 
151
138
  try {
@@ -169,7 +156,12 @@ try {
169
156
  const libPath = args.shift();
170
157
  if (!libPath) throw new Error("--lib requires a file argument");
171
158
  if (libraries.length > 0) throw new Error("--lib may be specified at most once");
172
- libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
159
+ const ext = path.extname(libPath).toLowerCase();
160
+ if (ext === ".k") {
161
+ libraries.push(loadLibrary(compileLibrary(fs.readFileSync(libPath, "utf8"), { source: libPath })));
162
+ } else {
163
+ libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
164
+ }
173
165
  } else if (args[0] === "--export") {
174
166
  args.shift();
175
167
  const spec = args.shift();
@@ -241,7 +233,8 @@ try {
241
233
  } else if (outputFormat === "klib") {
242
234
  output = compileLibraryBuffer(source, opts);
243
235
  } else if (outputFormat === "kvm") {
244
- output = compileToKVM(source, opts);
236
+ const object = decodeObject(compileObjectBuffer(source, opts));
237
+ output = objectToKVM(object);
245
238
  }
246
239
  }
247
240
 
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import { argv, exit, stdin, stdout } from "node:process";
5
+ import { decodeObject } from "../object.mjs";
6
+ import { objectToKIRP } from "../kir.mjs";
7
+
8
+ function helpText() {
9
+ return [
10
+ "Inspect a k .ko or .klib object.",
11
+ "",
12
+ `Usage: ${argv[1]} [options] [object-file]`,
13
+ "",
14
+ "Arguments:",
15
+ " object-file Input .ko or .klib file. Reads from stdin when omitted.",
16
+ "",
17
+ "Options:",
18
+ " --kir Print the KIR-P JSON view.",
19
+ " --summary Print a compact section summary. This is the default.",
20
+ " -h, --help Show this help.",
21
+ "",
22
+ "KIR-P is an inspection/export view; it does not change the stored object format."
23
+ ].join("\n");
24
+ }
25
+
26
+ function usage(stream = console.error) {
27
+ stream(helpText());
28
+ }
29
+
30
+ async function readStdinBytes() {
31
+ const chunks = [];
32
+ for await (const chunk of stdin) {
33
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
34
+ }
35
+ return Buffer.concat(chunks);
36
+ }
37
+
38
+ function inspectSummary(object) {
39
+ const relationEntries = Object.entries(object.rels || {});
40
+ const codeCount = Object.keys(object.codes || {}).length;
41
+ const aliasCount = Object.keys(object.relAlias || {}).length;
42
+ const metaCount = Object.keys(object.meta || {}).length;
43
+ const statuses = new Map();
44
+ for (const [, rel] of relationEntries) {
45
+ const status = rel.typeDerivation?.status || "unknown";
46
+ statuses.set(status, (statuses.get(status) || 0) + 1);
47
+ }
48
+
49
+ return [
50
+ `format: ${object.format}`,
51
+ `kind: ${object.main == null ? "library" : "executable"}`,
52
+ `main: ${object.main ?? "null"}`,
53
+ `codes: ${codeCount}`,
54
+ `relations: ${relationEntries.length}`,
55
+ `aliases: ${aliasCount}`,
56
+ `metadata entries: ${metaCount}`,
57
+ `type derivation: ${[...statuses.entries()].map(([status, count]) => `${status}=${count}`).join(", ") || "none"}`
58
+ ].join("\n") + "\n";
59
+ }
60
+
61
+ try {
62
+ const args = argv.slice(2);
63
+ if (args.includes("-h") || args.includes("--help")) {
64
+ usage(console.log);
65
+ exit(0);
66
+ }
67
+
68
+ let mode = "summary";
69
+ while (args.length > 0 && args[0].startsWith("--")) {
70
+ const option = args.shift();
71
+ if (option === "--kir") {
72
+ mode = "kir";
73
+ } else if (option === "--summary") {
74
+ mode = "summary";
75
+ } else {
76
+ throw new Error(`Unknown option: ${option}`);
77
+ }
78
+ }
79
+
80
+ const inputPath = args.shift() || null;
81
+ if (args.length > 0) {
82
+ throw new Error(`Unexpected argument: ${args[0]}`);
83
+ }
84
+
85
+ const buffer = inputPath == null ? await readStdinBytes() : fs.readFileSync(inputPath);
86
+ const object = decodeObject(buffer);
87
+ if (mode === "kir") {
88
+ stdout.write(JSON.stringify(objectToKIRP(object), null, 2) + "\n");
89
+ } else {
90
+ stdout.write(inspectSummary(object));
91
+ }
92
+ } catch (error) {
93
+ console.error(error.message || String(error));
94
+ usage();
95
+ exit(1);
96
+ }
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import { argv, exit, stdin, stdout } from "node:process";
5
+ import { decodeObject } from "../object.mjs";
6
+ import { KIR_FORMAT, KIR_VERSION, objectToKIRP } from "../kir.mjs";
7
+ import { isIntrinsic } from "../intrinsics.mjs";
8
+
9
+ const TYPE_DERIVATION_STATUSES = new Set(["converged", "not-converged", "unknown"]);
10
+ const KIR_PATTERN_KINDS = new Set([
11
+ "any",
12
+ "open-product",
13
+ "closed-product",
14
+ "open-union",
15
+ "closed-union",
16
+ "type"
17
+ ]);
18
+ const KIR_OPS = new Set([
19
+ "identity",
20
+ "empty",
21
+ "dot",
22
+ "div",
23
+ "vid",
24
+ "code",
25
+ "filter",
26
+ "ref",
27
+ "comp",
28
+ "union",
29
+ "product"
30
+ ]);
31
+
32
+ function fail(message) {
33
+ throw new Error(message);
34
+ }
35
+
36
+ function isObject(value) {
37
+ return value != null && typeof value === "object" && !Array.isArray(value);
38
+ }
39
+
40
+ function assertObject(value, path) {
41
+ if (!isObject(value)) fail(`${path} must be an object`);
42
+ }
43
+
44
+ function assertString(value, path) {
45
+ if (typeof value !== "string") fail(`${path} must be a string`);
46
+ }
47
+
48
+ function assertArray(value, path) {
49
+ if (!Array.isArray(value)) fail(`${path} must be an array`);
50
+ }
51
+
52
+ function assertInteger(value, path) {
53
+ if (!Number.isInteger(value)) fail(`${path} must be an integer`);
54
+ }
55
+
56
+ function validateStatus(status, path) {
57
+ if (!TYPE_DERIVATION_STATUSES.has(status)) {
58
+ fail(`${path} must be one of ${[...TYPE_DERIVATION_STATUSES].join(", ")}`);
59
+ }
60
+ }
61
+
62
+ function validateObjectExp(exp, path, relNames, codeNames) {
63
+ assertObject(exp, path);
64
+ assertString(exp.op, `${path}.op`);
65
+
66
+ if (exp.patterns != null) {
67
+ assertArray(exp.patterns, `${path}.patterns`);
68
+ exp.patterns.forEach((id, index) => assertInteger(id, `${path}.patterns[${index}]`));
69
+ }
70
+
71
+ switch (exp.op) {
72
+ case "identity":
73
+ case "empty":
74
+ break;
75
+ case "dot":
76
+ assertString(exp.dot, `${path}.dot`);
77
+ break;
78
+ case "div":
79
+ assertString(exp.div, `${path}.div`);
80
+ break;
81
+ case "vid":
82
+ assertString(exp.vid, `${path}.vid`);
83
+ break;
84
+ case "code":
85
+ assertString(exp.code, `${path}.code`);
86
+ if (!codeNames.has(exp.code)) fail(`${path}.code references missing code ${exp.code}`);
87
+ break;
88
+ case "filter":
89
+ validateObjectFilter(exp.filter, `${path}.filter`, codeNames);
90
+ break;
91
+ case "ref":
92
+ assertString(exp.ref, `${path}.ref`);
93
+ if (!isIntrinsic(exp.ref) && !relNames.has(exp.ref)) {
94
+ fail(`${path}.ref references missing relation ${exp.ref}`);
95
+ }
96
+ break;
97
+ case "comp":
98
+ assertArray(exp.comp, `${path}.comp`);
99
+ exp.comp.forEach((child, index) => validateObjectExp(child, `${path}.comp[${index}]`, relNames, codeNames));
100
+ break;
101
+ case "union":
102
+ assertArray(exp.union, `${path}.union`);
103
+ exp.union.forEach((child, index) => validateObjectExp(child, `${path}.union[${index}]`, relNames, codeNames));
104
+ break;
105
+ case "product":
106
+ assertArray(exp.product, `${path}.product`);
107
+ exp.product.forEach((field, index) => {
108
+ assertObject(field, `${path}.product[${index}]`);
109
+ assertString(field.label, `${path}.product[${index}].label`);
110
+ validateObjectExp(field.exp, `${path}.product[${index}].exp`, relNames, codeNames);
111
+ });
112
+ break;
113
+ default:
114
+ fail(`${path}.op has unsupported value ${exp.op}`);
115
+ }
116
+ }
117
+
118
+ function validateObjectFilter(filter, path, codeNames) {
119
+ assertObject(filter, path);
120
+ if (filter.type === "code") {
121
+ assertString(filter.code, `${path}.code`);
122
+ if (!codeNames.has(filter.code)) fail(`${path}.code references missing code ${filter.code}`);
123
+ }
124
+ if (filter.fields != null) {
125
+ assertObject(filter.fields, `${path}.fields`);
126
+ for (const [label, child] of Object.entries(filter.fields)) {
127
+ validateObjectFilter(child, `${path}.fields.${label}`, codeNames);
128
+ }
129
+ }
130
+ }
131
+
132
+ function validateTypePatternGraph(graph, path, codeNames) {
133
+ assertObject(graph, path);
134
+ const nodes = graph.patterns?.nodes;
135
+ const parent = graph.patterns?.parent;
136
+ assertArray(nodes, `${path}.patterns.nodes`);
137
+ assertArray(parent, `${path}.patterns.parent`);
138
+ if (parent.length > nodes.length) {
139
+ fail(`${path}.patterns.parent length must not exceed nodes length`);
140
+ }
141
+
142
+ nodes.forEach((node, index) => {
143
+ assertObject(node, `${path}.patterns.nodes[${index}]`);
144
+ assertString(node.pattern, `${path}.patterns.nodes[${index}].pattern`);
145
+ if (node.pattern === "type") {
146
+ assertString(node.type, `${path}.patterns.nodes[${index}].type`);
147
+ if (!codeNames.has(node.type)) fail(`${path}.patterns.nodes[${index}].type references missing code ${node.type}`);
148
+ }
149
+ });
150
+
151
+ parent.forEach((parentId, index) => {
152
+ if (parentId == null) return;
153
+ assertInteger(parentId, `${path}.patterns.parent[${index}]`);
154
+ if (parentId < 0 || parentId >= nodes.length) fail(`${path}.patterns.parent[${index}] is out of range`);
155
+ });
156
+
157
+ assertArray(graph.edges, `${path}.edges`);
158
+ if (graph.edges.length > nodes.length) {
159
+ fail(`${path}.edges length must not exceed nodes length`);
160
+ }
161
+ graph.edges.forEach((edges, source) => {
162
+ if (edges == null) return;
163
+ assertObject(edges, `${path}.edges[${source}]`);
164
+ for (const [label, dests] of Object.entries(edges)) {
165
+ assertString(label, `${path}.edges[${source}] label`);
166
+ assertArray(dests, `${path}.edges[${source}].${label}`);
167
+ dests.forEach((target, index) => {
168
+ assertInteger(target, `${path}.edges[${source}].${label}[${index}]`);
169
+ if (target < 0 || target >= nodes.length) fail(`${path}.edges[${source}].${label}[${index}] is out of range`);
170
+ });
171
+ }
172
+ });
173
+
174
+ assertObject(graph.codeId || {}, `${path}.codeId`);
175
+ for (const [code, id] of Object.entries(graph.codeId || {})) {
176
+ if (!codeNames.has(code)) fail(`${path}.codeId references missing code ${code}`);
177
+ assertInteger(id, `${path}.codeId.${code}`);
178
+ if (id < 0 || id >= nodes.length) fail(`${path}.codeId.${code} is out of range`);
179
+ }
180
+ }
181
+
182
+ export function validateObjectPayload(object) {
183
+ assertObject(object, "object");
184
+ if (object.format !== "k-object") fail("object.format must be k-object");
185
+ assertObject(object.codes || {}, "object.codes");
186
+ assertObject(object.rels || {}, "object.rels");
187
+ assertObject(object.relAlias || {}, "object.relAlias");
188
+ assertObject(object.meta || {}, "object.meta");
189
+ assertObject(object.compileStats || {}, "object.compileStats");
190
+
191
+ if (object.main != null) {
192
+ assertString(object.main, "object.main");
193
+ if (!(object.main in object.rels)) fail(`object.main references missing relation ${object.main}`);
194
+ }
195
+
196
+ const codeNames = new Set(Object.keys(object.codes || {}));
197
+ const relNames = new Set(Object.keys(object.rels || {}));
198
+
199
+ for (const [name, hash] of Object.entries(object.relAlias || {})) {
200
+ assertString(name, `object.relAlias key ${name}`);
201
+ assertString(hash, `object.relAlias.${name}`);
202
+ if (!relNames.has(hash) && !relNames.has(name) && !hash.startsWith("@")) {
203
+ fail(`object.relAlias.${name} references missing relation ${hash}`);
204
+ }
205
+ }
206
+
207
+ for (const [name, rel] of Object.entries(object.rels || {})) {
208
+ assertObject(rel, `object.rels.${name}`);
209
+ validateObjectExp(rel.def, `object.rels.${name}.def`, relNames, codeNames);
210
+ assertObject(rel.typeDerivation || {}, `object.rels.${name}.typeDerivation`);
211
+ validateStatus(rel.typeDerivation?.status || "unknown", `object.rels.${name}.typeDerivation.status`);
212
+ validateTypePatternGraph(rel.typePatternGraph, `object.rels.${name}.typePatternGraph`, codeNames);
213
+ }
214
+
215
+ for (const [hash, entry] of Object.entries(object.meta || {})) {
216
+ assertObject(entry, `object.meta.${hash}`);
217
+ if (entry.type !== "code" && entry.type !== "rel") fail(`object.meta.${hash}.type must be code or rel`);
218
+ assertArray(entry.origins || [], `object.meta.${hash}.origins`);
219
+ }
220
+
221
+ return object;
222
+ }
223
+
224
+ function validateKirPatterns(patterns, path, nodeCount) {
225
+ if (patterns == null) return;
226
+ assertArray(patterns, path);
227
+ patterns.forEach((id, index) => {
228
+ assertInteger(id, `${path}[${index}]`);
229
+ if (id < 0 || id >= nodeCount) fail(`${path}[${index}] is out of range`);
230
+ });
231
+ }
232
+
233
+ function validateKIRExp(exp, path, nodeCount, relNames, codeNames) {
234
+ assertObject(exp, path);
235
+ assertString(exp.op, `${path}.op`);
236
+ if (!KIR_OPS.has(exp.op)) fail(`${path}.op has unsupported value ${exp.op}`);
237
+ validateKirPatterns(exp.patterns, `${path}.patterns`, nodeCount);
238
+
239
+ switch (exp.op) {
240
+ case "identity":
241
+ case "empty":
242
+ break;
243
+ case "dot":
244
+ assertString(exp.label, `${path}.label`);
245
+ break;
246
+ case "div":
247
+ case "vid":
248
+ assertString(exp.tag, `${path}.tag`);
249
+ break;
250
+ case "code":
251
+ assertString(exp.code, `${path}.code`);
252
+ if (!codeNames.has(exp.code)) fail(`${path}.code references missing code ${exp.code}`);
253
+ break;
254
+ case "filter":
255
+ assertObject(exp.filter, `${path}.filter`);
256
+ break;
257
+ case "ref":
258
+ assertString(exp.ref, `${path}.ref`);
259
+ if (!isIntrinsic(exp.ref) && !relNames.has(exp.ref)) fail(`${path}.ref references missing relation ${exp.ref}`);
260
+ break;
261
+ case "comp":
262
+ case "union":
263
+ assertArray(exp.items, `${path}.items`);
264
+ exp.items.forEach((child, index) => validateKIRExp(child, `${path}.items[${index}]`, nodeCount, relNames, codeNames));
265
+ break;
266
+ case "product":
267
+ assertArray(exp.fields, `${path}.fields`);
268
+ exp.fields.forEach((field, index) => {
269
+ assertObject(field, `${path}.fields[${index}]`);
270
+ assertString(field.label, `${path}.fields[${index}].label`);
271
+ validateKIRExp(field.expr, `${path}.fields[${index}].expr`, nodeCount, relNames, codeNames);
272
+ });
273
+ break;
274
+ }
275
+ }
276
+
277
+ function validateKIRPatternGraph(patternGraph, path, codeNames) {
278
+ assertObject(patternGraph, path);
279
+ assertArray(patternGraph.nodes, `${path}.nodes`);
280
+ const nodeCount = patternGraph.nodes.length;
281
+ patternGraph.nodes.forEach((node, index) => {
282
+ assertObject(node, `${path}.nodes[${index}]`);
283
+ if (node.id !== index) fail(`${path}.nodes[${index}].id must be ${index}`);
284
+ if (!KIR_PATTERN_KINDS.has(node.kind)) fail(`${path}.nodes[${index}].kind is unsupported`);
285
+ if (node.kind === "type") {
286
+ assertString(node.code, `${path}.nodes[${index}].code`);
287
+ if (!codeNames.has(node.code)) fail(`${path}.nodes[${index}].code references missing code ${node.code}`);
288
+ }
289
+ if (node.edges != null) {
290
+ assertArray(node.edges, `${path}.nodes[${index}].edges`);
291
+ node.edges.forEach((edge, edgeIndex) => {
292
+ assertObject(edge, `${path}.nodes[${index}].edges[${edgeIndex}]`);
293
+ assertString(edge.label, `${path}.nodes[${index}].edges[${edgeIndex}].label`);
294
+ assertInteger(edge.target, `${path}.nodes[${index}].edges[${edgeIndex}].target`);
295
+ if (edge.target < 0 || edge.target >= nodeCount) {
296
+ fail(`${path}.nodes[${index}].edges[${edgeIndex}].target is out of range`);
297
+ }
298
+ });
299
+ }
300
+ });
301
+
302
+ assertObject(patternGraph.sourceNodeMap || {}, `${path}.sourceNodeMap`);
303
+ for (const [sourceId, kirId] of Object.entries(patternGraph.sourceNodeMap || {})) {
304
+ if (!/^\d+$/.test(sourceId)) fail(`${path}.sourceNodeMap key ${sourceId} must be an integer string`);
305
+ assertInteger(kirId, `${path}.sourceNodeMap.${sourceId}`);
306
+ if (kirId < 0 || kirId >= nodeCount) fail(`${path}.sourceNodeMap.${sourceId} is out of range`);
307
+ }
308
+ }
309
+
310
+ export function validateKIRP(kir) {
311
+ assertObject(kir, "kir");
312
+ if (kir.format !== KIR_FORMAT) fail(`kir.format must be ${KIR_FORMAT}`);
313
+ if (kir.version !== KIR_VERSION) fail(`kir.version must be ${KIR_VERSION}`);
314
+ if (kir.layer !== "KIR-P") fail("kir.layer must be KIR-P");
315
+ if (kir.kind !== "executable" && kir.kind !== "library") fail("kir.kind must be executable or library");
316
+ assertObject(kir.codes || {}, "kir.codes");
317
+ assertObject(kir.rels || {}, "kir.rels");
318
+ assertObject(kir.relAlias || {}, "kir.relAlias");
319
+ assertObject(kir.compileStats || {}, "kir.compileStats");
320
+ assertObject(kir.meta || {}, "kir.meta");
321
+
322
+ if (kir.kind === "executable") {
323
+ assertString(kir.main, "kir.main");
324
+ if (!(kir.main in kir.rels)) fail(`kir.main references missing relation ${kir.main}`);
325
+ } else if (kir.main !== null) {
326
+ fail("library KIR must have main: null");
327
+ }
328
+
329
+ const codeNames = new Set(Object.keys(kir.codes || {}));
330
+ const relNames = new Set(Object.keys(kir.rels || {}));
331
+
332
+ for (const [name, rel] of Object.entries(kir.rels || {})) {
333
+ assertObject(rel, `kir.rels.${name}`);
334
+ validateStatus(rel.typeDerivation?.status || "unknown", `kir.rels.${name}.typeDerivation.status`);
335
+ validateKIRPatternGraph(rel.patternGraph, `kir.rels.${name}.patternGraph`, codeNames);
336
+ const nodeCount = rel.patternGraph.nodes.length;
337
+ for (const field of ["inputPattern", "outputPattern"]) {
338
+ if (rel[field] == null) continue;
339
+ assertInteger(rel[field], `kir.rels.${name}.${field}`);
340
+ if (rel[field] < 0 || rel[field] >= nodeCount) fail(`kir.rels.${name}.${field} is out of range`);
341
+ }
342
+ validateKIRExp(rel.body, `kir.rels.${name}.body`, nodeCount, relNames, codeNames);
343
+ }
344
+
345
+ return kir;
346
+ }
347
+
348
+ function helpText() {
349
+ return [
350
+ "Validate a k .ko/.klib object or KIR-P JSON file.",
351
+ "",
352
+ `Usage: ${argv[1]} [options] [input-file]`,
353
+ "",
354
+ "Arguments:",
355
+ " input-file Input .ko/.klib file, or KIR JSON with --kir. Reads stdin when omitted.",
356
+ "",
357
+ "Options:",
358
+ " --kir Validate input as KIR-P JSON instead of a k object.",
359
+ " -h, --help Show this help.",
360
+ "",
361
+ "Object validation also validates the derived KIR-P export view."
362
+ ].join("\n");
363
+ }
364
+
365
+ function usage(stream = console.error) {
366
+ stream(helpText());
367
+ }
368
+
369
+ async function readStdinBytes() {
370
+ const chunks = [];
371
+ for await (const chunk of stdin) {
372
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
373
+ }
374
+ return Buffer.concat(chunks);
375
+ }
376
+
377
+ async function main() {
378
+ const args = argv.slice(2);
379
+ if (args.includes("-h") || args.includes("--help")) {
380
+ usage(console.log);
381
+ exit(0);
382
+ }
383
+
384
+ let mode = "object";
385
+ while (args.length > 0 && args[0].startsWith("--")) {
386
+ const option = args.shift();
387
+ if (option === "--kir") {
388
+ mode = "kir";
389
+ } else {
390
+ throw new Error(`Unknown option: ${option}`);
391
+ }
392
+ }
393
+
394
+ const inputPath = args.shift() || null;
395
+ if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
396
+
397
+ const input = inputPath == null ? await readStdinBytes() : fs.readFileSync(inputPath);
398
+ if (mode === "kir") {
399
+ validateKIRP(JSON.parse(input.toString("utf8")));
400
+ stdout.write("OK KIR-P\n");
401
+ return;
402
+ }
403
+
404
+ const object = validateObjectPayload(decodeObject(input));
405
+ validateKIRP(objectToKIRP(object));
406
+ stdout.write(`OK ${object.main == null ? "library" : "object"}\n`);
407
+ }
408
+
409
+ if (import.meta.url === `file://${process.argv[1]}`) {
410
+ main().catch((error) => {
411
+ console.error(error.message || String(error));
412
+ usage();
413
+ exit(1);
414
+ });
415
+ }