@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.
- package/CHANGELOG.md +75 -0
- package/DOCS/CODECS.md +37 -2
- package/DOCS/CODEC_ARCHITECTURE.md +12 -5
- package/DOCS/FILE_FORMATS.md +238 -15
- package/DOCS/POLYMORPHIC_IR.md +104 -0
- package/DOCS/REPL.md +11 -3
- package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
- package/JOURNAL.md +288 -0
- package/README.md +12 -3
- package/Value.mjs +28 -3
- package/access-table.mjs +343 -0
- package/backend-api.mjs +23 -1
- package/backends/llvm/README.md +20 -21
- package/backends/llvm/bin/k-llvm-build.mjs +105 -32
- package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
- package/backends/llvm/bin/k-llvm.mjs +292 -0
- package/backends/llvm/runtime/krt.c +8 -0
- package/backends/llvm/runtime/krt.h +1 -0
- package/backends/llvm/src/cli.mjs +95 -17
- package/backends/llvm/src/executable.mjs +128 -11
- package/backends/llvm/src/kvm2llvm.mjs +10 -5
- package/backends/llvm/src/llvm.mjs +121 -9
- package/backends/llvm/tests/integration.sh +11 -11
- package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
- package/backends/llvm/tests/perf-support.mjs +24 -3
- package/backends/llvm/tests/test-llvm.mjs +22 -1
- package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
- package/backends/wasm/bin/k-wasm-run.mjs +61 -20
- package/backends/wasm/bin/k-wasm.mjs +47 -13
- package/backends/wasm/src/cli.mjs +141 -19
- package/backends/wasm/src/wasm.mjs +130 -23
- package/browser/repl-browser.mjs +16 -5
- package/browser/tree-view.mjs +47 -8
- package/browser/vfs-data.mjs +6 -5
- package/codecs/README.md +1 -0
- package/codecs/djson.mjs +270 -0
- package/codecs/int.mjs +2 -1
- package/codecs/json.mjs +7 -2
- package/codecs/k-parse.mjs +36 -9
- package/codecs/k-print.mjs +26 -6
- package/codecs/runtime/codec-sdk.mjs +24 -6
- package/codecs/runtime/pattern-json.mjs +80 -1
- package/codecs/runtime/prefix-codec.mjs +629 -67
- package/codecs/runtime/show-value.mjs +93 -51
- package/codecs/runtime/unicode-string.mjs +29 -6
- package/codecs/show.mjs +24 -6
- package/codecs/utf16.mjs +2 -1
- package/k.mjs +56 -16
- package/kir.mjs +58 -19
- package/kvm.mjs +49 -12
- package/objects/compile.mjs +37 -23
- package/objects/decompile.mjs +40 -10
- package/objects/extract-aliases.mjs +44 -14
- package/objects/inspect.mjs +19 -14
- package/objects/validate.mjs +18 -11
- package/package.json +11 -3
- package/pkir.mjs +772 -0
- package/repl.mjs +92 -23
- package/scripts/build-repl-html.mjs +22 -3
- package/scripts/perf-ieee.mjs +264 -13
- package/scripts/perf-int.mjs +315 -22
- package/scripts/perf-multiplication.mjs +58 -7
- package/scripts/perf-poly.mjs +306 -24
- package/scripts/run-tests.mjs +3 -0
- package/tests/test-access-table.mjs +213 -0
- package/tests/test-codecs.mjs +168 -0
- package/tests/test-pkir.mjs +530 -0
- package/tests/test-polymorphic-tdd.mjs +196 -0
- package/tests/test-repl-html.mjs +16 -2
- package/tests/test-repl.mjs +63 -1
- 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!");
|
package/tests/test-repl-html.mjs
CHANGED
|
@@ -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
|
|
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 {
|
|
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");
|
package/tests/test-repl.mjs
CHANGED
|
@@ -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
|
-
}
|