@fraczak/k 6.1.0 → 6.1.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/Code-derivation-tests/pattern-envelope.mjs +26 -0
- package/DOCS/REPL.md +33 -0
- package/DOCS/dependency-graph.html +140 -0
- package/DOCS/dependency-graph.md +144 -0
- 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/ieee.klib +0 -0
- package/package.json +2 -2
- package/repl-codecs.mjs +173 -0
- package/repl.mjs +241 -12
- package/run.mjs +16 -2
- package/test-ieee-arithmetic.mjs +93 -0
- package/test-repl.mjs +72 -2
- package/tests.sh +10 -1
package/codecs/utf8.mjs
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { stdin, stdout, argv, exit } from "node:process";
|
|
4
|
-
import {
|
|
4
|
+
import { isMainEntrypoint } from "./runtime/cli-entry.mjs";
|
|
5
|
+
import {
|
|
6
|
+
STRING_PATTERN_PROPERTY_LIST,
|
|
7
|
+
encodeText,
|
|
8
|
+
decodeText,
|
|
9
|
+
textToStringValue,
|
|
10
|
+
stringValueToText
|
|
11
|
+
} from "./string-codec.mjs";
|
|
12
|
+
|
|
13
|
+
const name = "utf8";
|
|
14
|
+
const patterns = [STRING_PATTERN_PROPERTY_LIST];
|
|
15
|
+
|
|
16
|
+
function parse(text) {
|
|
17
|
+
return textToStringValue(text);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function print(value) {
|
|
21
|
+
return stringValueToText(value);
|
|
22
|
+
}
|
|
5
23
|
|
|
6
24
|
function usage(stream = console.error) {
|
|
7
25
|
stream(`Usage: ${argv[1]} --parse | --print`);
|
|
@@ -41,7 +59,11 @@ async function main() {
|
|
|
41
59
|
}
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
if (isMainEntrypoint(import.meta.url, argv[1])) {
|
|
63
|
+
main().catch(err => {
|
|
64
|
+
console.error(err.message || String(err));
|
|
65
|
+
exit(1);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { name, patterns, parse, print };
|
package/ieee.klib
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.2",
|
|
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
9
|
"deps:graph": "node scripts/generate-dependency-graph.mjs"
|
|
10
10
|
},
|
|
11
11
|
"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,35 @@ 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");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function commitResult(state, result, lastMain) {
|
|
300
|
+
if (result !== undefined) {
|
|
301
|
+
state.value = result;
|
|
302
|
+
state.lastMain = lastMain;
|
|
303
|
+
}
|
|
304
|
+
return [printValue(result, state)];
|
|
266
305
|
}
|
|
267
306
|
|
|
268
307
|
function userDefinedNames(source) {
|
|
@@ -340,6 +379,13 @@ function canonicalNames(state) {
|
|
|
340
379
|
])].filter((name) => name.startsWith("@")).sort();
|
|
341
380
|
}
|
|
342
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
|
+
|
|
343
389
|
function aliasNames(state) {
|
|
344
390
|
return [...new Set([
|
|
345
391
|
...Object.keys(state.typeAliases),
|
|
@@ -347,6 +393,17 @@ function aliasNames(state) {
|
|
|
347
393
|
])].sort();
|
|
348
394
|
}
|
|
349
395
|
|
|
396
|
+
function displayAliases(state) {
|
|
397
|
+
const aliases = {};
|
|
398
|
+
for (const [name, hash] of [
|
|
399
|
+
...Object.entries(state.typeAliases),
|
|
400
|
+
...Object.entries(state.relAliases)
|
|
401
|
+
].sort(([a], [b]) => a.localeCompare(b))) {
|
|
402
|
+
if (!aliases[hash]) aliases[hash] = name;
|
|
403
|
+
}
|
|
404
|
+
return aliases;
|
|
405
|
+
}
|
|
406
|
+
|
|
350
407
|
function expandHome(input) {
|
|
351
408
|
if (input === "~") return os.homedir();
|
|
352
409
|
if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
|
|
@@ -378,6 +435,16 @@ function completeCanonical(line, state) {
|
|
|
378
435
|
return [matches, line];
|
|
379
436
|
}
|
|
380
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
|
+
|
|
381
448
|
function completeIdentifier(line, state) {
|
|
382
449
|
const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
|
|
383
450
|
if (!match) return [[], line];
|
|
@@ -391,6 +458,19 @@ function completeIdentifier(line, state) {
|
|
|
391
458
|
return [matches, line];
|
|
392
459
|
}
|
|
393
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
|
+
|
|
394
474
|
function completePath(line, tokenStart) {
|
|
395
475
|
const token = line.slice(tokenStart);
|
|
396
476
|
const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
|
|
@@ -449,6 +529,75 @@ function loadPathCompletionStart(argStart, arg) {
|
|
|
449
529
|
return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
|
|
450
530
|
}
|
|
451
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
|
+
|
|
452
601
|
function completeCommandArgument(line, state) {
|
|
453
602
|
const body = line.slice(1);
|
|
454
603
|
const firstSpace = body.search(/\s/);
|
|
@@ -458,6 +607,14 @@ function completeCommandArgument(line, state) {
|
|
|
458
607
|
const argStart = 1 + firstSpace + 1;
|
|
459
608
|
const arg = line.slice(argStart);
|
|
460
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
|
+
|
|
461
618
|
if (PATH_COMMANDS.has(command)) {
|
|
462
619
|
const tokenStart = command === "load"
|
|
463
620
|
? loadPathCompletionStart(argStart, arg)
|
|
@@ -659,14 +816,14 @@ async function runExpression(input, state) {
|
|
|
659
816
|
}
|
|
660
817
|
const mainRel = annotated.rels.__main__;
|
|
661
818
|
run.defs = annotated;
|
|
819
|
+
let result;
|
|
662
820
|
try {
|
|
663
|
-
|
|
821
|
+
result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
|
|
664
822
|
} catch (error) {
|
|
665
823
|
throw remapError(error, preambleLineCount(preamble));
|
|
666
824
|
}
|
|
667
|
-
state.lastMain = expression;
|
|
668
825
|
restoreCodes(state);
|
|
669
|
-
return
|
|
826
|
+
return commitResult(state, result, expression);
|
|
670
827
|
}
|
|
671
828
|
|
|
672
829
|
async function runSnippet(input, state, options = {}) {
|
|
@@ -685,17 +842,69 @@ async function runSnippet(input, state, options = {}) {
|
|
|
685
842
|
|
|
686
843
|
const mainRel = annotated.rels.__main__;
|
|
687
844
|
run.defs = annotated;
|
|
845
|
+
let result;
|
|
688
846
|
try {
|
|
689
|
-
|
|
847
|
+
result = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
|
|
690
848
|
} catch (error) {
|
|
691
849
|
throw remapError(error, lineOffset);
|
|
692
850
|
}
|
|
693
|
-
state.lastMain = snippet;
|
|
694
851
|
restoreCodes(state);
|
|
695
|
-
return
|
|
852
|
+
return commitResult(state, result, snippet);
|
|
853
|
+
}
|
|
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)];
|
|
696
901
|
}
|
|
697
902
|
|
|
698
903
|
async function evaluateInput(input, state) {
|
|
904
|
+
if (state.pendingInput) {
|
|
905
|
+
return consumeCodecInput(input, state);
|
|
906
|
+
}
|
|
907
|
+
|
|
699
908
|
const line = input.trim();
|
|
700
909
|
if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
|
|
701
910
|
|
|
@@ -745,9 +954,13 @@ async function evaluateCommand(line, state) {
|
|
|
745
954
|
return [listAliases(state.relAliases)];
|
|
746
955
|
case "val":
|
|
747
956
|
return [
|
|
748
|
-
printValue(state.value),
|
|
957
|
+
printValue(state.value, state),
|
|
749
958
|
JSON.stringify(state.value, null, 2)
|
|
750
959
|
];
|
|
960
|
+
case "codec":
|
|
961
|
+
return loadCodec(arg, state);
|
|
962
|
+
case "input":
|
|
963
|
+
return requestCodecInput(arg, state);
|
|
751
964
|
case "reset": {
|
|
752
965
|
const fresh = createState();
|
|
753
966
|
Object.assign(state, fresh);
|
|
@@ -803,13 +1016,13 @@ async function evaluateCommand(line, state) {
|
|
|
803
1016
|
if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
|
|
804
1017
|
const { hash, rel } = resolveRel(state, arg);
|
|
805
1018
|
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
806
|
-
return [`${arg} = ${prettyRelation(rel)}; -- ${hash}`];
|
|
1019
|
+
return [`${arg} = ${prettyRelation(rel, displayAliases(state), state.relAliases)}; -- ${hash}`];
|
|
807
1020
|
}
|
|
808
1021
|
case "C": {
|
|
809
1022
|
if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
|
|
810
1023
|
const hash = state.typeAliases[arg] || (arg.startsWith("@") ? arg : null);
|
|
811
1024
|
if (!hash || !(hash in state.codes)) throw new Error(`Unknown type '${arg}'`);
|
|
812
|
-
return [`$ ${arg} = ${prettyCode(
|
|
1025
|
+
return [`$ ${arg} = ${prettyCode(displayAliases(state), codes.find, codes.find(hash))}; -- ${hash}`];
|
|
813
1026
|
}
|
|
814
1027
|
default:
|
|
815
1028
|
throw new Error(`Unknown command ':${command}'. Try :help`);
|
|
@@ -826,6 +1039,9 @@ function helpText() {
|
|
|
826
1039
|
":type name show type definition",
|
|
827
1040
|
":codes list type aliases",
|
|
828
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",
|
|
829
1045
|
":load [--no-alias] file",
|
|
830
1046
|
" load .k source or .klib",
|
|
831
1047
|
":klib file export state as a library",
|
|
@@ -865,6 +1081,19 @@ function startRepl() {
|
|
|
865
1081
|
console.log("k interpreter (.klib-backed). Type :help for commands.");
|
|
866
1082
|
rl.prompt();
|
|
867
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
|
+
|
|
868
1097
|
if (lineHasExplicitContinuation(line)) {
|
|
869
1098
|
buffer.push(lineForContinuation(line));
|
|
870
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": {
|