@fraczak/k 6.3.1 → 6.3.2
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/a.klib +1 -1
- package/a.kvm +142676 -0
- package/a.wasm +0 -0
- package/benchmark-results.html +532 -0
- package/benchmark-results.json +480 -0
- package/kvm.mjs +78 -11
- package/package.json +4 -1
- package/scripts/benchmark.mjs +393 -0
- package/scripts/git-hooks/pre-commit +10 -0
- package/scripts/render-benchmark.mjs +343 -0
- package/scripts/run-tests.mjs +1 -0
- package/tests/integration.sh +7 -0
- package/tests/test-arithmetics.mjs +95 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const inputPath = path.resolve(root, process.argv[2] || "benchmark-results.json");
|
|
9
|
+
const outputPath = path.resolve(root, process.argv[3] || "benchmark-results.html");
|
|
10
|
+
|
|
11
|
+
const rowOrder = [
|
|
12
|
+
"Native JS (Envelope-Aware)",
|
|
13
|
+
"Native JS (Envelope-Free)",
|
|
14
|
+
"kVM Interpreter (Env-Free)"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const columnGroups = [
|
|
18
|
+
{
|
|
19
|
+
title: "ieee",
|
|
20
|
+
source: "Examples/ieee.k",
|
|
21
|
+
columns: [
|
|
22
|
+
{ key: "ieee.add", label: "add" },
|
|
23
|
+
{ key: "ieee.div", label: "div" },
|
|
24
|
+
{ key: "ieee.mul", label: "mul" },
|
|
25
|
+
{ key: "ieee.sub", label: "sub" }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: "int",
|
|
30
|
+
source: "Examples/arithmetics.k",
|
|
31
|
+
columns: [
|
|
32
|
+
{ key: "int.factorial", label: "factorial" },
|
|
33
|
+
{ key: "int.gcd.90d", label: "gcd" },
|
|
34
|
+
{ key: "int.times.220d", label: "times" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function escapeHtml(value) {
|
|
40
|
+
return String(value)
|
|
41
|
+
.replaceAll("&", "&")
|
|
42
|
+
.replaceAll("<", "<")
|
|
43
|
+
.replaceAll(">", ">")
|
|
44
|
+
.replaceAll("\"", """);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fmtMs(value) {
|
|
48
|
+
return value == null ? "n/a" : `${value.toFixed(2)} ms`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fmtMiB(value) {
|
|
52
|
+
return value == null ? "n/a" : `${value.toFixed(1)} MiB`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function fmtRatio(value) {
|
|
56
|
+
return Number.isFinite(value) ? `${value.toFixed(2)}x` : "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resultMap(results) {
|
|
60
|
+
const map = new Map();
|
|
61
|
+
for (const result of results) {
|
|
62
|
+
if (!map.has(result.mode)) map.set(result.mode, new Map());
|
|
63
|
+
map.get(result.mode).set(result.case, result);
|
|
64
|
+
}
|
|
65
|
+
return map;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function factorialResults(byCase) {
|
|
69
|
+
return [...byCase.entries()]
|
|
70
|
+
.filter(([caseName]) => caseName.startsWith("int.factorial."))
|
|
71
|
+
.sort(([a], [b]) => Number(a.split(".").at(-1)) - Number(b.split(".").at(-1)))
|
|
72
|
+
.map(([caseName, result]) => ({
|
|
73
|
+
input: caseName.split(".").at(-1),
|
|
74
|
+
result
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function bestForColumn(results, column) {
|
|
79
|
+
const relevant = column.key === "int.factorial"
|
|
80
|
+
? results.filter(result => result.case.startsWith("int.factorial."))
|
|
81
|
+
: results.filter(result => result.case === column.key);
|
|
82
|
+
return relevant
|
|
83
|
+
.filter(result => result.status === "ok" && typeof result.avgMs === "number")
|
|
84
|
+
.sort((a, b) => a.avgMs - b.avgMs)[0] || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function cellFor(result, best) {
|
|
88
|
+
if (!result) return `<span class="muted">not run</span>`;
|
|
89
|
+
if (result.status !== "ok") return `<span class="bad">${escapeHtml(result.status)}</span>`;
|
|
90
|
+
const ratio = best ? result.avgMs / best.avgMs : NaN;
|
|
91
|
+
return `
|
|
92
|
+
<div class="time">${fmtMs(result.avgMs)} <span>${fmtRatio(ratio)}</span></div>
|
|
93
|
+
<div class="sub">rss ${fmtMiB(result.maxRssMiB)}</div>`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function factorialCell(byCase, allResults) {
|
|
97
|
+
const rows = factorialResults(byCase);
|
|
98
|
+
if (rows.length === 0) return `<span class="muted">not run</span>`;
|
|
99
|
+
return rows.map(({ input, result }) => {
|
|
100
|
+
const best = bestForColumn(allResults, { key: result.case });
|
|
101
|
+
return `
|
|
102
|
+
<div class="mini">
|
|
103
|
+
<span class="mini-label">${escapeHtml(input)}</span>
|
|
104
|
+
<div>${cellFor(result, best)}</div>
|
|
105
|
+
</div>`;
|
|
106
|
+
}).join("");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildMatrix(doc) {
|
|
110
|
+
const results = doc.results || [];
|
|
111
|
+
const byMode = resultMap(results);
|
|
112
|
+
|
|
113
|
+
const groupHeader = columnGroups.map(group =>
|
|
114
|
+
`<th colspan="${group.columns.length}" class="group">${escapeHtml(group.title)} <span>${escapeHtml(group.source)}</span></th>`
|
|
115
|
+
).join("");
|
|
116
|
+
|
|
117
|
+
const columnHeader = columnGroups.flatMap(group => group.columns)
|
|
118
|
+
.map(column => `<th>${escapeHtml(column.label)}</th>`)
|
|
119
|
+
.join("");
|
|
120
|
+
|
|
121
|
+
const body = rowOrder.map(mode => {
|
|
122
|
+
const byCase = byMode.get(mode) || new Map();
|
|
123
|
+
const cells = columnGroups.flatMap(group => group.columns).map(column => {
|
|
124
|
+
if (column.key === "int.factorial") {
|
|
125
|
+
return `<td>${factorialCell(byCase, results)}</td>`;
|
|
126
|
+
}
|
|
127
|
+
return `<td>${cellFor(byCase.get(column.key), bestForColumn(results, column))}</td>`;
|
|
128
|
+
}).join("");
|
|
129
|
+
return `<tr><th class="row-head">${escapeHtml(mode)}</th>${cells}</tr>`;
|
|
130
|
+
}).join("");
|
|
131
|
+
|
|
132
|
+
return `
|
|
133
|
+
<table class="matrix">
|
|
134
|
+
<thead>
|
|
135
|
+
<tr><th></th>${groupHeader}</tr>
|
|
136
|
+
<tr><th>implementation</th>${columnHeader}</tr>
|
|
137
|
+
</thead>
|
|
138
|
+
<tbody>${body}</tbody>
|
|
139
|
+
</table>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildDetails(results) {
|
|
143
|
+
return results
|
|
144
|
+
.slice()
|
|
145
|
+
.sort((a, b) => a.case.localeCompare(b.case) || a.mode.localeCompare(b.mode))
|
|
146
|
+
.map(result => `
|
|
147
|
+
<tr>
|
|
148
|
+
<td>${escapeHtml(result.case)}</td>
|
|
149
|
+
<td>${escapeHtml(result.mode)}</td>
|
|
150
|
+
<td>${escapeHtml(result.status)}</td>
|
|
151
|
+
<td class="num">${fmtMs(result.avgMs)}</td>
|
|
152
|
+
<td class="num">${fmtMs(result.minMs)}</td>
|
|
153
|
+
<td class="num">${fmtMs(result.maxMs)}</td>
|
|
154
|
+
<td class="num">${fmtMiB(result.maxRssMiB)}</td>
|
|
155
|
+
<td class="hash">${escapeHtml(result.resultDigest || "")}</td>
|
|
156
|
+
</tr>`)
|
|
157
|
+
.join("");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function render(doc) {
|
|
161
|
+
const results = doc.results || [];
|
|
162
|
+
const generatedAt = doc.generatedAt ? new Date(doc.generatedAt).toLocaleString() : "unknown";
|
|
163
|
+
const completed = results.filter(result => result.status === "ok").length;
|
|
164
|
+
|
|
165
|
+
return `<!doctype html>
|
|
166
|
+
<html lang="en">
|
|
167
|
+
<head>
|
|
168
|
+
<meta charset="utf-8">
|
|
169
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
170
|
+
<title>k Benchmark Results</title>
|
|
171
|
+
<style>
|
|
172
|
+
:root {
|
|
173
|
+
color-scheme: light;
|
|
174
|
+
--ink: #17212f;
|
|
175
|
+
--muted: #627083;
|
|
176
|
+
--line: #d9e0ea;
|
|
177
|
+
--paper: #f5f7fa;
|
|
178
|
+
--panel: #fff;
|
|
179
|
+
--accent: #1d5fd1;
|
|
180
|
+
--soft: #eef3fb;
|
|
181
|
+
--bad: #b42318;
|
|
182
|
+
}
|
|
183
|
+
* { box-sizing: border-box; }
|
|
184
|
+
body {
|
|
185
|
+
margin: 0;
|
|
186
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
187
|
+
color: var(--ink);
|
|
188
|
+
background: var(--paper);
|
|
189
|
+
}
|
|
190
|
+
header {
|
|
191
|
+
padding: 28px 32px 20px;
|
|
192
|
+
background: var(--panel);
|
|
193
|
+
border-bottom: 1px solid var(--line);
|
|
194
|
+
}
|
|
195
|
+
h1 { margin: 0 0 10px; font-size: 30px; letter-spacing: 0; }
|
|
196
|
+
.meta {
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-wrap: wrap;
|
|
199
|
+
gap: 14px;
|
|
200
|
+
color: var(--muted);
|
|
201
|
+
font-size: 14px;
|
|
202
|
+
}
|
|
203
|
+
main {
|
|
204
|
+
max-width: 1320px;
|
|
205
|
+
margin: 0 auto;
|
|
206
|
+
padding: 24px 32px 40px;
|
|
207
|
+
}
|
|
208
|
+
.summary {
|
|
209
|
+
display: grid;
|
|
210
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
211
|
+
gap: 12px;
|
|
212
|
+
margin-bottom: 18px;
|
|
213
|
+
}
|
|
214
|
+
.metric, .panel {
|
|
215
|
+
background: var(--panel);
|
|
216
|
+
border: 1px solid var(--line);
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
}
|
|
219
|
+
.metric { padding: 14px 16px; }
|
|
220
|
+
.metric .label { color: var(--muted); font-size: 13px; }
|
|
221
|
+
.metric .value { margin-top: 5px; font-size: 22px; font-weight: 700; }
|
|
222
|
+
.panel { overflow: auto; }
|
|
223
|
+
table {
|
|
224
|
+
width: 100%;
|
|
225
|
+
border-collapse: collapse;
|
|
226
|
+
}
|
|
227
|
+
th, td {
|
|
228
|
+
border-bottom: 1px solid var(--line);
|
|
229
|
+
padding: 12px;
|
|
230
|
+
text-align: left;
|
|
231
|
+
vertical-align: top;
|
|
232
|
+
font-size: 13px;
|
|
233
|
+
}
|
|
234
|
+
thead th {
|
|
235
|
+
background: #fafbfd;
|
|
236
|
+
color: var(--muted);
|
|
237
|
+
font-weight: 700;
|
|
238
|
+
}
|
|
239
|
+
.matrix {
|
|
240
|
+
min-width: 980px;
|
|
241
|
+
}
|
|
242
|
+
.matrix .group {
|
|
243
|
+
text-align: center;
|
|
244
|
+
color: var(--ink);
|
|
245
|
+
font-size: 15px;
|
|
246
|
+
background: var(--soft);
|
|
247
|
+
}
|
|
248
|
+
.matrix .group span {
|
|
249
|
+
display: block;
|
|
250
|
+
color: var(--muted);
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
font-weight: 500;
|
|
253
|
+
margin-top: 2px;
|
|
254
|
+
}
|
|
255
|
+
.row-head {
|
|
256
|
+
width: 230px;
|
|
257
|
+
color: var(--ink);
|
|
258
|
+
font-weight: 800;
|
|
259
|
+
background: #fbfcfe;
|
|
260
|
+
}
|
|
261
|
+
.time {
|
|
262
|
+
font-weight: 800;
|
|
263
|
+
font-variant-numeric: tabular-nums;
|
|
264
|
+
white-space: nowrap;
|
|
265
|
+
}
|
|
266
|
+
.time span {
|
|
267
|
+
color: var(--muted);
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
margin-left: 4px;
|
|
270
|
+
}
|
|
271
|
+
.sub {
|
|
272
|
+
margin-top: 3px;
|
|
273
|
+
color: var(--muted);
|
|
274
|
+
font-size: 12px;
|
|
275
|
+
white-space: nowrap;
|
|
276
|
+
}
|
|
277
|
+
.mini {
|
|
278
|
+
display: grid;
|
|
279
|
+
grid-template-columns: 42px 1fr;
|
|
280
|
+
gap: 8px;
|
|
281
|
+
padding: 5px 0;
|
|
282
|
+
border-bottom: 1px solid #edf1f6;
|
|
283
|
+
}
|
|
284
|
+
.mini:last-child { border-bottom: 0; }
|
|
285
|
+
.mini-label {
|
|
286
|
+
color: var(--muted);
|
|
287
|
+
font-weight: 800;
|
|
288
|
+
font-variant-numeric: tabular-nums;
|
|
289
|
+
}
|
|
290
|
+
.muted { color: var(--muted); }
|
|
291
|
+
.bad { color: var(--bad); font-weight: 800; }
|
|
292
|
+
h2 { margin: 24px 0 10px; font-size: 18px; }
|
|
293
|
+
.details { margin-top: 16px; }
|
|
294
|
+
.details table { min-width: 920px; }
|
|
295
|
+
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
296
|
+
.hash { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--muted); }
|
|
297
|
+
@media (max-width: 900px) {
|
|
298
|
+
header, main { padding-left: 16px; padding-right: 16px; }
|
|
299
|
+
.summary { grid-template-columns: 1fr; }
|
|
300
|
+
}
|
|
301
|
+
</style>
|
|
302
|
+
</head>
|
|
303
|
+
<body>
|
|
304
|
+
<header>
|
|
305
|
+
<h1>k Benchmark Results</h1>
|
|
306
|
+
<div class="meta">
|
|
307
|
+
<span>Generated: ${escapeHtml(generatedAt)}</span>
|
|
308
|
+
<span>Node: ${escapeHtml(doc.node || "unknown")}</span>
|
|
309
|
+
<span>Platform: ${escapeHtml(doc.platform || "unknown")}</span>
|
|
310
|
+
<span>CPUs: ${escapeHtml(doc.cpuCount ?? "unknown")}</span>
|
|
311
|
+
<span>Samples/job: ${escapeHtml(doc.samples ?? "unknown")}</span>
|
|
312
|
+
</div>
|
|
313
|
+
</header>
|
|
314
|
+
<main>
|
|
315
|
+
<section class="summary">
|
|
316
|
+
<div class="metric"><div class="label">Completed Jobs</div><div class="value">${completed}/${results.length}</div></div>
|
|
317
|
+
<div class="metric"><div class="label">Timeout</div><div class="value">${((doc.timeoutMs || 0) / 60000).toFixed(1)} min</div></div>
|
|
318
|
+
<div class="metric"><div class="label">Parallel Jobs</div><div class="value">${escapeHtml(doc.parallelJobs ?? "n/a")}</div></div>
|
|
319
|
+
<div class="metric"><div class="label">Result Rows</div><div class="value">${results.length}</div></div>
|
|
320
|
+
</section>
|
|
321
|
+
<section class="panel">
|
|
322
|
+
${buildMatrix(doc)}
|
|
323
|
+
</section>
|
|
324
|
+
<section class="details">
|
|
325
|
+
<h2>Raw Result Rows</h2>
|
|
326
|
+
<div class="panel">
|
|
327
|
+
<table>
|
|
328
|
+
<thead>
|
|
329
|
+
<tr><th>Case</th><th>Mode</th><th>Status</th><th>Avg</th><th>Min</th><th>Max</th><th>Max RSS</th><th>Digest</th></tr>
|
|
330
|
+
</thead>
|
|
331
|
+
<tbody>${buildDetails(results)}</tbody>
|
|
332
|
+
</table>
|
|
333
|
+
</div>
|
|
334
|
+
</section>
|
|
335
|
+
</main>
|
|
336
|
+
</body>
|
|
337
|
+
</html>
|
|
338
|
+
`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const data = JSON.parse(fs.readFileSync(inputPath, "utf8"));
|
|
342
|
+
fs.writeFileSync(outputPath, render(data));
|
|
343
|
+
console.log(`Rendered ${path.relative(root, outputPath)} from ${path.relative(root, inputPath)}`);
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const tests = [
|
|
|
23
23
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
24
24
|
["tests/test-repl.mjs", node, ["tests/test-repl.mjs"]],
|
|
25
25
|
["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
|
|
26
|
+
["tests/test-arithmetics.mjs", node, ["tests/test-arithmetics.mjs"]],
|
|
26
27
|
["tests/test-ieee-arithmetic.mjs", node, ["tests/test-ieee-arithmetic.mjs"]],
|
|
27
28
|
["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
|
|
28
29
|
];
|
package/tests/integration.sh
CHANGED
|
@@ -19,7 +19,9 @@ grep -q '"__main__"' "$TMP_DIR/stdin.kvm"
|
|
|
19
19
|
./objects/compile.mjs --lib "$TMP_DIR/export.klib" --export id:alias 'alias' "$TMP_DIR/export.ko"
|
|
20
20
|
./objects/decompile.mjs "$TMP_DIR/export.ko" | grep -q '^----- main -----$'
|
|
21
21
|
node ./codecs/unit.mjs --parse | ./k.mjs --lib "$TMP_DIR/export.klib" --export id:alias 'alias' | node ./codecs/unit.mjs --print | grep -qx '{}'
|
|
22
|
+
node ./codecs/unit.mjs --parse | ./kvm.mjs --lib "$TMP_DIR/export.klib" --export id:alias 'alias' | node ./codecs/unit.mjs --print | grep -qx '{}'
|
|
22
23
|
node ./codecs/unit.mjs --parse | ./k.mjs "$TMP_DIR/export.ko" | node ./codecs/unit.mjs --print | grep -qx '{}'
|
|
24
|
+
node ./codecs/unit.mjs --parse | ./kvm.mjs "$TMP_DIR/export.ko" | node ./codecs/unit.mjs --print | grep -qx '{}'
|
|
23
25
|
if ./k.mjs --lib "$TMP_DIR/export.klib" --lib "$TMP_DIR/export.klib" '()' > "$TMP_DIR/two-lib.out" 2> "$TMP_DIR/two-lib.err"; then
|
|
24
26
|
echo "expected repeated --lib to fail" >&2
|
|
25
27
|
exit 1
|
|
@@ -56,6 +58,11 @@ if ./k.mjs -k Examples/byte.k > "$TMP_DIR/dash-k.out" 2> "$TMP_DIR/dash-k.err";
|
|
|
56
58
|
exit 1
|
|
57
59
|
fi
|
|
58
60
|
grep -q -- '-k is no longer supported' "$TMP_DIR/dash-k.err"
|
|
61
|
+
if ./kvm.mjs -k Examples/byte.k > "$TMP_DIR/dash-k-kvm.out" 2> "$TMP_DIR/dash-k-kvm.err"; then
|
|
62
|
+
echo "expected k-vm -k to fail" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
grep -q -- '-k is no longer supported' "$TMP_DIR/dash-k-kvm.err"
|
|
59
66
|
printf 'A🙂\nBé~\t' | ./codecs/utf8.mjs --parse | ./k.mjs Examples/byte.k |./codecs/utf8.mjs --print
|
|
60
67
|
echo
|
|
61
68
|
./objects/compile.mjs Examples/ieee.k "$TMP_DIR/ieee.klib"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const node = process.execPath;
|
|
10
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-arithmetics-"));
|
|
11
|
+
const libPath = path.join(tmpDir, "arithmetics.klib");
|
|
12
|
+
const program = "{inc x, () y} o";
|
|
13
|
+
|
|
14
|
+
const smallInput = "11111112222223333334444444555555666666777777788888899999900000011111122222233333344444455555";
|
|
15
|
+
|
|
16
|
+
function run(command, args, options = {}) {
|
|
17
|
+
const result = spawnSync(command, args, {
|
|
18
|
+
cwd: root,
|
|
19
|
+
input: options.input,
|
|
20
|
+
encoding: options.encoding ?? null,
|
|
21
|
+
maxBuffer: 64 * 1024 * 1024
|
|
22
|
+
});
|
|
23
|
+
if (result.error && result.status == null) throw result.error;
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertOk(result, label) {
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
const stderr = Buffer.isBuffer(result.stderr) ? result.stderr.toString("utf8") : String(result.stderr ?? "");
|
|
30
|
+
const stdout = Buffer.isBuffer(result.stdout) ? result.stdout.toString("utf8") : String(result.stdout ?? "");
|
|
31
|
+
assert.fail(`${label} failed with status ${result.status}\nSTDERR:\n${stderr}\nSTDOUT:\n${stdout}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseIntWire(text) {
|
|
36
|
+
const result = run(node, ["codecs/int.mjs", "--parse"], {
|
|
37
|
+
input: `${text}\n`
|
|
38
|
+
});
|
|
39
|
+
assertOk(result, `parse int ${text}`);
|
|
40
|
+
return result.stdout;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printIntWire(wire, label) {
|
|
44
|
+
const result = run(node, ["codecs/int.mjs", "--print"], {
|
|
45
|
+
input: wire,
|
|
46
|
+
encoding: "utf8"
|
|
47
|
+
});
|
|
48
|
+
assertOk(result, `${label} print`);
|
|
49
|
+
return result.stdout.trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function runnerArgs(operation) {
|
|
53
|
+
return [
|
|
54
|
+
"--lib", libPath,
|
|
55
|
+
"--export", `${operation}:o`,
|
|
56
|
+
"--export", "inc",
|
|
57
|
+
program
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runNative(operation, inputText) {
|
|
62
|
+
const wire = parseIntWire(inputText);
|
|
63
|
+
const result = run(node, ["k.mjs", ...runnerArgs(operation)], {
|
|
64
|
+
input: wire
|
|
65
|
+
});
|
|
66
|
+
assertOk(result, `native ${operation}(${inputText})`);
|
|
67
|
+
return printIntWire(result.stdout, `native ${operation}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function expected(operation, inputText) {
|
|
71
|
+
const n = BigInt(inputText);
|
|
72
|
+
if (operation === "plus") return (n + n + 1n).toString();
|
|
73
|
+
if (operation === "minus") return "1";
|
|
74
|
+
if (operation === "times") return (n * (n + 1n)).toString();
|
|
75
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
assertOk(
|
|
80
|
+
run(node, ["objects/compile.mjs", "Examples/arithmetics.k", libPath], { encoding: "utf8" }),
|
|
81
|
+
"compile Examples/arithmetics.k"
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
for (const operation of ["plus", "minus", "times"]) {
|
|
85
|
+
assert.equal(
|
|
86
|
+
runNative(operation, smallInput),
|
|
87
|
+
expected(operation, smallInput),
|
|
88
|
+
`native ${operation} should match BigInt oracle`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log("OK");
|
|
93
|
+
} finally {
|
|
94
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
95
|
+
}
|