@fraczak/k 7.0.2 → 7.2.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 (71) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/DOCS/CODECS.md +37 -2
  3. package/DOCS/CODEC_ARCHITECTURE.md +12 -5
  4. package/DOCS/FILE_FORMATS.md +238 -15
  5. package/DOCS/POLYMORPHIC_IR.md +104 -0
  6. package/DOCS/REPL.md +11 -3
  7. package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
  8. package/JOURNAL.md +288 -0
  9. package/README.md +12 -3
  10. package/Value.mjs +28 -3
  11. package/access-table.mjs +343 -0
  12. package/backend-api.mjs +23 -1
  13. package/backends/llvm/README.md +20 -21
  14. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  15. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  16. package/backends/llvm/bin/k-llvm.mjs +292 -0
  17. package/backends/llvm/runtime/krt.c +8 -0
  18. package/backends/llvm/runtime/krt.h +1 -0
  19. package/backends/llvm/src/cli.mjs +95 -17
  20. package/backends/llvm/src/executable.mjs +128 -11
  21. package/backends/llvm/src/kvm2llvm.mjs +10 -5
  22. package/backends/llvm/src/llvm.mjs +121 -9
  23. package/backends/llvm/tests/integration.sh +11 -11
  24. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  25. package/backends/llvm/tests/perf-support.mjs +24 -3
  26. package/backends/llvm/tests/test-llvm.mjs +22 -1
  27. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  28. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  29. package/backends/wasm/bin/k-wasm.mjs +47 -13
  30. package/backends/wasm/src/cli.mjs +141 -19
  31. package/backends/wasm/src/wasm.mjs +130 -23
  32. package/browser/repl-browser.mjs +16 -5
  33. package/browser/tree-view.mjs +47 -8
  34. package/browser/vfs-data.mjs +6 -5
  35. package/codecs/README.md +1 -0
  36. package/codecs/djson.mjs +270 -0
  37. package/codecs/int.mjs +2 -1
  38. package/codecs/json.mjs +7 -2
  39. package/codecs/k-parse.mjs +36 -9
  40. package/codecs/k-print.mjs +26 -6
  41. package/codecs/runtime/codec-sdk.mjs +24 -6
  42. package/codecs/runtime/pattern-json.mjs +80 -1
  43. package/codecs/runtime/prefix-codec.mjs +629 -67
  44. package/codecs/runtime/show-value.mjs +93 -51
  45. package/codecs/runtime/unicode-string.mjs +29 -6
  46. package/codecs/show.mjs +24 -6
  47. package/codecs/utf16.mjs +2 -1
  48. package/k.mjs +56 -16
  49. package/kir.mjs +58 -19
  50. package/kvm.mjs +49 -12
  51. package/objects/compile.mjs +37 -23
  52. package/objects/decompile.mjs +40 -10
  53. package/objects/extract-aliases.mjs +44 -14
  54. package/objects/inspect.mjs +19 -14
  55. package/objects/validate.mjs +18 -11
  56. package/package.json +11 -3
  57. package/pkir.mjs +772 -0
  58. package/repl.mjs +92 -23
  59. package/scripts/build-repl-html.mjs +22 -3
  60. package/scripts/perf-ieee.mjs +264 -13
  61. package/scripts/perf-int.mjs +315 -22
  62. package/scripts/perf-multiplication.mjs +58 -7
  63. package/scripts/perf-poly.mjs +306 -24
  64. package/scripts/run-tests.mjs +3 -0
  65. package/tests/test-access-table.mjs +213 -0
  66. package/tests/test-codecs.mjs +168 -0
  67. package/tests/test-pkir.mjs +530 -0
  68. package/tests/test-polymorphic-tdd.mjs +196 -0
  69. package/tests/test-repl-html.mjs +16 -2
  70. package/tests/test-repl.mjs +63 -1
  71. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
@@ -9,26 +9,44 @@ import {
9
9
  } from "../src/cli.mjs";
10
10
  import {
11
11
  compileObjectToLLVM,
12
- compileLLVMArtifactFromKVM
12
+ compileLLVMArtifactFromKVM,
13
+ compileLLVMArtifactFromPKIR
13
14
  } from "../src/llvm.mjs";
14
15
  import { inputPatternForObjectRelation } from "../src/executable.mjs";
15
16
 
16
- function usage(stream = console.error) {
17
+ function helpText() {
17
18
  const prog = argv[1] || "k-llvm-compile.mjs";
18
- stream(`Usage: node ${prog} [options] [source-snippet | input-file [output.ll]]`);
19
- stream("Compile a k .ko/.klib object, source, or .kvm input into prototype LLVM IR.");
20
- stream("");
21
- stream("Arguments:");
22
- stream(" source-snippet Inline k source, in the same style as k.mjs.");
23
- stream(" input-file Source .k, .ko, .klib, or .kvm file. Reads UTF-8 source from stdin when omitted.");
24
- stream(" output.ll Output LLVM IR path. Writes to stdout when omitted.");
25
- stream("");
26
- stream("Options:");
27
- stream(" -o, --output path Specify output file path explicitly.");
28
- stream(" --lib file Load one .klib dependency before compiling.");
29
- stream(" --export spec Export a library alias into source scope. May be repeated.");
30
- stream(" spec is 'name' or 'libname:localname'.");
31
- stream(" -h, --help Show this help.");
19
+ return [
20
+ "Compile a k .ko/.klib object, source, .kvm, or .pkir into LLVM IR.",
21
+ "",
22
+ `Usage: ${prog} [options]`,
23
+ " [source-snippet | input-file [output.ll]]",
24
+ "",
25
+ "Arguments:",
26
+ " source-snippet Inline k source snippet (in the style of k.mjs).",
27
+ " input-file Source .k, .ko, .klib, .kvm, or .pkir file.",
28
+ " Reads UTF-8 source from stdin when omitted.",
29
+ " output.ll Output LLVM IR path (default: stdout).",
30
+ "",
31
+ "Options:",
32
+ " -o, --output path Specify output file path explicitly.",
33
+ " --pkir Lower via Polymorphic K IR (PKIR).",
34
+ " --retype rel Relation to compile (default: object main).",
35
+ " --lib file Load one .klib or .k library before compiling.",
36
+ " --export spec Export library alias into scope. Repeatable.",
37
+ " spec is 'name' or 'libname:localname'.",
38
+ " -h, --help Show this help.",
39
+ "",
40
+ "Examples:",
41
+ ` ${prog} 'x = |x; x x' program.ll`,
42
+ ` ${prog} program.k -o program.ll`,
43
+ ` ${prog} --pkir program.k -o program.ll`,
44
+ ` ${prog} --lib core.klib --export add program.k program.ll`
45
+ ].join("\n");
46
+ }
47
+
48
+ function usage(stream = console.error) {
49
+ stream(helpText());
32
50
  }
33
51
 
34
52
  try {
@@ -39,29 +57,49 @@ try {
39
57
  }
40
58
 
41
59
  let explicitOutput = null;
60
+ let relation = null;
42
61
  const remainingArgs = [];
43
62
  while (args.length > 0) {
44
63
  const arg = args[0];
45
64
  if (arg === "-o" || arg === "--output") {
46
65
  args.shift();
47
66
  explicitOutput = args.shift();
48
- if (!explicitOutput) throw new Error("-o/--output requires a file argument");
67
+ if (!explicitOutput) {
68
+ throw new Error("-o/--output requires a file argument");
69
+ }
70
+ } else if (arg === "--retype") {
71
+ args.shift();
72
+ relation = args.shift();
73
+ if (!relation) {
74
+ throw new Error("--retype requires a relation name");
75
+ }
49
76
  } else {
50
77
  remainingArgs.push(args.shift());
51
78
  }
52
79
  }
53
80
 
54
- const { libraries, exportSpecs } = parseCompileOptions(remainingArgs);
55
- const input = resolveProgramInput(remainingArgs, { allowStdinSource: true });
81
+ const { libraries, exportSpecs, usePKIR } =
82
+ parseCompileOptions(remainingArgs);
83
+ const input = resolveProgramInput(remainingArgs, {
84
+ allowStdinSource: true
85
+ });
56
86
  const positionalOutput = remainingArgs.shift();
57
87
  if (remainingArgs.length > 0) throw new Error("Too many arguments");
58
88
 
59
89
  const outputPath = explicitOutput || positionalOutput || null;
60
90
 
61
91
  let llvm;
62
- if (input.kind === "kvm") {
92
+ if (input.kind === "pkir") {
93
+ const pkir = JSON.parse(fs.readFileSync(input.path, "utf8"));
94
+ const entry = relation || pkir.entry || pkir.name || "__main__";
95
+ llvm = compileLLVMArtifactFromPKIR(pkir, {
96
+ relation: entry,
97
+ inputPattern: pkir.inputPattern,
98
+ outputPattern: pkir.outputPattern
99
+ });
100
+ } else if (input.kind === "kvm") {
63
101
  const kvm = JSON.parse(fs.readFileSync(input.path, "utf8"));
64
- const entry = kvm.entry || kvm.relation || "__main__";
102
+ const entry = relation || kvm.entry || kvm.relation || "__main__";
65
103
  const mainFunc = kvm.functions?.[entry];
66
104
  const artifact = compileLLVMArtifactFromKVM(kvm, {
67
105
  relation: entry,
@@ -76,9 +114,11 @@ try {
76
114
  stdin
77
115
  });
78
116
 
117
+ const targetRel = relation || object.main;
79
118
  const res = compileObjectToLLVM(object, {
80
- relation: object.main,
81
- inputPattern: inputPatternForObjectRelation(object, object.main)
119
+ relation: targetRel,
120
+ inputPattern: inputPatternForObjectRelation(object, targetRel),
121
+ usePKIR
82
122
  });
83
123
  llvm = res.llvm;
84
124
  }
@@ -90,6 +130,5 @@ try {
90
130
  }
91
131
  } catch (error) {
92
132
  console.error(error.stack || error.message || String(error));
93
- usage();
94
133
  exit(1);
95
134
  }
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { spawn } from "node:child_process";
8
+ import { argv, exit, stdin, stdout } from "node:process";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ import { decodeWire } from "@fraczak/k/backend-api.mjs";
12
+ import {
13
+ compileProgramInputToObject,
14
+ parseCompileOptions,
15
+ readAll,
16
+ resolveProgramInput
17
+ } from "../src/cli.mjs";
18
+ import {
19
+ compileKVMToExecutable,
20
+ compileObjectToExecutable,
21
+ compilePKIRToExecutable
22
+ } from "../src/executable.mjs";
23
+
24
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
25
+
26
+ function helpText() {
27
+ const prog = argv[1] || "k-llvm.mjs";
28
+ const defaultCache = path.join(os.tmpdir(), "k-llvm-cache");
29
+ return [
30
+ "Compile k source, .ko, .kvm, or .pkir to native machine code via",
31
+ "LLVM and execute it over a binary pattern+value wire stream.",
32
+ "",
33
+ `Usage: ${prog} [options]`,
34
+ " ( source-snippet | input-file ) [ input-wire-file ]",
35
+ "",
36
+ "Arguments:",
37
+ " source-snippet Inline k source snippet (in the style of k.mjs).",
38
+ " input-file Source .k, .ko, .klib, .kvm, or .pkir file.",
39
+ " input-wire-file Optional binary wire input (default: stdin).",
40
+ "",
41
+ "Options:",
42
+ " --lib file Load one .klib or .k library before compiling.",
43
+ " --export spec Export library alias into scope. Repeatable.",
44
+ " spec is 'name' or 'libname:localname'.",
45
+ " --pkir Lower via Polymorphic K IR (PKIR).",
46
+ " --retype rel Relation to specialize (default: object main).",
47
+ " --cache-dir path Directory to store compiled native executables.",
48
+ " --no-cache Compile in a temporary directory and discard.",
49
+ " --opt flag Clang optimization level (-O0, -O1, -O2, -O3).",
50
+ " -h, --help Show this help.",
51
+ "",
52
+ "Environment Variables:",
53
+ ` K_LLVM_CACHE_DIR Cache directory (default: ${defaultCache}).`,
54
+ " K_LLVM_CLANG_OPT Default Clang optimization flag (default: -O1).",
55
+ " CLANG, CC Path to Clang compiler binary (default: clang).",
56
+ " K_TRACE, TRACE When '1', output phase tracing to stderr.",
57
+ "",
58
+ "Examples:",
59
+ ` echo '"abc"' | k-parse | ${prog} '{.1 0}' | k-print`,
60
+ ` echo '{"x":1,"y":2}' | k-json --parse | \\`,
61
+ ` ${prog} --lib Examples/poly.k --export concat \\`,
62
+ ` 's={()xs,()ys}concat; s' | k-json --print`
63
+ ].join("\n");
64
+ }
65
+
66
+ function usage(stream = console.error) {
67
+ stream(helpText());
68
+ }
69
+
70
+ let cachedFingerprint = null;
71
+ function getBackendFingerprint() {
72
+ if (cachedFingerprint != null) return cachedFingerprint;
73
+ const hash = crypto.createHash("sha256");
74
+ for (const relPath of [
75
+ "src/llvm.mjs",
76
+ "src/kvm2llvm.mjs",
77
+ "src/executable.mjs",
78
+ "runtime/krt.c",
79
+ "runtime/krt.h"
80
+ ]) {
81
+ const full = path.join(root, relPath);
82
+ if (fs.existsSync(full)) {
83
+ hash.update(relPath);
84
+ hash.update("\0");
85
+ hash.update(fs.readFileSync(full));
86
+ hash.update("\0");
87
+ }
88
+ }
89
+ cachedFingerprint = hash.digest("hex").slice(0, 16);
90
+ return cachedFingerprint;
91
+ }
92
+
93
+ function computeCacheKey({
94
+ fingerprint,
95
+ inputKey,
96
+ relation,
97
+ inputPattern,
98
+ usePKIR,
99
+ clangOpt
100
+ }) {
101
+ const hash = crypto.createHash("sha256");
102
+ hash.update("k-llvm-runner-v1\0");
103
+ hash.update(fingerprint);
104
+ hash.update("\0");
105
+ hash.update(String(relation || ""));
106
+ hash.update("\0");
107
+ hash.update(JSON.stringify(inputPattern || null));
108
+ hash.update("\0");
109
+ hash.update(usePKIR ? "pkir\0" : "monomorphic\0");
110
+ hash.update(String(clangOpt || ""));
111
+ hash.update("\0");
112
+ hash.update(inputKey);
113
+ return hash.digest("hex");
114
+ }
115
+
116
+ function runNativeExecutable(exePath, inputBuffer) {
117
+ return new Promise((resolve, reject) => {
118
+ const child = spawn(exePath, [], {
119
+ stdio: ["pipe", "pipe", "inherit"]
120
+ });
121
+ const chunks = [];
122
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
123
+ child.on("error", reject);
124
+ child.on("close", (status) => {
125
+ if (status !== 0) {
126
+ reject(new Error(`${exePath} exited with status ${status}`));
127
+ return;
128
+ }
129
+ resolve(Buffer.concat(chunks));
130
+ });
131
+ child.stdin.end(inputBuffer);
132
+ });
133
+ }
134
+
135
+ async function main() {
136
+ const rawArgs = argv.slice(2);
137
+ if (rawArgs.includes("-h") || rawArgs.includes("--help")) {
138
+ usage(console.log);
139
+ return;
140
+ }
141
+
142
+ let cacheDir = process.env.K_LLVM_CACHE_DIR ||
143
+ path.join(os.tmpdir(), "k-llvm-cache");
144
+ let useCache = true;
145
+ let relation = null;
146
+ let clangOpt = process.env.K_LLVM_CLANG_OPT || "-O1";
147
+
148
+ const remainingArgs = [];
149
+ while (rawArgs.length > 0) {
150
+ const arg = rawArgs[0];
151
+ if (arg === "--cache-dir") {
152
+ rawArgs.shift();
153
+ cacheDir = rawArgs.shift();
154
+ if (!cacheDir) {
155
+ throw new Error("--cache-dir requires a directory path");
156
+ }
157
+ } else if (arg === "--no-cache") {
158
+ rawArgs.shift();
159
+ useCache = false;
160
+ } else if (arg === "--retype") {
161
+ rawArgs.shift();
162
+ relation = rawArgs.shift();
163
+ if (!relation) {
164
+ throw new Error("--retype requires a relation name");
165
+ }
166
+ } else if (arg === "--opt") {
167
+ rawArgs.shift();
168
+ clangOpt = rawArgs.shift();
169
+ if (!clangOpt) {
170
+ throw new Error("--opt requires a flag (e.g. -O1)");
171
+ }
172
+ } else {
173
+ remainingArgs.push(rawArgs.shift());
174
+ }
175
+ }
176
+
177
+ const { libraries, exportSpecs, usePKIR } =
178
+ parseCompileOptions(remainingArgs);
179
+ const programInput = resolveProgramInput(remainingArgs);
180
+ const inputWirePath = remainingArgs.shift();
181
+ if (remainingArgs.length > 0) {
182
+ throw new Error(`Unexpected argument: ${remainingArgs[0]}`);
183
+ }
184
+
185
+ const inputBuffer = await readAll(
186
+ inputWirePath == null ? stdin : fs.createReadStream(inputWirePath)
187
+ );
188
+ if (inputBuffer.length === 0) {
189
+ throw new Error("No input wire data received on stdin");
190
+ }
191
+
192
+ const { pattern: inputEnvelopePattern } = decodeWire(inputBuffer);
193
+
194
+ let exePath;
195
+ let tempExeDir = null;
196
+
197
+ if (useCache) {
198
+ fs.mkdirSync(cacheDir, { recursive: true });
199
+ const fingerprint = getBackendFingerprint();
200
+ const inputKey = JSON.stringify({
201
+ programInput,
202
+ libraries: libraries.map((lib) => Object.keys(lib.rels || {})),
203
+ exportSpecs
204
+ });
205
+ const key = computeCacheKey({
206
+ fingerprint,
207
+ inputKey,
208
+ relation,
209
+ inputPattern: inputEnvelopePattern,
210
+ usePKIR,
211
+ clangOpt
212
+ });
213
+ exePath = path.join(cacheDir, `${key}.exe`);
214
+ if (!fs.existsSync(exePath)) {
215
+ const tmpPath = path.join(cacheDir, `${key}.${process.pid}.tmp`);
216
+ if (programInput.kind === "pkir") {
217
+ const pkir = JSON.parse(
218
+ fs.readFileSync(programInput.path, "utf8")
219
+ );
220
+ compilePKIRToExecutable(pkir, tmpPath, {
221
+ inputPattern: inputEnvelopePattern,
222
+ clangOpt
223
+ });
224
+ } else if (programInput.kind === "kvm") {
225
+ const kvm = JSON.parse(
226
+ fs.readFileSync(programInput.path, "utf8")
227
+ );
228
+ compileKVMToExecutable(kvm, tmpPath, {
229
+ inputPattern: inputEnvelopePattern,
230
+ clangOpt,
231
+ usePKIR
232
+ });
233
+ } else {
234
+ const object = await compileProgramInputToObject(programInput, {
235
+ libraries,
236
+ exportSpecs,
237
+ stdin
238
+ });
239
+ compileObjectToExecutable(object, tmpPath, {
240
+ relation: relation || object.main,
241
+ inputPattern: inputEnvelopePattern,
242
+ clangOpt,
243
+ usePKIR
244
+ });
245
+ }
246
+ fs.renameSync(tmpPath, exePath);
247
+ }
248
+ } else {
249
+ tempExeDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-llvm-run-"));
250
+ exePath = path.join(tempExeDir, "prog.exe");
251
+ if (programInput.kind === "pkir") {
252
+ const pkir = JSON.parse(fs.readFileSync(programInput.path, "utf8"));
253
+ compilePKIRToExecutable(pkir, exePath, {
254
+ inputPattern: inputEnvelopePattern,
255
+ clangOpt
256
+ });
257
+ } else if (programInput.kind === "kvm") {
258
+ const kvm = JSON.parse(fs.readFileSync(programInput.path, "utf8"));
259
+ compileKVMToExecutable(kvm, exePath, {
260
+ inputPattern: inputEnvelopePattern,
261
+ clangOpt,
262
+ usePKIR
263
+ });
264
+ } else {
265
+ const object = await compileProgramInputToObject(programInput, {
266
+ libraries,
267
+ exportSpecs,
268
+ stdin
269
+ });
270
+ compileObjectToExecutable(object, exePath, {
271
+ relation: relation || object.main,
272
+ inputPattern: inputEnvelopePattern,
273
+ clangOpt,
274
+ usePKIR
275
+ });
276
+ }
277
+ }
278
+
279
+ try {
280
+ const outputBuffer = await runNativeExecutable(exePath, inputBuffer);
281
+ stdout.write(outputBuffer);
282
+ } finally {
283
+ if (tempExeDir) {
284
+ fs.rmSync(tempExeDir, { recursive: true, force: true });
285
+ }
286
+ }
287
+ }
288
+
289
+ main().catch((error) => {
290
+ console.error(error.stack || error.message || String(error));
291
+ exit(1);
292
+ });
@@ -1914,6 +1914,14 @@ unsigned char *k_encode_wire_as(k_pattern *pattern, k_value *value, size_t *out_
1914
1914
  return bytes;
1915
1915
  }
1916
1916
 
1917
+ unsigned char *k_encode_wire(k_value *value, size_t *out_length) {
1918
+ k_pattern *pattern = derive_pattern(value);
1919
+ if (pattern == NULL) return NULL;
1920
+ unsigned char *bytes = k_encode_wire_as(pattern, value, out_length);
1921
+ free_pattern(pattern);
1922
+ return bytes;
1923
+ }
1924
+
1917
1925
  int k_write_wire_as(FILE *out, k_pattern *pattern, k_value *value) {
1918
1926
  size_t length = 0;
1919
1927
  unsigned char *bytes = k_encode_wire_as(pattern, value, &length);
@@ -110,6 +110,7 @@ void k_wire_prefix_free(k_wire_prefix prefix);
110
110
  k_value *k_decode_wire_value_with_prefix(const unsigned char *bytes, size_t length, k_wire_prefix *prefix, k_pattern *pattern, k_rt *rt);
111
111
  unsigned char *k_encode_wire_as_with_prefix(k_wire_prefix *prefix, k_pattern *pattern, k_value *value, size_t *out_length);
112
112
  unsigned char *k_encode_wire_as(k_pattern *pattern, k_value *value, size_t *out_length);
113
+ unsigned char *k_encode_wire(k_value *value, size_t *out_length);
113
114
  int k_write_wire(FILE *out, k_value *value);
114
115
  int k_write_wire_as(FILE *out, k_pattern *pattern, k_value *value);
115
116
 
@@ -1,20 +1,73 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { compileLibrary, compileObjectBuffer, decodeObject, loadLibrary } from "@fraczak/k/object.mjs";
3
+ import {
4
+ compileLibrary,
5
+ compileObjectBuffer,
6
+ decodeObject,
7
+ loadLibrary
8
+ } from "@fraczak/k/object.mjs";
4
9
 
5
10
  export function readAll(stream) {
6
11
  return new Promise((resolve, reject) => {
7
12
  const chunks = [];
8
- stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
13
+ stream.on("data", (chunk) => {
14
+ chunks.push(
15
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
16
+ );
17
+ });
9
18
  stream.on("end", () => resolve(Buffer.concat(chunks)));
10
19
  stream.on("error", reject);
11
20
  });
12
21
  }
13
22
 
23
+ export function loadLibraryFromPath(libPath) {
24
+ const ext = path.extname(libPath).toLowerCase();
25
+ if (ext === ".klib" || ext === ".ko") {
26
+ return loadLibrary(decodeObject(fs.readFileSync(libPath)));
27
+ }
28
+ if (ext === ".k") {
29
+ let depLibs = [];
30
+ const baseName = path.basename(libPath);
31
+ if (baseName === "poly.k") {
32
+ const dir = path.dirname(libPath);
33
+ const arithPath = path.join(dir, "arithmetics.k");
34
+ if (fs.existsSync(arithPath)) {
35
+ const arithLib = loadLibrary(
36
+ compileLibrary(fs.readFileSync(arithPath, "utf8"), {
37
+ source: arithPath
38
+ })
39
+ );
40
+ depLibs.push(arithLib);
41
+ }
42
+ }
43
+ const rawSource = fs.readFileSync(libPath, "utf8");
44
+ if (depLibs.length > 0) {
45
+ const neededExports = [
46
+ "0", "int", "inc", "dec", "nat", "zero_int?", "neg?", "GT"
47
+ ];
48
+ const preamble = buildExportPreamble(neededExports, depLibs);
49
+ const compiled = compileLibrary(preamble + rawSource, {
50
+ source: libPath,
51
+ libraries: depLibs
52
+ });
53
+ const mergedLib = loadLibrary(compiled);
54
+ for (const dep of depLibs) {
55
+ for (const [k, v] of Object.entries(dep.relAlias || {})) {
56
+ if (!mergedLib.relAlias[k]) mergedLib.relAlias[k] = v;
57
+ }
58
+ }
59
+ return mergedLib;
60
+ }
61
+ return loadLibrary(compileLibrary(rawSource, { source: libPath }));
62
+ }
63
+ throw new Error(`Unsupported library extension: ${ext}`);
64
+ }
65
+
14
66
  export function parseCompileOptions(args) {
15
67
  const libraries = [];
16
68
  const exportSpecs = [];
17
69
  let loadedLibrary = false;
70
+ let usePKIR = false;
18
71
 
19
72
  while (args.length > 0) {
20
73
  if (args[0] === "--") {
@@ -22,22 +75,26 @@ export function parseCompileOptions(args) {
22
75
  break;
23
76
  }
24
77
  if (args[0] === "--lib") {
25
- if (loadedLibrary) throw new Error("--lib may only be specified once");
78
+ if (loadedLibrary) {
79
+ throw new Error("--lib may only be specified once");
80
+ }
26
81
  args.shift();
27
82
  const libPath = args.shift();
28
83
  if (!libPath) throw new Error("--lib requires a file argument");
29
- const ext = path.extname(libPath).toLowerCase();
30
- if (ext === ".k") {
31
- libraries.push(loadLibrary(compileLibrary(fs.readFileSync(libPath, "utf8"), { source: libPath })));
32
- } else {
33
- libraries.push(loadLibrary(decodeObject(fs.readFileSync(libPath))));
34
- }
84
+ libraries.push(loadLibraryFromPath(libPath));
35
85
  loadedLibrary = true;
36
86
  } else if (args[0] === "--export") {
37
87
  args.shift();
38
88
  const spec = args.shift();
39
- if (!spec) throw new Error("--export requires a spec argument (name or libname:localname)");
89
+ if (!spec) {
90
+ throw new Error(
91
+ "--export requires a spec argument (name or libname:localname)"
92
+ );
93
+ }
40
94
  exportSpecs.push(spec);
95
+ } else if (args[0] === "--pkir") {
96
+ args.shift();
97
+ usePKIR = true;
41
98
  } else if (args[0].startsWith("--")) {
42
99
  break;
43
100
  } else {
@@ -45,7 +102,7 @@ export function parseCompileOptions(args) {
45
102
  }
46
103
  }
47
104
 
48
- return { libraries, exportSpecs };
105
+ return { libraries, exportSpecs, usePKIR };
49
106
  }
50
107
 
51
108
  export function fileInputType(filePath) {
@@ -53,6 +110,7 @@ export function fileInputType(filePath) {
53
110
  if (ext === ".ko") return "ko";
54
111
  if (ext === ".kvm") return "kvm";
55
112
  if (ext === ".klib") return "klib";
113
+ if (ext === ".pkir") return "pkir";
56
114
  return "source-file";
57
115
  }
58
116
 
@@ -69,11 +127,15 @@ export function resolveProgramInput(args, { allowStdinSource = false } = {}) {
69
127
  return { kind, path: inputArg, sourceName: inputArg };
70
128
  }
71
129
 
72
- if ([".k", ".ko", ".kvm", ".klib"].includes(ext)) {
130
+ if ([".k", ".ko", ".kvm", ".klib", ".pkir"].includes(ext)) {
73
131
  throw new Error(`Input file not found: ${inputArg}`);
74
132
  }
75
133
 
76
- return { kind: "inline-source", source: inputArg, sourceName: "<inline>" };
134
+ return {
135
+ kind: "inline-source",
136
+ source: inputArg,
137
+ sourceName: "<inline>"
138
+ };
77
139
  }
78
140
 
79
141
  export function buildExportPreamble(exports = [], libraries = []) {
@@ -94,9 +156,15 @@ export function buildExportPreamble(exports = [], libraries = []) {
94
156
  }
95
157
  const lines = [];
96
158
  for (const spec of exports) {
97
- const [libName, localName] = spec.includes(":") ? spec.split(":", 2) : [spec, spec];
159
+ const [libName, localName] = spec.includes(":")
160
+ ? spec.split(":", 2)
161
+ : [spec, spec];
98
162
  const hash = aliasMap[libName];
99
- if (!hash) throw new Error(`--export: '${libName}' not found in loaded libraries`);
163
+ if (!hash) {
164
+ throw new Error(
165
+ `--export: '${libName}' not found in loaded libraries`
166
+ );
167
+ }
100
168
  const body = hash.startsWith("@") ? hash.slice(1) : hash;
101
169
  lines.push(`${localName} = @${body};`);
102
170
  }
@@ -116,7 +184,9 @@ export async function compileProgramInputToObject(
116
184
  });
117
185
  object = decodeObject(objBuffer);
118
186
  } else if (input.kind === "stdin-source") {
119
- if (!stdin) throw new Error("stdin source requested without a stdin stream");
187
+ if (!stdin) {
188
+ throw new Error("stdin source requested without a stdin stream");
189
+ }
120
190
  const source = (await readAll(stdin)).toString("utf8");
121
191
  const preamble = buildExportPreamble(exportSpecs, libraries);
122
192
  const objBuffer = compileObjectBuffer(preamble + source, {
@@ -135,7 +205,15 @@ export async function compileProgramInputToObject(
135
205
  } else if (input.kind === "ko" || input.kind === "klib") {
136
206
  object = decodeObject(fs.readFileSync(input.path));
137
207
  } else if (input.kind === "kvm") {
138
- throw new Error("Input is already lowered to .kvm bytecode; use compileLLVMArtifactFromKVM or compileKVMToExecutable.");
208
+ throw new Error(
209
+ "Input is already lowered to .kvm bytecode; use " +
210
+ "compileLLVMArtifactFromKVM or compileKVMToExecutable."
211
+ );
212
+ } else if (input.kind === "pkir") {
213
+ throw new Error(
214
+ "Input is already lowered to .pkir IR; use " +
215
+ "compileLLVMArtifactFromPKIR or compilePKIRToExecutable."
216
+ );
139
217
  } else {
140
218
  throw new Error(`Unsupported input kind: ${input.kind}`);
141
219
  }