@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.
- package/DOCS/CODECS.md +258 -0
- package/DOCS/DICTIONARY.md +1172 -0
- package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
- package/DOCS/KIR_V1.md +221 -0
- package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
- package/DOCS/PROFILING_AND_TRACING.md +196 -0
- package/DOCS/REPL.md +76 -14
- package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
- package/DOCS/dependency-graph.md +144 -0
- package/Examples/arithmetics.k +51 -48
- package/Examples/bnat.k +1 -1
- package/Examples/buda.k +44 -0
- package/Examples/ieee.k +300 -8671
- package/Examples/poly.k +67 -0
- package/README.md +68 -19
- package/augmentation.mjs +9 -7
- package/backend-api.mjs +11 -2
- package/backends/arm64/README.md +104 -0
- package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
- package/backends/arm64/bin/k-arm64-run.mjs +58 -0
- package/backends/arm64/bin/k-arm64.mjs +88 -0
- package/backends/arm64/scripts/run-tests.mjs +41 -0
- package/backends/arm64/src/arm64.mjs +229 -0
- package/backends/arm64/src/cli.mjs +155 -0
- package/backends/arm64/src/kvm2arm64.mjs +906 -0
- package/backends/arm64/src/runtime.c +728 -0
- package/backends/arm64/tests/integration.sh +112 -0
- package/backends/arm64/tests/test-arm64.mjs +240 -0
- package/backends/llvm/README.md +135 -0
- package/backends/llvm/bin/k-llvm-build.mjs +71 -0
- package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
- package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
- package/backends/llvm/bin/k-llvm-run.mjs +90 -0
- package/backends/llvm/runtime/krt.c +1508 -0
- package/backends/llvm/runtime/krt.h +109 -0
- package/backends/llvm/scripts/conformance.mjs +76 -0
- package/backends/llvm/scripts/run-tests.mjs +28 -0
- package/backends/llvm/src/executable.mjs +562 -0
- package/backends/llvm/src/kvm2llvm.mjs +998 -0
- package/backends/llvm/src/llvm.mjs +1092 -0
- package/backends/llvm/tests/composition-driver.c +22 -0
- package/backends/llvm/tests/identity-driver.c +27 -0
- package/backends/llvm/tests/integration.sh +153 -0
- package/backends/llvm/tests/perf-support.mjs +735 -0
- package/backends/llvm/tests/product-driver.c +23 -0
- package/backends/llvm/tests/projection-driver.c +19 -0
- package/backends/llvm/tests/relation-driver.c +29 -0
- package/backends/llvm/tests/test-llvm.mjs +240 -0
- package/backends/llvm/tests/union-driver.c +31 -0
- package/backends/llvm/tests/variant-driver.c +19 -0
- package/backends/llvm/tests/variant-projection-driver.c +21 -0
- package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
- package/backends/wasm/README.md +97 -0
- package/backends/wasm/TECHNICAL-DEBT.md +60 -0
- package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
- package/backends/wasm/bin/k-wasm-run.mjs +140 -0
- package/backends/wasm/bin/k-wasm.mjs +49 -0
- package/backends/wasm/demo_ieee.mjs +319 -0
- package/backends/wasm/runtime.wat +102 -0
- package/backends/wasm/scripts/run-tests.mjs +41 -0
- package/backends/wasm/src/cli.mjs +133 -0
- package/backends/wasm/src/kvm2wasm.mjs +661 -0
- package/backends/wasm/src/wasm.mjs +1010 -0
- package/backends/wasm/tests/integration.sh +182 -0
- package/backends/wasm/tests/perf-support.mjs +336 -0
- package/backends/wasm/tests/test-wasm.mjs +617 -0
- package/browser/repl-browser.mjs +1584 -0
- package/browser/shims/assert.mjs +19 -0
- package/browser/shims/buffer.mjs +224 -0
- package/browser/shims/crypto.mjs +103 -0
- package/browser/shims/fs.mjs +159 -0
- package/browser/shims/os.mjs +17 -0
- package/browser/shims/path.mjs +54 -0
- package/browser/shims/process.mjs +44 -0
- package/browser/shims/readline.mjs +11 -0
- package/browser/shims/url.mjs +12 -0
- package/browser/vfs-data.mjs +19 -0
- package/codecs/README.md +3 -0
- package/codecs/json.mjs +16 -2
- package/codecs/runtime/show-value.mjs +2 -2
- package/conformance/helper-reuse/case.json +8 -0
- package/conformance/helper-reuse/expected.kv +1 -0
- package/conformance/helper-reuse/input.kv +1 -0
- package/conformance/helper-reuse/program.k +2 -0
- package/conformance/identity/case.json +8 -0
- package/conformance/identity/expected.kv +1 -0
- package/conformance/identity/input.kv +1 -0
- package/conformance/identity/program.k +1 -0
- package/conformance/product-projection/case.json +8 -0
- package/conformance/product-projection/expected.kv +1 -0
- package/conformance/product-projection/input.kv +1 -0
- package/conformance/product-projection/program.k +1 -0
- package/conformance/run.mjs +149 -0
- package/conformance/variant-construction/case.json +8 -0
- package/conformance/variant-construction/expected.kv +1 -0
- package/conformance/variant-construction/input.kv +1 -0
- package/conformance/variant-construction/program.k +1 -0
- package/harness/adapters/arm64.mjs +105 -0
- package/harness/adapters/kvm.mjs +144 -0
- package/harness/adapters/llvm.mjs +118 -0
- package/harness/adapters/native.mjs +77 -0
- package/harness/adapters/wasm.mjs +169 -0
- package/harness/backend.mjs +95 -0
- package/harness/cli.mjs +130 -0
- package/harness/format.mjs +95 -0
- package/harness/index.mjs +25 -0
- package/harness/persistent.mjs +121 -0
- package/harness/registry.mjs +44 -0
- package/harness/suite.mjs +257 -0
- package/harness/suites/poly.mjs +156 -0
- package/harness/trace.mjs +50 -0
- package/harness/util.mjs +92 -0
- package/intrinsics.mjs +41 -0
- package/kir.mjs +399 -0
- package/kvm.mjs +585 -81
- package/objects/README.md +37 -3
- package/objects/compile.mjs +15 -22
- package/objects/inspect.mjs +96 -0
- package/objects/validate.mjs +415 -0
- package/package.json +51 -19
- package/repl-codecs.mjs +133 -20
- package/repl.mjs +408 -59
- package/run.mjs +2 -7
- package/scripts/benchmark.mjs +406 -22
- package/scripts/build-repl-html.mjs +1784 -0
- package/scripts/perf-ieee.mjs +586 -0
- package/scripts/perf-int.mjs +531 -0
- package/scripts/perf-poly.mjs +945 -0
- package/scripts/render-benchmark.mjs +12 -8
- package/scripts/run-tests.mjs +6 -0
- package/tests/integration.sh +19 -2
- package/tests/test-arithmetics.mjs +54 -0
- package/tests/test-ieee-arithmetic.mjs +6 -2
- package/tests/test-kir.mjs +85 -0
- package/tests/test-kvm-polymorphic.mjs +278 -0
- package/tests/test-kvm.mjs +25 -6
- package/tests/test-poly.mjs +129 -0
- package/tests/test-repl-html.mjs +303 -0
- package/tests/test-repl.mjs +175 -1
- package/tests/test-validate-object.mjs +40 -0
- package/a.klib +0 -1
- package/a.kvm +0 -142676
- package/a.wasm +0 -0
- package/benchmark-results.html +0 -532
- package/benchmark-results.json +0 -480
- package/k-vscode/.vscode/launch.json +0 -14
- package/k-vscode/README.md +0 -75
- package/k-vscode/extension.js +0 -273
- package/k-vscode/language-configuration.json +0 -42
- package/k-vscode/package.json +0 -79
- package/k-vscode/snippets/k.json +0 -92
- package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
- package/k-vscode/test/background-ranges.test.js +0 -50
- package/k-vscode/themes/k-contrast-color-theme.json +0 -104
- 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
|
|
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
|
package/objects/compile.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
"
|
|
39
|
-
"
|
|
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]}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|