@fraczak/k 6.2.4 → 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/Value.mjs +50 -40
- package/a.klib +1 -1
- package/a.kvm +142676 -0
- package/a.wasm +0 -0
- package/backend-api.mjs +1 -1
- package/benchmark-results.html +532 -0
- package/benchmark-results.json +480 -0
- package/codecs/example-pipeline.mjs +7 -7
- package/codecs/ieee.mjs +8 -8
- package/codecs/int.mjs +6 -6
- package/codecs/json-codec.mjs +15 -15
- package/codecs/runtime/codec.mjs +13 -13
- package/codecs/runtime/prefix-codec.mjs +18 -18
- package/codecs/runtime/show-value.mjs +3 -3
- package/codecs/runtime/test-codec.mjs +7 -7
- package/codecs/runtime/typeFromValue.mjs +4 -4
- package/codecs/runtime/unicode-string.mjs +30 -30
- package/codecs/unit.mjs +4 -4
- package/index.mjs +2 -2
- package/kvm.mjs +86 -19
- package/package.json +4 -1
- package/repl.mjs +2 -2
- package/run.mjs +9 -9
- 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 +2 -0
- package/tests/code-derivation/codec-witness-pattern.mjs +4 -4
- package/tests/code-derivation/pattern-envelope.mjs +12 -12
- package/tests/integration.sh +7 -0
- package/tests/test-arithmetics.mjs +95 -0
- package/tests/test-deep-wire-value.mjs +4 -4
- package/tests/test-ieee-arithmetic.mjs +3 -3
- package/tests/test-k-object.mjs +10 -10
- package/tests/test-kvm.mjs +1 -1
- package/tests/test-repl.mjs +4 -4
- package/tests/test-structural-values.mjs +27 -0
- package/valueIO.mjs +1 -1
- package/valueParser.jison +9 -9
- package/valueParser.mjs +7 -7
|
@@ -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
|
@@ -18,10 +18,12 @@ const tests = [
|
|
|
18
18
|
["tests/test-fingerprint.mjs", node, ["tests/test-fingerprint.mjs"]],
|
|
19
19
|
["tests/test-hash-fuzz.mjs", node, ["tests/test-hash-fuzz.mjs"]],
|
|
20
20
|
["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
|
|
21
|
+
["tests/test-structural-values.mjs", node, ["tests/test-structural-values.mjs"]],
|
|
21
22
|
["tests/test-deep-wire-value.mjs", node, ["tests/test-deep-wire-value.mjs"]],
|
|
22
23
|
["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
|
|
23
24
|
["tests/test-repl.mjs", node, ["tests/test-repl.mjs"]],
|
|
24
25
|
["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
|
|
26
|
+
["tests/test-arithmetics.mjs", node, ["tests/test-arithmetics.mjs"]],
|
|
25
27
|
["tests/test-ieee-arithmetic.mjs", node, ["tests/test-ieee-arithmetic.mjs"]],
|
|
26
28
|
["tests/integration.sh", path.join(root, "tests/integration.sh"), []]
|
|
27
29
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
2
|
import { parseValue } from '../../valueIO.mjs';
|
|
3
3
|
import { encodeToWire, decodeWire } from '../../codecs/runtime/prefix-codec.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { isProduct, isVariant } from '../../Value.mjs';
|
|
5
5
|
|
|
6
6
|
const value = parseValue('{a:{b:x,c:{}}}');
|
|
7
7
|
const repeatedClosedValue = parseValue('{a:{m:{},n:{}},b:{m:{},n:{}}}');
|
|
@@ -27,9 +27,9 @@ assert.deepEqual(explicitProductWire.pattern, [
|
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
29
|
const decoded = explicitProductWire.value;
|
|
30
|
-
assert(decoded
|
|
31
|
-
assert(decoded.product.a
|
|
32
|
-
assert(decoded.product.a.product.b
|
|
30
|
+
assert(isProduct(decoded));
|
|
31
|
+
assert(isProduct(decoded.product.a));
|
|
32
|
+
assert(isVariant(decoded.product.a.product.b));
|
|
33
33
|
assert.equal(decoded.product.a.product.b.tag, "x");
|
|
34
34
|
|
|
35
35
|
const repeatedClosedWire = decodeWire(encodeToWire(repeatedClosedValue, null));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import k from "../../index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { Value, mergePatterns, withPattern } from "../../Value.mjs";
|
|
4
4
|
import { decodeWire, encodeToWire } from "../../codecs/runtime/prefix-codec.mjs";
|
|
5
5
|
import run from "../../run.mjs";
|
|
6
6
|
import codes from "../../codes.mjs";
|
|
@@ -17,16 +17,16 @@ const script = `
|
|
|
17
17
|
{}|_|0|1|+ $int
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
|
-
const result = k.compile(script)(
|
|
20
|
+
const result = k.compile(script)(Value.product({}));
|
|
21
21
|
assert.deepEqual(result.pattern, INT_PATTERN);
|
|
22
22
|
|
|
23
23
|
const decoded = decodeWire(encodeToWire(result, result.pattern));
|
|
24
24
|
assert.deepEqual(decoded.pattern, INT_PATTERN);
|
|
25
25
|
|
|
26
|
-
const two =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const two = Value.variant("+",
|
|
27
|
+
Value.variant("1",
|
|
28
|
+
Value.variant("0",
|
|
29
|
+
Value.variant("_", Value.product({}))
|
|
30
30
|
)
|
|
31
31
|
)
|
|
32
32
|
);
|
|
@@ -53,13 +53,13 @@ const wrappedFloatPattern = [
|
|
|
53
53
|
["closed-product", []],
|
|
54
54
|
["closed-union", [["inexact", 5]]]
|
|
55
55
|
];
|
|
56
|
-
const wrappedFloat =
|
|
57
|
-
result:
|
|
58
|
-
sign:
|
|
59
|
-
exponent:
|
|
60
|
-
fraction:
|
|
56
|
+
const wrappedFloat = Value.product({
|
|
57
|
+
result: Value.product({
|
|
58
|
+
sign: Value.variant("+", Value.product({})),
|
|
59
|
+
exponent: Value.product({ 0: Value.variant("1", Value.product({})) }),
|
|
60
|
+
fraction: Value.product({ 0: Value.variant("0", Value.product({})) })
|
|
61
61
|
}),
|
|
62
|
-
flags:
|
|
62
|
+
flags: Value.variant("inexact", Value.product({}))
|
|
63
63
|
});
|
|
64
64
|
const resultProjection = k.compile(".result")(withPattern(wrappedFloat, wrappedFloatPattern));
|
|
65
65
|
assert.deepEqual(resultProjection.pattern, [
|
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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { Value } from "../Value.mjs";
|
|
3
3
|
import { decodeWire, encodeToWire } from "../codecs/runtime/prefix-codec.mjs";
|
|
4
4
|
|
|
5
5
|
const depth = 12000;
|
|
@@ -9,12 +9,12 @@ const pattern = [
|
|
|
9
9
|
["closed-product", []]
|
|
10
10
|
];
|
|
11
11
|
|
|
12
|
-
let bits =
|
|
12
|
+
let bits = Value.variant("_", Value.product({}));
|
|
13
13
|
for (let i = 0; i < depth; i++) {
|
|
14
|
-
bits =
|
|
14
|
+
bits = Value.variant("1", bits);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const wire = encodeToWire(
|
|
17
|
+
const wire = encodeToWire(Value.variant("+", bits), pattern);
|
|
18
18
|
const decoded = decodeWire(wire).value;
|
|
19
19
|
assert.equal(decoded.tag, "+");
|
|
20
20
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
|
|
3
3
|
import { createState, evaluateInput } from "../repl.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { Value, isProduct } from "../Value.mjs";
|
|
5
5
|
import codes from "../codes.mjs";
|
|
6
6
|
import { parse as parseFloat64, print as printFloat64 } from "../codecs/ieee.mjs";
|
|
7
7
|
import { singletonPatternToCodeHash, valueForCode } from "../repl-codecs.mjs";
|
|
@@ -21,7 +21,7 @@ function float64(text) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function floatPair(x, y) {
|
|
24
|
-
return valueForCode(
|
|
24
|
+
return valueForCode(Value.product({
|
|
25
25
|
x: float64(x),
|
|
26
26
|
y: float64(y)
|
|
27
27
|
}), floatPairHash, codes.find);
|
|
@@ -46,7 +46,7 @@ function assertFloatResult(value, expected, label) {
|
|
|
46
46
|
|
|
47
47
|
async function assertOperation({ op, x, y, result, flags = null, projection = false }) {
|
|
48
48
|
const bundle = await runOperation(op, x, y);
|
|
49
|
-
assert.ok(bundle
|
|
49
|
+
assert.ok(isProduct(bundle), `${op} should return a result bundle`);
|
|
50
50
|
assert.deepEqual(Object.keys(bundle.product).sort(), ["flags", "result"]);
|
|
51
51
|
assertFloatResult(bundle.product.result, result, `${op}(${x}, ${y}).result`);
|
|
52
52
|
if (flags != null) {
|
package/tests/test-k-object.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import k from "../index.mjs";
|
|
4
4
|
import { parseValue } from "../valueIO.mjs";
|
|
5
5
|
import { decodeWire, encodeToWire, CORE_PATTERN_PROPERTY_LIST } from "../codecs/runtime/prefix-codec.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { Value } from "../Value.mjs";
|
|
7
7
|
import { exportPatternGraph } from "../codecs/runtime/codec.mjs";
|
|
8
8
|
import { patternToPropertyList } from "../codecs/runtime/pattern-json.mjs";
|
|
9
9
|
import {
|
|
@@ -27,9 +27,9 @@ assert.deepEqual(decodedWire.pattern, [
|
|
|
27
27
|
]);
|
|
28
28
|
assert.deepEqual(decodedWire.value.toJSON(), value.toJSON());
|
|
29
29
|
|
|
30
|
-
const unicodeLabelValue =
|
|
31
|
-
"é":
|
|
32
|
-
"𐐷":
|
|
30
|
+
const unicodeLabelValue = Value.product({
|
|
31
|
+
"é": Value.variant("🙂", Value.product({})),
|
|
32
|
+
"𐐷": Value.product({})
|
|
33
33
|
});
|
|
34
34
|
const unicodeWire = encodeToWire(unicodeLabelValue, null);
|
|
35
35
|
const decodedUnicode = decodeWire(unicodeWire);
|
|
@@ -73,15 +73,15 @@ assert(object.meta[object.relAlias.__main__]?.origins?.some((origin) =>
|
|
|
73
73
|
));
|
|
74
74
|
|
|
75
75
|
const objectFn = objectToFunction(object);
|
|
76
|
-
const objectResult = objectFn(
|
|
76
|
+
const objectResult = objectFn(Value.product({}));
|
|
77
77
|
assert.deepEqual(objectResult.pattern, INT_PATTERN);
|
|
78
78
|
assert.deepEqual(objectResult.toJSON(), { "+": { "1": { "0": "_" } } });
|
|
79
|
-
const convergedObjectResult = runConvergedObject(object,
|
|
79
|
+
const convergedObjectResult = runConvergedObject(object, Value.product({}));
|
|
80
80
|
assert.deepEqual(convergedObjectResult.pattern, INT_PATTERN);
|
|
81
81
|
assert.deepEqual(convergedObjectResult.toJSON(), objectResult.toJSON());
|
|
82
82
|
|
|
83
83
|
const decompiledSource = decompileObjectBuffer(objectBuffer);
|
|
84
|
-
const decompiledResult = k.compile(decompiledSource)(
|
|
84
|
+
const decompiledResult = k.compile(decompiledSource)(Value.product({}));
|
|
85
85
|
assert.deepEqual(decompiledResult.pattern, INT_PATTERN);
|
|
86
86
|
assert.deepEqual(decompiledResult.toJSON(), objectResult.toJSON());
|
|
87
87
|
assert.match(decompiledSource, /^----- codes -----$/m);
|
|
@@ -112,11 +112,11 @@ assert.equal(arithmeticsRoundTripSource, arithmeticsDecompiledSource);
|
|
|
112
112
|
|
|
113
113
|
assert.throws(() => k.compile("@A = (); @A"), /Parse error/);
|
|
114
114
|
assert.throws(() => k.compile("$ @A = {}; $@A"), /Parse error/);
|
|
115
|
-
assert.deepEqual(k.compile("7jfi = ?X0; 7jfi")(
|
|
115
|
+
assert.deepEqual(k.compile("7jfi = ?X0; 7jfi")(Value.product({})).toJSON(), {});
|
|
116
116
|
|
|
117
117
|
const sccSource = decompileObjectBuffer(compileObjectBuffer("c = {}; b = c |x; a = b |y; a"));
|
|
118
118
|
assert.match(sccSource, /----- rels -----\nPQgV = .+\n\naQAD = .+\n\nN9UH = /s);
|
|
119
|
-
assert.deepEqual(k.compile(sccSource)(
|
|
119
|
+
assert.deepEqual(k.compile(sccSource)(Value.product({})).toJSON(), { y: "x" });
|
|
120
120
|
|
|
121
121
|
const libraryBuffer = compileLibraryBuffer("$ nat = <{} zero, nat succ>;\nsucc = |succ;\n", { source: "defs-only.k" });
|
|
122
122
|
assert.equal(libraryBuffer.subarray(0, 1).toString("utf8"), "{");
|
|
@@ -173,7 +173,7 @@ assert(derivedAliases.indexOf("other = @") < derivedAliases.indexOf("succ = @"))
|
|
|
173
173
|
}));
|
|
174
174
|
assert.equal(nonConverged.rels.__main__.typeDerivation.status, "not-converged");
|
|
175
175
|
assert.throws(
|
|
176
|
-
() => runConvergedObject(nonConverged,
|
|
176
|
+
() => runConvergedObject(nonConverged, Value.product({ x: Value.product({}) })),
|
|
177
177
|
/type derivation is not-converged/
|
|
178
178
|
);
|
|
179
179
|
} finally {
|