@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
@@ -0,0 +1,196 @@
1
+ import assert from "node:assert/strict";
2
+ import { createState, evaluateInput } from "../backend-api.mjs";
3
+
4
+ console.log("==> Running Polymorphic TDD Suite (tests/test-polymorphic-tdd.mjs)");
5
+
6
+ const state = createState();
7
+
8
+ async function evalK(snippet) {
9
+ const res = await evaluateInput(snippet, state);
10
+ assert.ok(
11
+ res && res.length > 0 && !res[0].includes("undefined"),
12
+ `Evaluation failed for snippet:\n${snippet}`
13
+ );
14
+ const raw = res[0];
15
+ const splitIdx = raw.indexOf(" ?");
16
+ const val = splitIdx !== -1 ? raw.slice(0, splitIdx) : raw;
17
+ const type = splitIdx !== -1 ? raw.slice(splitIdx + 1) : "";
18
+ return { val, type, raw };
19
+ }
20
+
21
+ // --------------------------------------------------------------------
22
+ // Test 1: Search - Homogeneous List with Opaque Fields
23
+ // --------------------------------------------------------------------
24
+ {
25
+ console.log("--> Test 1: Search on Homogeneous List with Opaque Fields");
26
+ const script = `
27
+ search = {()list, {}|nil result} search_;
28
+ search_ = ?{X list, X result}
29
+ <
30
+ {.list/nil if, .result then}.then,
31
+ <
32
+ {.list/cons.car.flag/true if, {.list/cons.cdr list, {.list/cons.car car, .result cdr}|cons result} then} .then,
33
+ {.list/cons.cdr list, .result result}
34
+ > search_
35
+ >;
36
+
37
+ i1 = {{}|alpha id, {}|true flag, {}|payload_a payload};
38
+ i2 = {{}|beta id, {}|false flag, {}|payload_b payload};
39
+ i3 = {{}|gamma id, {}|true flag, {}|payload_c payload};
40
+
41
+ l_homo = {i1 car, {i2 car, {i3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
42
+ l_homo search
43
+ `;
44
+
45
+ const out = await evalK(script);
46
+ // Expect i3 followed by i1 (accumulator reverses order); i2 is excluded
47
+ assert.match(out.val, /gamma/);
48
+ assert.match(out.val, /alpha/);
49
+ assert.doesNotMatch(out.val, /beta/);
50
+ // Check opaque fields are intact
51
+ assert.match(out.val, /payload_c/);
52
+ assert.match(out.val, /payload_a/);
53
+ console.log(" Passed: Homogeneous list filtered with opaque payload preserved.");
54
+ }
55
+
56
+ // --------------------------------------------------------------------
57
+ // Test 2: Search - Pathological Field Ordering & Disparate Field Schemas
58
+ // --------------------------------------------------------------------
59
+ {
60
+ console.log("--> Test 2: Search with Out-of-Order Fields & Disparate Value Types");
61
+ const script = `
62
+ p1 = {{}|large_blob data, {}|true flag, {}|first id};
63
+ p2 = {{}|false flag, {}|second id, {}|medium_blob data};
64
+ p3 = {{}|third id, {}|tiny_blob data, {}|true flag};
65
+
66
+ l_reorder = {p1 car, {p2 car, {p3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
67
+ l_reorder search
68
+ `;
69
+
70
+ const out = await evalK(script);
71
+ // Expect p3 matching first, then p1 matching; p2 excluded
72
+ assert.match(out.val, /third/);
73
+ assert.match(out.val, /tiny_blob/);
74
+ assert.match(out.val, /first/);
75
+ assert.match(out.val, /large_blob/);
76
+ assert.doesNotMatch(out.val, /second/);
77
+ assert.doesNotMatch(out.val, /medium_blob/);
78
+ console.log(" Passed: Out-of-order fields & disparate value types preserved.");
79
+ }
80
+
81
+ // --------------------------------------------------------------------
82
+ // Test 3: Search - Boundary Cases (Empty List, All Match, None Match)
83
+ // --------------------------------------------------------------------
84
+ {
85
+ console.log("--> Test 3: Search Boundary Cases (Empty, None Match, All Match)");
86
+
87
+ // 3a: Empty list
88
+ const outEmpty = await evalK(`{}|nil search`);
89
+ assert.match(outEmpty.val, /nil/);
90
+
91
+ // 3b: None match (all flag: false)
92
+ const outNone = await evalK(`
93
+ n1 = {{}|f1 id, {}|false flag};
94
+ n2 = {{}|f2 id, {}|false flag};
95
+ l_none = {n1 car, {n2 car, {}|nil cdr}|cons cdr}|cons;
96
+ l_none search
97
+ `);
98
+ assert.match(outNone.val, /nil/);
99
+
100
+ // 3c: All match (all flag: true)
101
+ const outAll = await evalK(`
102
+ m1 = {{}|t1 id, {}|true flag};
103
+ m2 = {{}|t2 id, {}|true flag};
104
+ l_all = {m1 car, {m2 car, {}|nil cdr}|cons cdr}|cons;
105
+ l_all search
106
+ `);
107
+ assert.match(outAll.val, /t2.*t1/);
108
+ console.log(" Passed: Boundary cases handled cleanly.");
109
+ }
110
+
111
+ // --------------------------------------------------------------------
112
+ // Test 4: Multi-Variant Dispatch with Varying Offsets (extract_id)
113
+ // --------------------------------------------------------------------
114
+ {
115
+ console.log("--> Test 4: Multi-Variant Dispatch with Varying Offsets");
116
+ const script = `
117
+ extract_id = <
118
+ /click.id,
119
+ /sensor.id,
120
+ /ping.id
121
+ >;
122
+
123
+ evt_c = {{}|evt_click id, {}|active status}|click;
124
+ evt_s = {{}|battery_low status, {}|evt_sensor id, {}|reading data}|sensor;
125
+ evt_p = {{}|evt_ping id}|ping;
126
+ `;
127
+ await evalK(script);
128
+
129
+ const outC = await evalK("evt_c extract_id");
130
+ assert.match(outC.val, /evt_click/);
131
+
132
+ const outS = await evalK("evt_s extract_id");
133
+ assert.match(outS.val, /evt_sensor/);
134
+
135
+ const outP = await evalK("evt_p extract_id");
136
+ assert.match(outP.val, /evt_ping/);
137
+ console.log(" Passed: Common field extracted across variants with distinct layouts.");
138
+ }
139
+
140
+ // --------------------------------------------------------------------
141
+ // Test 5: Polymorphic Zip - Concurrency of Two Independent Types
142
+ // --------------------------------------------------------------------
143
+ {
144
+ console.log("--> Test 5: Polymorphic Zip across Independent Types");
145
+ const script = `
146
+ zip = <
147
+ {.xs /nil if, {}|nil then} .then,
148
+ {.ys /nil if, {}|nil then} .then,
149
+ {
150
+ {.xs/cons.car x, .ys/cons.car y} car,
151
+ {.xs/cons.cdr xs, .ys/cons.cdr ys} zip cdr
152
+ }|cons
153
+ >;
154
+
155
+ xs = {{}|num1 car, {{}|num2 car, {}|nil cdr}|cons cdr}|cons;
156
+ ys = {
157
+ {{}|rec1_name name, {}|flag_true flag} car,
158
+ {{{}|rec2_name name, {}|flag_false flag} car, {}|nil cdr}|cons cdr
159
+ }|cons;
160
+
161
+ {xs xs, ys ys} zip
162
+ `;
163
+
164
+ const out = await evalK(script);
165
+ assert.match(out.val, /num1.*rec1_name/);
166
+ assert.match(out.val, /num2.*rec2_name/);
167
+ console.log(" Passed: Independent polymorphic streams paired into product.");
168
+ }
169
+
170
+ // --------------------------------------------------------------------
171
+ // Test 6: Polymorphic Reverse with Opaque Records
172
+ // --------------------------------------------------------------------
173
+ {
174
+ console.log("--> Test 6: Polymorphic Reverse with Opaque Records");
175
+ const script = `
176
+ poly_rev = {()xs, {}|nil acc} _poly_rev;
177
+ _poly_rev = ?{X xs, X acc} <
178
+ {.xs/nil if, .acc then} .then,
179
+ {.xs/cons.cdr xs, {.xs/cons.car car, .acc cdr}|cons acc} _poly_rev
180
+ >;
181
+
182
+ r1 = {{}|first item, {}|opaque_1 extra};
183
+ r2 = {{}|second item, {}|opaque_2 extra};
184
+ r3 = {{}|third item, {}|opaque_3 extra};
185
+ list_to_rev = {r1 car, {r2 car, {r3 car, {}|nil cdr}|cons cdr}|cons cdr}|cons;
186
+
187
+ list_to_rev poly_rev
188
+ `;
189
+
190
+ const out = await evalK(script);
191
+ assert.match(out.val, /third.*second.*first/);
192
+ assert.match(out.val, /opaque_3.*opaque_2.*opaque_1/);
193
+ console.log(" Passed: Polymorphic list reversed with opaque record payloads intact.");
194
+ }
195
+
196
+ console.log("\n==> All Polymorphic TDD Tests Passed Successfully!");
@@ -140,6 +140,7 @@ if (chromiumBin) {
140
140
  const expectedFiles = [
141
141
  "arithmetics.k",
142
142
  "core.k",
143
+ "djson.mjs",
143
144
  "ieee.k",
144
145
  "ieee.mjs",
145
146
  "int.mjs",
@@ -622,9 +623,12 @@ if (chromiumBin) {
622
623
  assert.strictEqual(narrowNavbarCheck.hasTiming, false, "Help modal should not contain :timing");
623
624
  assert.strictEqual(narrowNavbarCheck.hasCodecDefine, false, "Help modal should not contain :codec define");
624
625
 
625
- // 6. Test dual engine switching in browser (wasm <-> js)
626
+ // 6. Test 3-way engine switching in browser (wasm -> pkir -> js)
626
627
  const engineCheck = await evaluateAsync(`(async () => {
627
628
  const initialEngine = window.kRepl.getEngine();
629
+ await window.kRepl.executeCommand(":engine pkir");
630
+ const pkirEngine = window.kRepl.getEngine();
631
+ const badgePkirText = document.getElementById("engine-badge")?.textContent;
628
632
  await window.kRepl.executeCommand(":engine js");
629
633
  const jsEngine = window.kRepl.getEngine();
630
634
  const badgeJsText = document.getElementById("engine-badge")?.textContent;
@@ -632,9 +636,19 @@ if (chromiumBin) {
632
636
  await window.kRepl.executeCommand(":engine wasm");
633
637
  const wasmEngine = window.kRepl.getEngine();
634
638
  const badgeWasmText = document.getElementById("engine-badge")?.textContent;
635
- return { initialEngine, jsEngine, wasmEngine, badgeJsText, badgeWasmText };
639
+ return {
640
+ initialEngine,
641
+ pkirEngine,
642
+ jsEngine,
643
+ wasmEngine,
644
+ badgePkirText,
645
+ badgeJsText,
646
+ badgeWasmText
647
+ };
636
648
  })()`);
637
649
  assert.strictEqual(engineCheck.initialEngine, "wasm");
650
+ assert.strictEqual(engineCheck.pkirEngine, "pkir");
651
+ assert.strictEqual(engineCheck.badgePkirText, "pkir-wasm");
638
652
  assert.strictEqual(engineCheck.jsEngine, "js");
639
653
  assert.strictEqual(engineCheck.badgeJsText, "js-in-process");
640
654
  assert.strictEqual(engineCheck.wasmEngine, "wasm");
@@ -226,6 +226,18 @@ output = await evaluateInput(':input json.mjs {"left":true,"right":false}', json
226
226
  assert.match(output[0], /json\.mjs: \{"left":true,"right":false\}/);
227
227
  assert.equal(promptForState(jsonState), "> ");
228
228
 
229
+ // Test djson.mjs in repl
230
+ const djsonState = createState();
231
+ output = await evaluateInput(":codec load ./codecs/djson.mjs", djsonState);
232
+ assert.match(output[0], /^loaded codec djson\.mjs/);
233
+ completions = completeInput(":input dj", djsonState)[0];
234
+ assert(completions.includes(":input djson.mjs"));
235
+ output = await evaluateInput(
236
+ ':input djson.mjs {"msg":"hello","values":[1,2]}',
237
+ djsonState
238
+ );
239
+ assert.match(output[0], /djson\.mjs: \{"msg":"hello","values":\[1,2\]\}/);
240
+
229
241
  const replPath = fileURLToPath(new URL("../repl.mjs", import.meta.url));
230
242
  try {
231
243
  fs.rmSync(symlinkPath, { force: true });
@@ -471,6 +483,10 @@ output = await evaluateInput(":wasm", timingState);
471
483
  assert.equal(output[0], "engine set to wasm");
472
484
  assert.equal(timingState.engine, "wasm");
473
485
 
486
+ output = await evaluateInput(":pkir", timingState);
487
+ assert.equal(output[0], "engine set to pkir");
488
+ assert.equal(timingState.engine, "pkir");
489
+
474
490
  output = await evaluateInput(":js", timingState);
475
491
  assert.equal(output[0], "engine set to js");
476
492
  assert.equal(timingState.engine, "js");
@@ -483,12 +499,13 @@ assert.equal(timingState.engine, "js");
483
499
  // Verify invalid engine error
484
500
  await assert.rejects(
485
501
  () => evaluateInput(":engine invalid", timingState),
486
- /Invalid engine 'invalid'\. Valid engines: wasm, js/
502
+ /Invalid engine 'invalid'\. Valid engines: wasm, pkir, js/
487
503
  );
488
504
 
489
505
  // Verify autocompletion for :engine
490
506
  completions = completeInput(":engine ", timingState)[0];
491
507
  assert(completions.includes(":engine wasm"));
508
+ assert(completions.includes(":engine pkir"));
492
509
  assert(completions.includes(":engine js"));
493
510
 
494
511
  // Verify evaluating expressions in JS engine
@@ -509,6 +526,30 @@ output = await evaluateInput("{ 10 int x, 5 int y } plus", jsState);
509
526
  assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
510
527
  assert.match(output[1], /\(js\)/);
511
528
 
529
+ // Verify evaluating expressions in PKIR-powered Wasm engine
530
+ const pkirState = createState({ engine: "pkir" });
531
+ assert.equal(pkirState.engine, "pkir");
532
+
533
+ output = await evaluateInput(
534
+ "succ = | succ;\n{} | zero succ succ",
535
+ pkirState
536
+ );
537
+ assert.match(output[0], /^\{\}\|zero\|succ\|succ/);
538
+ assert.match(output[1], /\(pkir\)/);
539
+
540
+ output = await evaluateInput(
541
+ "not = </true | false, /false | true >;\n{} | true not",
542
+ pkirState
543
+ );
544
+ assert.match(output[0], /^\{\}\|false/);
545
+ assert.match(output[1], /\(pkir\)/);
546
+
547
+ output = await evaluateInput(":load Examples/arithmetics.k", pkirState);
548
+ assert.equal(output[0], "loaded Examples/arithmetics.k");
549
+ output = await evaluateInput("{ 10 int x, 5 int y } plus", pkirState);
550
+ assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
551
+ assert.match(output[1], /\(pkir\)/);
552
+
512
553
  // Verify open product projection in wasm engine
513
554
  const wasmListState = createState({ engine: "wasm" });
514
555
  await evaluateInput(":load Examples/arithmetics.k", wasmListState);
@@ -519,6 +560,27 @@ output = await evaluateInput("len", wasmListState);
519
560
  assert.match(output[0], /int\.mjs:\s*3/);
520
561
  assert.match(output[1], /\(wasm\)/);
521
562
 
563
+ // Verify :limit command and bounded printing
564
+ const limitState = createState({ engine: "wasm" });
565
+ output = await evaluateInput(":limit", limitState);
566
+ assert.equal(output[0], "Current output limit: none");
567
+
568
+ output = await evaluateInput(":limit 40", limitState);
569
+ assert.equal(output[0], "output limit set to 40");
570
+
571
+ output = await evaluateInput(":limit", limitState);
572
+ assert.equal(output[0], "Current output limit: 40");
573
+
574
+ output = await evaluateInput(":load Examples/arithmetics.k", limitState);
575
+ output = await evaluateInput("{ 10 int x, 5 int y } plus", limitState);
576
+ assert(output[0].includes("..."));
577
+ assert(output[0].length <= 80);
578
+
579
+ output = await evaluateInput(":limit none", limitState);
580
+ assert.equal(output[0], "output limit disabled");
581
+ output = await evaluateInput(":limit", limitState);
582
+ assert.equal(output[0], "Current output limit: none");
583
+
522
584
  fs.rmSync(tmpDir, { recursive: true, force: true });
523
585
  fs.rmSync(symlinkPath, { force: true });
524
586
  console.log("OK");
@@ -1,160 +0,0 @@
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
- import { decodeObject } from "@fraczak/k/object.mjs";
11
- import { decodeWire } from "@fraczak/k/codecs/runtime/prefix-codec.mjs";
12
- import { compileObjectToExecutable } from "../src/executable.mjs";
13
-
14
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
-
16
- function usage(stream = console.error) {
17
- const prog = argv[1] || "k-llvm-jit.mjs";
18
- stream(`Usage: node ${prog} [options] object-file`);
19
- stream("Compile and execute a k .ko/.klib object against the stdin binary envelope.");
20
- stream("");
21
- stream("The runner reads a binary k pattern+value envelope from stdin, specializes");
22
- stream("the object with that envelope pattern, then writes a binary envelope to stdout.");
23
- stream("");
24
- stream("Options:");
25
- stream(" --retype rel Relation to specialize. Defaults to object main.");
26
- stream(" --cache-dir path Directory for compiled native executables.");
27
- stream(" --no-cache Compile in a temporary directory and remove it after execution.");
28
- stream(" -h, --help Show this help.");
29
- }
30
-
31
- async function readStdinBuffer() {
32
- const chunks = [];
33
- for await (const chunk of stdin) {
34
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
35
- }
36
- return Buffer.concat(chunks);
37
- }
38
-
39
- let cachedBackendFingerprint = null;
40
-
41
- function backendFingerprint() {
42
- if (cachedBackendFingerprint != null) return cachedBackendFingerprint;
43
- const hash = crypto.createHash("sha256");
44
- for (const relPath of ["src/llvm.mjs", "src/kvm2llvm.mjs", "src/executable.mjs", "runtime/krt.c", "runtime/krt.h"]) {
45
- hash.update(relPath);
46
- hash.update("\0");
47
- hash.update(fs.readFileSync(path.join(root, relPath)));
48
- hash.update("\0");
49
- }
50
- cachedBackendFingerprint = hash.digest("hex").slice(0, 16);
51
- return cachedBackendFingerprint;
52
- }
53
-
54
- function cacheKey({ objectBuffer, relation, inputPattern }) {
55
- const hash = crypto.createHash("sha256");
56
- hash.update("k-llvm-jit-v2\0");
57
- hash.update(backendFingerprint());
58
- hash.update("\0");
59
- hash.update(relation);
60
- hash.update("\0");
61
- hash.update(JSON.stringify(inputPattern));
62
- hash.update("\0");
63
- hash.update(objectBuffer);
64
- return hash.digest("hex");
65
- }
66
-
67
- function runExecutable(exePath, inputBuffer) {
68
- return new Promise((resolve, reject) => {
69
- const child = spawn(exePath, [], { stdio: ["pipe", "pipe", "pipe"] });
70
- const stdoutChunks = [];
71
- const stderrChunks = [];
72
-
73
- child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
74
- child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
75
- child.on("error", reject);
76
- child.on("close", (status) => {
77
- if (status !== 0) {
78
- const stderr = Buffer.concat(stderrChunks).toString("utf8");
79
- reject(new Error(`${exePath} failed with status ${status}${stderr ? `\n${stderr}` : ""}`));
80
- return;
81
- }
82
- resolve(Buffer.concat(stdoutChunks));
83
- });
84
-
85
- child.stdin.end(inputBuffer);
86
- });
87
- }
88
-
89
- function compileCached({ object, objectBuffer, relation, inputPattern, cacheDir }) {
90
- fs.mkdirSync(cacheDir, { recursive: true });
91
- const key = cacheKey({ objectBuffer, relation, inputPattern });
92
- const exePath = path.join(cacheDir, `${key}.exe`);
93
- if (!fs.existsSync(exePath)) {
94
- const tmpPath = path.join(cacheDir, `${key}.${process.pid}.tmp`);
95
- compileObjectToExecutable(object, tmpPath, { relation, inputPattern });
96
- fs.renameSync(tmpPath, exePath);
97
- }
98
- return exePath;
99
- }
100
-
101
- function compileTemporary({ object, relation, inputPattern }) {
102
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-llvm-jit-"));
103
- const exePath = path.join(tmpDir, "program");
104
- compileObjectToExecutable(object, exePath, { relation, inputPattern });
105
- return { exePath, tmpDir };
106
- }
107
-
108
- try {
109
- const args = argv.slice(2);
110
- if (args.includes("-h") || args.includes("--help")) {
111
- usage(console.log);
112
- exit(0);
113
- }
114
-
115
- let relation = null;
116
- let cacheDir = path.join(os.tmpdir(), "k-llvm-jit-cache");
117
- let useCache = true;
118
- while (args.length > 0 && args[0].startsWith("--")) {
119
- const option = args.shift();
120
- if (option === "--retype") {
121
- relation = args.shift();
122
- if (!relation) throw new Error("--retype requires a relation name");
123
- } else if (option === "--cache-dir") {
124
- cacheDir = args.shift();
125
- if (!cacheDir) throw new Error("--cache-dir requires a path");
126
- } else if (option === "--no-cache") {
127
- useCache = false;
128
- } else {
129
- throw new Error(`Unknown option: ${option}`);
130
- }
131
- }
132
-
133
- const objectPath = args.shift() || null;
134
- if (objectPath == null) throw new Error("object-file is required");
135
- if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
136
-
137
- const objectBuffer = fs.readFileSync(objectPath);
138
- const object = decodeObject(objectBuffer);
139
- const relationName = relation || object.main;
140
- const inputBuffer = await readStdinBuffer();
141
- const { pattern: inputPattern } = decodeWire(inputBuffer);
142
-
143
- let tmpDir = null;
144
- try {
145
- const exePath = useCache
146
- ? compileCached({ object, objectBuffer, relation: relationName, inputPattern, cacheDir })
147
- : (() => {
148
- const compiled = compileTemporary({ object, relation: relationName, inputPattern });
149
- tmpDir = compiled.tmpDir;
150
- return compiled.exePath;
151
- })();
152
- stdout.write(await runExecutable(exePath, inputBuffer));
153
- } finally {
154
- if (tmpDir != null) fs.rmSync(tmpDir, { recursive: true, force: true });
155
- }
156
- } catch (error) {
157
- console.error(error.stack || error.message || String(error));
158
- usage();
159
- exit(1);
160
- }