@fraczak/k 6.1.1 → 6.1.3
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/.codex +0 -0
- package/Code-derivation-tests/pattern-envelope.mjs +26 -0
- package/DOCS/BIG_PICTURE.md +10 -10
- package/DOCS/DIVERGENCE_DETECTION.md +1 -1
- package/DOCS/REPL.md +33 -0
- package/DOCS/TYPE_DERIVATION.md +12 -12
- package/DOCS/a.html +398 -0
- package/DOCS/book/01-introduction.md +2 -2
- package/DOCS/book/02-syntax-and-values.md +8 -8
- package/DOCS/book/03-types-as-finite-automata.md +2 -2
- package/DOCS/book/04-partial-functions-and-composition.md +2 -2
- package/DOCS/book/05-typing-filters-and-normalization.md +3 -3
- package/DOCS/book/06-values-in-memory.md +3 -3
- package/DOCS/book/07-the-partial-function-abi.md +1 -1
- package/DOCS/book/08-operational-semantics-and-execution.md +1 -1
- package/DOCS/book/09-compiler-architecture.md +2 -2
- package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +1 -0
- package/DOCS/dependency-graph.html +140 -0
- package/DOCS/dependency-graph.md +144 -0
- package/NAT.k~ +452 -0
- package/README.md +7 -8
- package/UU/ta.k +1 -0
- package/UU/ta.k~ +0 -0
- package/UU/tb.k +0 -0
- package/UU/tc.k +0 -0
- package/a.k +138 -0
- package/a.klib +1 -0
- package/codecs/BINARY_FORMAT.html +471 -0
- package/codecs/BINARY_FORMAT.md +1 -1
- package/codecs/POLYMORPHIC_BINARY_FORMAT.html +355 -0
- package/codecs/POLYMORPHIC_BINARY_FORMAT.md +1 -1
- package/codecs/ieee.mjs +19 -4
- package/codecs/int.mjs +11 -4
- package/codecs/json.mjs +7 -4
- package/codecs/k-parse.mjs +7 -4
- package/codecs/k-print.mjs +7 -4
- package/codecs/runtime/cli-entry.mjs +13 -0
- package/codecs/show.mjs +7 -4
- package/codecs/unit.mjs +23 -8
- package/codecs/utf16.mjs +27 -5
- package/codecs/utf8.mjs +27 -5
- package/codes.mjs +14 -33
- package/core.json +1 -0
- package/core.klib +1 -0
- package/core.klib~ +0 -0
- package/export.mjs +3 -3
- package/ieee.klib +1 -0
- package/k-vscode/package-lock.json +4176 -0
- package/kkk.k +197 -0
- package/kkk.klib +0 -0
- package/kkk.klib~ +0 -0
- package/nak.klib +1 -0
- package/nak2.klib +1 -0
- package/nat.k +10 -0
- package/nat2.k +9 -0
- package/object.mjs +20 -7
- package/p.json +5475 -0
- package/package.json +3 -2
- package/repl-codecs.mjs +173 -0
- package/repl.mjs +215 -5
- package/run.mjs +16 -2
- package/scripts/benchmark-idempotency.mjs +72 -0
- package/test-ieee-arithmetic.mjs +93 -0
- package/test-k-object.mjs +19 -0
- package/test-repl.mjs +60 -1
- package/tests.sh +10 -1
- package/type_registry/registry.mjs~ +105 -0
- package/type_registry/typeParser.jison~ +132 -0
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/copilot.data.migration.agent.xml +0 -6
- package/.idea/copilot.data.migration.ask.xml +0 -6
- package/.idea/copilot.data.migration.ask2agent.xml +0 -6
- package/.idea/copilot.data.migration.edit.xml +0 -6
- package/.idea/k.iml +0 -9
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/k_compiler/runtime.c +0 -205
- package/k_compiler/runtime.h +0 -34
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.3",
|
|
4
4
|
"description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepare": "jiwson -m es6 parser.jison -o parser.mjs; jiwson -m es6 valueParser.jison -o valueParser.mjs; ",
|
|
8
|
-
"test": "node test.mjs; for f in Code-derivation-tests/*.mjs; do echo $f; node $f; done; node test-fingerprint.mjs; node test-hash-fuzz.mjs; node test-hash-normalization.mjs; node test-k-object.mjs; node test-repl.mjs; ./tests.sh",
|
|
8
|
+
"test": "node test.mjs; for f in Code-derivation-tests/*.mjs; do echo $f; node $f; done; node test-fingerprint.mjs; node test-hash-fuzz.mjs; node test-hash-normalization.mjs; node test-k-object.mjs; node test-repl.mjs; node test-ieee-arithmetic.mjs; ./tests.sh",
|
|
9
|
+
"perf:idempotency": "node scripts/benchmark-idempotency.mjs",
|
|
9
10
|
"deps:graph": "node scripts/generate-dependency-graph.mjs"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
package/repl-codecs.mjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { encodeToWire, decodeWire } from "./codecs/runtime/prefix-codec.mjs";
|
|
6
|
+
import { deriveClosedPattern } from "./codecs/runtime/codec.mjs";
|
|
7
|
+
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
8
|
+
import { finalize } from "./codes.mjs";
|
|
9
|
+
|
|
10
|
+
function isCanonicalCodeName(name) {
|
|
11
|
+
return typeof name === "string" && name.startsWith("@");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function singletonPatternToCodeHash(pattern) {
|
|
15
|
+
if (!Array.isArray(pattern) || pattern.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const codeDefs = {};
|
|
20
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
21
|
+
const [kind, edges] = pattern[i] || [];
|
|
22
|
+
if (kind !== "closed-product" && kind !== "closed-union") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(edges)) {
|
|
26
|
+
throw new Error(`Invalid singleton pattern node ${i}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const code = kind === "closed-product" ? "product" : "union";
|
|
30
|
+
codeDefs[`C${i}`] = {
|
|
31
|
+
code,
|
|
32
|
+
[code]: Object.fromEntries(edges.map(([label, target]) => {
|
|
33
|
+
if (!Number.isInteger(target) || target < 0 || target >= pattern.length) {
|
|
34
|
+
throw new Error(`Invalid singleton pattern edge target ${target}`);
|
|
35
|
+
}
|
|
36
|
+
return [label, `C${target}`];
|
|
37
|
+
}))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return finalize(codeDefs).representatives.C0 || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function codeHashToPattern(codeHash, findCode) {
|
|
45
|
+
const code = findCode(codeHash);
|
|
46
|
+
if (!code || code.code === "undefined") {
|
|
47
|
+
throw new Error(`Unknown type '${codeHash}'`);
|
|
48
|
+
}
|
|
49
|
+
const resolveType = (name) => {
|
|
50
|
+
const resolved = findCode(name);
|
|
51
|
+
if (!resolved || resolved.code === "undefined") {
|
|
52
|
+
throw new Error(`Unknown type '${name}'`);
|
|
53
|
+
}
|
|
54
|
+
return resolved;
|
|
55
|
+
};
|
|
56
|
+
return patternToPropertyList(deriveClosedPattern(codeHash, code, resolveType));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function valueForCode(value, codeHash, findCode) {
|
|
60
|
+
const pattern = codeHashToPattern(codeHash, findCode);
|
|
61
|
+
return decodeWire(encodeToWire(value, pattern)).value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeCodecModule(mod, fallbackName) {
|
|
65
|
+
const codec = mod.replCodec || mod.codec || mod.default || mod;
|
|
66
|
+
const name = codec.name || mod.name || fallbackName;
|
|
67
|
+
const patterns = codec.patterns || (codec.pattern ? [codec.pattern] : null);
|
|
68
|
+
const codes = codec.codes || codec.codeHashes || (codec.code ? [codec.code] : null) ||
|
|
69
|
+
patterns?.map((pattern, index) => {
|
|
70
|
+
const codeHash = singletonPatternToCodeHash(pattern);
|
|
71
|
+
if (!codeHash) throw new Error(`Codec '${name}' pattern ${index} is not singleton`);
|
|
72
|
+
return codeHash;
|
|
73
|
+
});
|
|
74
|
+
if (!name || typeof name !== "string") {
|
|
75
|
+
throw new Error("Codec module must export a string name");
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(codes) || codes.length === 0) {
|
|
78
|
+
throw new Error(`Codec '${name}' must export a non-empty codes array`);
|
|
79
|
+
}
|
|
80
|
+
if (codec.parse != null && typeof codec.parse !== "function") {
|
|
81
|
+
throw new Error(`Codec '${name}' parse export must be a function`);
|
|
82
|
+
}
|
|
83
|
+
if (codec.print != null && typeof codec.print !== "function") {
|
|
84
|
+
throw new Error(`Codec '${name}' print export must be a function`);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
codes,
|
|
89
|
+
parse: codec.parse,
|
|
90
|
+
print: codec.print
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function codecStore(state) {
|
|
95
|
+
if (!state.codecs) state.codecs = {};
|
|
96
|
+
return state.codecs;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function registerCodec(state, codec, source = null) {
|
|
100
|
+
const registered = [];
|
|
101
|
+
const store = codecStore(state);
|
|
102
|
+
for (const codeHash of codec.codes) {
|
|
103
|
+
if (!isCanonicalCodeName(codeHash)) {
|
|
104
|
+
throw new Error(`Codec '${codec.name}' has non-canonical code '${codeHash}'`);
|
|
105
|
+
}
|
|
106
|
+
if (!store[codeHash]) store[codeHash] = [];
|
|
107
|
+
const entry = {
|
|
108
|
+
name: codec.name,
|
|
109
|
+
source,
|
|
110
|
+
parse: codec.parse,
|
|
111
|
+
print: codec.print
|
|
112
|
+
};
|
|
113
|
+
const existingIndex = store[codeHash].findIndex((existing) =>
|
|
114
|
+
existing.name === entry.name && existing.source === entry.source
|
|
115
|
+
);
|
|
116
|
+
if (existingIndex === -1) {
|
|
117
|
+
store[codeHash].push(entry);
|
|
118
|
+
} else {
|
|
119
|
+
store[codeHash][existingIndex] = entry;
|
|
120
|
+
}
|
|
121
|
+
registered.push({ codeHash, ...entry });
|
|
122
|
+
}
|
|
123
|
+
return registered;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function loadCodecModule(state, filePath) {
|
|
127
|
+
const resolved = path.resolve(filePath);
|
|
128
|
+
const url = pathToFileURL(resolved);
|
|
129
|
+
url.searchParams.set("mtime", String(fs.statSync(resolved).mtimeMs));
|
|
130
|
+
const mod = await import(url.href);
|
|
131
|
+
const codec = normalizeCodecModule(mod, path.basename(resolved, path.extname(resolved)));
|
|
132
|
+
return registerCodec(state, codec, resolved);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function listCodecs(state) {
|
|
136
|
+
const entries = Object.entries(codecStore(state))
|
|
137
|
+
.flatMap(([codeHash, codecs]) => codecs.map((codec) => ({ codeHash, ...codec })))
|
|
138
|
+
.sort((a, b) => a.name.localeCompare(b.name) || a.codeHash.localeCompare(b.codeHash));
|
|
139
|
+
if (entries.length === 0) return "(none)";
|
|
140
|
+
return entries.map((entry) => `${entry.name} ${entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function codecNames(state, codeHash = null) {
|
|
144
|
+
const store = codecStore(state);
|
|
145
|
+
const codecs = codeHash ? (store[codeHash] || []) : Object.values(store).flat();
|
|
146
|
+
return [...new Set(codecs.map((codec) => codec.name))].sort();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resolveCodec(state, codeHash, codecName = null, capability = null) {
|
|
150
|
+
const matches = (codecStore(state)[codeHash] || []).filter((codec) =>
|
|
151
|
+
(codecName == null || codec.name === codecName) &&
|
|
152
|
+
(capability == null || typeof codec[capability] === "function")
|
|
153
|
+
);
|
|
154
|
+
if (matches.length === 0) {
|
|
155
|
+
const suffix = codecName ? ` named '${codecName}'` : "";
|
|
156
|
+
throw new Error(`No ${capability || "usable"} codec${suffix} for ${codeHash}`);
|
|
157
|
+
}
|
|
158
|
+
if (matches.length > 1 && codecName == null) {
|
|
159
|
+
throw new Error(`Multiple codecs for ${codeHash}; specify one of: ${matches.map((codec) => codec.name).join(", ")}`);
|
|
160
|
+
}
|
|
161
|
+
return matches[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export {
|
|
165
|
+
codecNames,
|
|
166
|
+
codeHashToPattern,
|
|
167
|
+
listCodecs,
|
|
168
|
+
loadCodecModule,
|
|
169
|
+
registerCodec,
|
|
170
|
+
resolveCodec,
|
|
171
|
+
singletonPatternToCodeHash,
|
|
172
|
+
valueForCode
|
|
173
|
+
};
|
package/repl.mjs
CHANGED
|
@@ -23,15 +23,25 @@ import {
|
|
|
23
23
|
prettyRelation
|
|
24
24
|
} from "./object.mjs";
|
|
25
25
|
import { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
|
|
26
|
+
import {
|
|
27
|
+
codecNames,
|
|
28
|
+
codeHashToPattern,
|
|
29
|
+
listCodecs,
|
|
30
|
+
loadCodecModule,
|
|
31
|
+
resolveCodec,
|
|
32
|
+
singletonPatternToCodeHash,
|
|
33
|
+
valueForCode
|
|
34
|
+
} from "./repl-codecs.mjs";
|
|
26
35
|
|
|
27
36
|
const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
28
37
|
const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
|
|
29
38
|
const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
|
|
30
39
|
const COMMAND_NAMES = [
|
|
31
40
|
"help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
|
|
32
|
-
"codes", "rels", "val", "reset", "klib", "ko", "load",
|
|
41
|
+
"codes", "rels", "val", "codec", "input", "reset", "klib", "ko", "load",
|
|
33
42
|
"quit", "exit"
|
|
34
43
|
];
|
|
44
|
+
const CODEC_COMMAND_NAMES = ["load", "list"];
|
|
35
45
|
const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
|
|
36
46
|
const initialCodes = codes.dump();
|
|
37
47
|
|
|
@@ -50,6 +60,8 @@ function createState() {
|
|
|
50
60
|
rels: {},
|
|
51
61
|
relAliases: {},
|
|
52
62
|
typeAliases: {},
|
|
63
|
+
codecs: {},
|
|
64
|
+
pendingInput: null,
|
|
53
65
|
meta: {},
|
|
54
66
|
value: emptyValue(),
|
|
55
67
|
lastMain: null
|
|
@@ -261,8 +273,27 @@ function listAliases(aliases) {
|
|
|
261
273
|
return entries.map(([name, hash]) => `${name} = ${hash}`).join("\n");
|
|
262
274
|
}
|
|
263
275
|
|
|
264
|
-
function
|
|
265
|
-
return
|
|
276
|
+
function formatCodecOutput(output) {
|
|
277
|
+
if (Buffer.isBuffer(output)) return output.toString("utf8");
|
|
278
|
+
return String(output);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function printValue(value, state = null) {
|
|
282
|
+
const lines = [valueWithEnvelopeToK(value)];
|
|
283
|
+
if (!state || value === undefined) return lines[0];
|
|
284
|
+
|
|
285
|
+
const codeHash = singletonPatternToCodeHash(value.pattern);
|
|
286
|
+
if (!codeHash) return lines[0];
|
|
287
|
+
|
|
288
|
+
for (const codec of state.codecs?.[codeHash] || []) {
|
|
289
|
+
if (typeof codec.print !== "function") continue;
|
|
290
|
+
try {
|
|
291
|
+
lines.push(`${codec.name}: ${formatCodecOutput(codec.print(value, { codeHash, state }))}`);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
lines.push(`${codec.name}: <error: ${error.message || String(error)}>`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return lines.join("\n");
|
|
266
297
|
}
|
|
267
298
|
|
|
268
299
|
function commitResult(state, result, lastMain) {
|
|
@@ -270,7 +301,7 @@ function commitResult(state, result, lastMain) {
|
|
|
270
301
|
state.value = result;
|
|
271
302
|
state.lastMain = lastMain;
|
|
272
303
|
}
|
|
273
|
-
return [printValue(result)];
|
|
304
|
+
return [printValue(result, state)];
|
|
274
305
|
}
|
|
275
306
|
|
|
276
307
|
function userDefinedNames(source) {
|
|
@@ -348,6 +379,13 @@ function canonicalNames(state) {
|
|
|
348
379
|
])].filter((name) => name.startsWith("@")).sort();
|
|
349
380
|
}
|
|
350
381
|
|
|
382
|
+
function canonicalCodeNames(state) {
|
|
383
|
+
return [...new Set([
|
|
384
|
+
...Object.keys(state.codes),
|
|
385
|
+
...Object.values(state.typeAliases)
|
|
386
|
+
])].filter((name) => name.startsWith("@")).sort();
|
|
387
|
+
}
|
|
388
|
+
|
|
351
389
|
function aliasNames(state) {
|
|
352
390
|
return [...new Set([
|
|
353
391
|
...Object.keys(state.typeAliases),
|
|
@@ -397,6 +435,16 @@ function completeCanonical(line, state) {
|
|
|
397
435
|
return [matches, line];
|
|
398
436
|
}
|
|
399
437
|
|
|
438
|
+
function completeCanonicalCode(line, state) {
|
|
439
|
+
const match = line.match(/^(.*?)(@[A-Za-z0-9_?!+-]*)$/);
|
|
440
|
+
if (!match) return [[], line];
|
|
441
|
+
const [, prefix, partial] = match;
|
|
442
|
+
const matches = canonicalCodeNames(state)
|
|
443
|
+
.filter((name) => name.startsWith(partial))
|
|
444
|
+
.map((name) => `${prefix}${name}`);
|
|
445
|
+
return [matches, line];
|
|
446
|
+
}
|
|
447
|
+
|
|
400
448
|
function completeIdentifier(line, state) {
|
|
401
449
|
const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
|
|
402
450
|
if (!match) return [[], line];
|
|
@@ -410,6 +458,19 @@ function completeIdentifier(line, state) {
|
|
|
410
458
|
return [matches, line];
|
|
411
459
|
}
|
|
412
460
|
|
|
461
|
+
function completeTypeIdentifier(line, state) {
|
|
462
|
+
const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
|
|
463
|
+
if (!match) return [[], line];
|
|
464
|
+
const [, prefix, partial] = match;
|
|
465
|
+
const isTypeToken = partial.startsWith("$");
|
|
466
|
+
const barePartial = isTypeToken ? partial.slice(1) : partial;
|
|
467
|
+
const matches = Object.keys(state.typeAliases)
|
|
468
|
+
.sort()
|
|
469
|
+
.filter((name) => name.startsWith(barePartial))
|
|
470
|
+
.map((name) => `${prefix}${isTypeToken ? "$" : ""}${name}`);
|
|
471
|
+
return [matches, line];
|
|
472
|
+
}
|
|
473
|
+
|
|
413
474
|
function completePath(line, tokenStart) {
|
|
414
475
|
const token = line.slice(tokenStart);
|
|
415
476
|
const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
|
|
@@ -468,6 +529,75 @@ function loadPathCompletionStart(argStart, arg) {
|
|
|
468
529
|
return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
|
|
469
530
|
}
|
|
470
531
|
|
|
532
|
+
function completeCommandWord(line, argStart, arg, words) {
|
|
533
|
+
const leadingWhitespace = arg.length - arg.trimStart().length;
|
|
534
|
+
const trimmed = arg.trimStart();
|
|
535
|
+
if (/\s/.test(trimmed)) return [[], line];
|
|
536
|
+
const prefix = line.slice(0, argStart + leadingWhitespace);
|
|
537
|
+
const matches = words
|
|
538
|
+
.filter((word) => word.startsWith(trimmed))
|
|
539
|
+
.map((word) => `${prefix}${word}`);
|
|
540
|
+
return [matches, line];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function codecLoadPathCompletionStart(argStart, arg) {
|
|
544
|
+
const match = arg.match(/^(\s*)load(?:\s+(.*))?$/);
|
|
545
|
+
if (!match) return null;
|
|
546
|
+
if (match[2] == null) return null;
|
|
547
|
+
const [, leading, rest] = match;
|
|
548
|
+
const restLeadingWhitespace = rest.length - rest.trimStart().length;
|
|
549
|
+
return argStart + leading.length + "load".length + 1 + restLeadingWhitespace + completionTokenStart(rest.trimStart());
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function completeCodecCommand(line, argStart, arg) {
|
|
553
|
+
const trimmed = arg.trimStart();
|
|
554
|
+
if (trimmed === "" || !/\s/.test(trimmed)) {
|
|
555
|
+
return completeCommandWord(line, argStart, arg, CODEC_COMMAND_NAMES);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const tokenStart = codecLoadPathCompletionStart(argStart, arg);
|
|
559
|
+
if (tokenStart == null) return [[], line];
|
|
560
|
+
return completePath(line, tokenStart);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function resolveTypeHash(state, rawName) {
|
|
564
|
+
const token = rawName.trim();
|
|
565
|
+
const bareName = token.startsWith("$") ? token.slice(1) : token;
|
|
566
|
+
const hash = state.typeAliases[bareName] || (token.startsWith("@") ? token : null);
|
|
567
|
+
if (!hash || !(hash in state.codes)) throw new Error(`Unknown type '${rawName}'`);
|
|
568
|
+
return hash;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function completeInputCommand(line, state, argStart, arg) {
|
|
572
|
+
const match = arg.match(/^(\s*)(\S+)?(\s+)?(\S*)?$/);
|
|
573
|
+
if (!match) return [[], line];
|
|
574
|
+
const [, leading, typeToken = "", afterTypeWhitespace = "", codecPartial = ""] = match;
|
|
575
|
+
|
|
576
|
+
if (!typeToken || !afterTypeWhitespace) {
|
|
577
|
+
if (/@[A-Za-z0-9_?!+-]*$/.test(line)) return completeCanonicalCode(line, state);
|
|
578
|
+
return completeTypeIdentifier(line, state);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (typeToken) {
|
|
582
|
+
const prefix = line.slice(0, argStart + leading.length + typeToken.length + afterTypeWhitespace.length);
|
|
583
|
+
const names = (() => {
|
|
584
|
+
try {
|
|
585
|
+
return codecNames(state, resolveTypeHash(state, typeToken));
|
|
586
|
+
} catch {
|
|
587
|
+
return codecNames(state);
|
|
588
|
+
}
|
|
589
|
+
})();
|
|
590
|
+
return [
|
|
591
|
+
names
|
|
592
|
+
.filter((name) => name.startsWith(codecPartial))
|
|
593
|
+
.map((name) => `${prefix}${name}`),
|
|
594
|
+
line
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return [[], line];
|
|
599
|
+
}
|
|
600
|
+
|
|
471
601
|
function completeCommandArgument(line, state) {
|
|
472
602
|
const body = line.slice(1);
|
|
473
603
|
const firstSpace = body.search(/\s/);
|
|
@@ -477,6 +607,14 @@ function completeCommandArgument(line, state) {
|
|
|
477
607
|
const argStart = 1 + firstSpace + 1;
|
|
478
608
|
const arg = line.slice(argStart);
|
|
479
609
|
|
|
610
|
+
if (command === "codec") {
|
|
611
|
+
return completeCodecCommand(line, argStart, arg);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (command === "input") {
|
|
615
|
+
return completeInputCommand(line, state, argStart, arg);
|
|
616
|
+
}
|
|
617
|
+
|
|
480
618
|
if (PATH_COMMANDS.has(command)) {
|
|
481
619
|
const tokenStart = command === "load"
|
|
482
620
|
? loadPathCompletionStart(argStart, arg)
|
|
@@ -714,7 +852,59 @@ async function runSnippet(input, state, options = {}) {
|
|
|
714
852
|
return commitResult(state, result, snippet);
|
|
715
853
|
}
|
|
716
854
|
|
|
855
|
+
async function loadCodec(input, state) {
|
|
856
|
+
const [subcommand, ...rest] = input.trim().split(/\s+/);
|
|
857
|
+
switch (subcommand) {
|
|
858
|
+
case "load": {
|
|
859
|
+
const filePath = rest.join(" ");
|
|
860
|
+
if (!filePath) throw new Error(":codec load requires a file path");
|
|
861
|
+
const registered = await loadCodecModule(state, expandHome(filePath));
|
|
862
|
+
return registered.map(({ name, codeHash }) => `loaded codec ${name} for ${codeHash}`);
|
|
863
|
+
}
|
|
864
|
+
case "list":
|
|
865
|
+
if (rest.length > 0) throw new Error(":codec list does not accept arguments");
|
|
866
|
+
return [listCodecs(state)];
|
|
867
|
+
default:
|
|
868
|
+
throw new Error(":codec requires 'load' or 'list'");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async function requestCodecInput(input, state) {
|
|
873
|
+
const parts = input.trim().split(/\s+/).filter(Boolean);
|
|
874
|
+
if (parts.length === 0 || parts.length > 2) {
|
|
875
|
+
throw new Error(":input requires a singleton type and optional codec name");
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const codeHash = resolveTypeHash(state, parts[0]);
|
|
879
|
+
const codecName = parts[1] || null;
|
|
880
|
+
resolveCodec(state, codeHash, codecName, "parse");
|
|
881
|
+
state.pendingInput = { codeHash, codecName };
|
|
882
|
+
return [`input ${codeHash}${codecName ? ` using ${codecName}` : ""}: enter value text`];
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
async function consumeCodecInput(input, state) {
|
|
886
|
+
const pending = state.pendingInput;
|
|
887
|
+
if (!pending) return null;
|
|
888
|
+
state.pendingInput = null;
|
|
889
|
+
|
|
890
|
+
restoreCodes(state);
|
|
891
|
+
const codec = resolveCodec(state, pending.codeHash, pending.codecName, "parse");
|
|
892
|
+
const pattern = codeHashToPattern(pending.codeHash, codes.find);
|
|
893
|
+
const parsed = await codec.parse(input, {
|
|
894
|
+
codeHash: pending.codeHash,
|
|
895
|
+
pattern,
|
|
896
|
+
state
|
|
897
|
+
});
|
|
898
|
+
const value = valueForCode(parsed, pending.codeHash, codes.find);
|
|
899
|
+
state.value = value;
|
|
900
|
+
return [printValue(value, state)];
|
|
901
|
+
}
|
|
902
|
+
|
|
717
903
|
async function evaluateInput(input, state) {
|
|
904
|
+
if (state.pendingInput) {
|
|
905
|
+
return consumeCodecInput(input, state);
|
|
906
|
+
}
|
|
907
|
+
|
|
718
908
|
const line = input.trim();
|
|
719
909
|
if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
|
|
720
910
|
|
|
@@ -764,9 +954,13 @@ async function evaluateCommand(line, state) {
|
|
|
764
954
|
return [listAliases(state.relAliases)];
|
|
765
955
|
case "val":
|
|
766
956
|
return [
|
|
767
|
-
printValue(state.value),
|
|
957
|
+
printValue(state.value, state),
|
|
768
958
|
JSON.stringify(state.value, null, 2)
|
|
769
959
|
];
|
|
960
|
+
case "codec":
|
|
961
|
+
return loadCodec(arg, state);
|
|
962
|
+
case "input":
|
|
963
|
+
return requestCodecInput(arg, state);
|
|
770
964
|
case "reset": {
|
|
771
965
|
const fresh = createState();
|
|
772
966
|
Object.assign(state, fresh);
|
|
@@ -845,6 +1039,9 @@ function helpText() {
|
|
|
845
1039
|
":type name show type definition",
|
|
846
1040
|
":codes list type aliases",
|
|
847
1041
|
":rels list relation aliases",
|
|
1042
|
+
":codec load file load a REPL codec module",
|
|
1043
|
+
":codec list list loaded codecs",
|
|
1044
|
+
":input type [codec] read next line as codec input",
|
|
848
1045
|
":load [--no-alias] file",
|
|
849
1046
|
" load .k source or .klib",
|
|
850
1047
|
":klib file export state as a library",
|
|
@@ -884,6 +1081,19 @@ function startRepl() {
|
|
|
884
1081
|
console.log("k interpreter (.klib-backed). Type :help for commands.");
|
|
885
1082
|
rl.prompt();
|
|
886
1083
|
async function handleLine(line) {
|
|
1084
|
+
if (state.pendingInput && buffer.length === 0) {
|
|
1085
|
+
rl.setPrompt("> ");
|
|
1086
|
+
try {
|
|
1087
|
+
for (const output of await consumeCodecInput(line, state)) {
|
|
1088
|
+
console.log(output);
|
|
1089
|
+
}
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
console.error(error.message || String(error));
|
|
1092
|
+
}
|
|
1093
|
+
if (!closed) rl.prompt();
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
887
1097
|
if (lineHasExplicitContinuation(line)) {
|
|
888
1098
|
buffer.push(lineForContinuation(line));
|
|
889
1099
|
rl.setPrompt(" ");
|
package/run.mjs
CHANGED
|
@@ -122,6 +122,20 @@ function constrainWithPattern(value, constraint, exp) {
|
|
|
122
122
|
return withPattern(value, pattern);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function projectionPattern(staticOutputPattern, valuePattern, label, exp) {
|
|
126
|
+
const dynamicOutputPattern = edgeSubpattern(valuePattern, label);
|
|
127
|
+
const pattern = intersectPatterns(staticOutputPattern, dynamicOutputPattern);
|
|
128
|
+
if (!pattern && staticOutputPattern && dynamicOutputPattern) {
|
|
129
|
+
throw new TypeError(
|
|
130
|
+
`Type Error in '${exp.op}'${expLocation(exp)}\n` +
|
|
131
|
+
` - Projected value envelope does not intersect expression output pattern.\n` +
|
|
132
|
+
` - output pattern: ${patternPreview(staticOutputPattern)}\n` +
|
|
133
|
+
` - projected envelope: ${patternPreview(dynamicOutputPattern)}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return pattern;
|
|
137
|
+
}
|
|
138
|
+
|
|
125
139
|
function compiledExp(findCode, exp, typePatternGraph) {
|
|
126
140
|
const cached = exp._compiledRun;
|
|
127
141
|
if (cached?.findCode === findCode && cached?.typePatternGraph === typePatternGraph && cached?.defs === run.defs) {
|
|
@@ -188,13 +202,13 @@ function compiledExp(findCode, exp, typePatternGraph) {
|
|
|
188
202
|
case "dot":
|
|
189
203
|
fn = (value) => {
|
|
190
204
|
if (value === undefined) return undefined;
|
|
191
|
-
return withPattern(value.product[exp.dot], staticOutputPattern
|
|
205
|
+
return withPattern(value.product[exp.dot], projectionPattern(staticOutputPattern, value.pattern, exp.dot, exp));
|
|
192
206
|
};
|
|
193
207
|
break;
|
|
194
208
|
case "div":
|
|
195
209
|
fn = (value) => {
|
|
196
210
|
if (value === undefined || value.tag !== exp.div) return undefined;
|
|
197
|
-
return withPattern(value.value, staticOutputPattern
|
|
211
|
+
return withPattern(value.value, projectionPattern(staticOutputPattern, value.pattern, exp.div, exp));
|
|
198
212
|
};
|
|
199
213
|
break;
|
|
200
214
|
case "comp": {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, statSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { performance } from "node:perf_hooks";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_EXAMPLES = [
|
|
10
|
+
"Examples/nat.k",
|
|
11
|
+
"Examples/byte.k",
|
|
12
|
+
"Examples/bnat.k",
|
|
13
|
+
"Examples/arithmetics.k",
|
|
14
|
+
"Examples/ieee.k"
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const iterations = Number(process.env.K_BENCH_ITERATIONS || 5);
|
|
18
|
+
const examples = process.argv.slice(2);
|
|
19
|
+
const benchmarkExamples = examples.length > 0 ? examples : DEFAULT_EXAMPLES;
|
|
20
|
+
|
|
21
|
+
function run(args) {
|
|
22
|
+
const result = spawnSync("node", args, {
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
26
|
+
});
|
|
27
|
+
if (result.status !== 0) {
|
|
28
|
+
throw new Error(`${args.join(" ")} failed\n${result.stderr || result.stdout}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function median(values) {
|
|
33
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
34
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatMs(value) {
|
|
38
|
+
return `${Math.round(value)} ms`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatBytes(value) {
|
|
42
|
+
if (value >= 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
43
|
+
if (value >= 1024) return `${Math.round(value / 1024)} KiB`;
|
|
44
|
+
return `${value} B`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`compile-lib/decompile round-trip benchmark (${iterations} iterations)`);
|
|
48
|
+
console.log("example, median, min, max, object");
|
|
49
|
+
|
|
50
|
+
for (const example of benchmarkExamples) {
|
|
51
|
+
const times = [];
|
|
52
|
+
let size = 0;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < iterations; i++) {
|
|
55
|
+
const out = mkdtempSync(join(tmpdir(), `k-idempotency-bench-${example.replaceAll("/", "_")}-`));
|
|
56
|
+
const t0 = performance.now();
|
|
57
|
+
run(["objects/compile-lib.mjs", example, join(out, "1.klib")]);
|
|
58
|
+
run(["objects/decompile.mjs", join(out, "1.klib"), join(out, "1.k")]);
|
|
59
|
+
run(["objects/compile-lib.mjs", join(out, "1.k"), join(out, "2.klib")]);
|
|
60
|
+
run(["objects/decompile.mjs", join(out, "2.klib"), join(out, "2.k")]);
|
|
61
|
+
times.push(performance.now() - t0);
|
|
62
|
+
size = statSync(join(out, "1.klib")).size;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log([
|
|
66
|
+
example,
|
|
67
|
+
formatMs(median(times)),
|
|
68
|
+
formatMs(Math.min(...times)),
|
|
69
|
+
formatMs(Math.max(...times)),
|
|
70
|
+
formatBytes(size)
|
|
71
|
+
].join(", "));
|
|
72
|
+
}
|