@fraczak/k 5.1.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/Code-derivation-tests/blowup.mjs +3 -3
  2. package/Code-derivation-tests/bnat.mjs +2 -2
  3. package/Code-derivation-tests/codec-witness-pattern.mjs +50 -0
  4. package/Code-derivation-tests/convergence-strategy.mjs +24 -0
  5. package/Code-derivation-tests/dot.mjs +12 -9
  6. package/Code-derivation-tests/filter-syntax.mjs +21 -0
  7. package/Code-derivation-tests/loop.mjs +8 -2
  8. package/Code-derivation-tests/nat.mjs +1 -1
  9. package/Code-derivation-tests/not-not.mjs +3 -3
  10. package/Code-derivation-tests/pattern-envelope.mjs +68 -0
  11. package/Code-derivation-tests/rlz.mjs +2 -2
  12. package/{BIG_PICTURE.md → DOCS/BIG_PICTURE.md} +103 -50
  13. package/DOCS/REPL.md +191 -0
  14. package/DOCS/book/01-introduction.md +51 -0
  15. package/DOCS/book/02-syntax-and-values.md +111 -0
  16. package/DOCS/book/03-types-as-finite-automata.md +156 -0
  17. package/DOCS/book/04-partial-functions-and-composition.md +146 -0
  18. package/DOCS/book/05-typing-filters-and-normalization.md +111 -0
  19. package/DOCS/book.md +54 -0
  20. package/README.md +204 -508
  21. package/TypeGraph.mjs +43 -0
  22. package/TypePatternGraph.mjs +251 -0
  23. package/Value.mjs +192 -0
  24. package/augmentation.mjs +198 -0
  25. package/codecs/BINARY_FORMAT.md +53 -0
  26. package/codecs/POLYMORPHIC_BINARY_FORMAT.md +303 -0
  27. package/codecs/README.md +215 -0
  28. package/codecs/example-pipeline.mjs +149 -0
  29. package/codecs/ieee.mjs +140 -0
  30. package/codecs/int.mjs +104 -0
  31. package/codecs/json-codec.mjs +215 -0
  32. package/codecs/json.mjs +40 -0
  33. package/codecs/k-parse.mjs +90 -0
  34. package/codecs/k-pattern.mjs +75 -0
  35. package/codecs/k-print.mjs +46 -0
  36. package/codecs/runtime/codec.mjs +768 -0
  37. package/codecs/runtime/ieee-pattern.mjs +30 -0
  38. package/codecs/runtime/pattern-json.mjs +96 -0
  39. package/codecs/runtime/prefix-codec.mjs +425 -0
  40. package/codecs/runtime/show-value.mjs +68 -0
  41. package/codecs/runtime/typeFromValue.mjs +34 -0
  42. package/codecs/runtime/unicode-string.mjs +220 -0
  43. package/codecs/show.mjs +41 -0
  44. package/codecs/string-codec.mjs +80 -0
  45. package/codecs/unit.mjs +51 -0
  46. package/codecs/utf16.mjs +66 -0
  47. package/codecs/utf8.mjs +37 -0
  48. package/codes.mjs +146 -44
  49. package/compiler.mjs +111 -0
  50. package/convergence.mjs +197 -0
  51. package/core.k +197 -0
  52. package/export.mjs +22 -32
  53. package/hash.mjs +29 -31
  54. package/index.mjs +41 -15
  55. package/k-vscode/.vscode/launch.json +14 -0
  56. package/k-vscode/README.md +65 -0
  57. package/k-vscode/language-configuration.json +42 -0
  58. package/k-vscode/package.json +57 -0
  59. package/k-vscode/snippets/k.json +92 -0
  60. package/k-vscode/syntaxes/k.tmLanguage.json +243 -0
  61. package/k.mjs +56 -54
  62. package/object.mjs +517 -0
  63. package/objects/README.md +38 -0
  64. package/objects/compile-lib.mjs +42 -0
  65. package/objects/compile.mjs +42 -0
  66. package/objects/decompile.mjs +32 -0
  67. package/package.json +18 -8
  68. package/parser.jison +27 -46
  69. package/parser.mjs +68 -100
  70. package/pretty.mjs +21 -40
  71. package/repl2.mjs +652 -0
  72. package/run.mjs +264 -231
  73. package/symbol-table.mjs +3 -17
  74. package/test-fingerprint.mjs +7 -7
  75. package/test-hash-fuzz.mjs +107 -0
  76. package/test-hash-normalization.mjs +134 -0
  77. package/test-k-object.mjs +74 -0
  78. package/test-repl2.mjs +95 -0
  79. package/test.mjs +45 -70
  80. package/tests.sh +10 -1
  81. package/typing.mjs +129 -676
  82. package/unification.mjs +157 -0
  83. package/valueIO.mjs +54 -0
  84. package/valueParser.jison +143 -0
  85. package/valueParser.mjs +713 -0
  86. package/Code-derivation-tests/built-in.mjs +0 -19
  87. package/Code-derivation-tests/vector.mjs +0 -19
  88. package/Filters/bug.k +0 -3
  89. package/Filters/composing.k +0 -15
  90. package/Filters/filter.k +0 -9
  91. package/TODO.md +0 -53
  92. package/TYPING.md +0 -365
  93. package/k-repl.cast +0 -1913
  94. package/k.cast +0 -618
  95. package/my-objects.json +0 -6
  96. package/patterns.mjs +0 -543
  97. package/repl.mjs +0 -213
  98. package/test-filters.mjs +0 -45
  99. package/typing-test.mjs +0 -40
package/repl2.mjs ADDED
@@ -0,0 +1,652 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import readline from "node:readline";
7
+ import { fileURLToPath } from "node:url";
8
+ import { argv, exit, stdin, stdout } from "node:process";
9
+
10
+ import { annotate, parse } from "./index.mjs";
11
+ import run from "./run.mjs";
12
+ import codes from "./codes.mjs";
13
+ import { Product } from "./Value.mjs";
14
+ import { patterns2filters, prettyCode, prettyRel } from "./pretty.mjs";
15
+ import {
16
+ compileObject,
17
+ compileLibrary,
18
+ decodeLibrary,
19
+ encodeObject,
20
+ encodeLibrary,
21
+ hydrateObject,
22
+ loadLibrary
23
+ } from "./object.mjs";
24
+ import { propertyListToFilter, valueToK, valueWithEnvelopeToK } from "./codecs/runtime/show-value.mjs";
25
+
26
+ const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
27
+ const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
28
+ const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
29
+ const COMMAND_NAMES = [
30
+ "help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
31
+ "codes", "rels", "val", "reset", "save", "klib", "ko", "export", "load",
32
+ "quit", "exit"
33
+ ];
34
+ const PATH_COMMANDS = new Set(["save", "klib", "ko", "export", "load"]);
35
+ const initialCodes = codes.dump();
36
+
37
+ function cloneJSON(value) {
38
+ return JSON.parse(JSON.stringify(value));
39
+ }
40
+
41
+ function emptyValue() {
42
+ return new Product({}, [["closed-product", []]]);
43
+ }
44
+
45
+ function createState() {
46
+ codes.load(cloneJSON(initialCodes));
47
+ return {
48
+ codes: codes.dump(),
49
+ rels: {},
50
+ relAliases: {},
51
+ typeAliases: {},
52
+ meta: {},
53
+ value: emptyValue(),
54
+ lastMain: null
55
+ };
56
+ }
57
+
58
+ function stateLibrary(state) {
59
+ return {
60
+ format: "k-object",
61
+ version: 2,
62
+ codes: state.codes,
63
+ rels: state.rels,
64
+ relAlias: state.relAliases,
65
+ meta: state.meta,
66
+ main: null
67
+ };
68
+ }
69
+
70
+ function restoreCodes(state) {
71
+ codes.load(state.codes);
72
+ }
73
+
74
+ function aliasLine(kind, name, hash) {
75
+ if (!NAME_RE.test(name)) return null;
76
+ const body = hash.startsWith("@") ? hash.slice(1) : hash;
77
+ return kind === "code" ? `$ ${name} = @${body};` : `${name} = @${body};`;
78
+ }
79
+
80
+ function aliasPreamble(state, omitName = null) {
81
+ const lines = [];
82
+ for (const [name, hash] of Object.entries(state.typeAliases).sort()) {
83
+ if (name !== omitName) lines.push(aliasLine("code", name, hash));
84
+ }
85
+ for (const [name, hash] of Object.entries(state.relAliases).sort()) {
86
+ if (name !== omitName) lines.push(aliasLine("rel", name, hash));
87
+ }
88
+ return lines.filter(Boolean).join("\n");
89
+ }
90
+
91
+ function compileWithOptionalIdentity(source, options) {
92
+ try {
93
+ return hydrateObject(compileLibrary(source, options));
94
+ } catch (error) {
95
+ if (!/got 'EOF'|Expecting/.test(error.message)) throw error;
96
+ return hydrateObject(compileLibrary(`${source}\n()`, options));
97
+ }
98
+ }
99
+
100
+ function annotateWithOptionalIdentity(source, options) {
101
+ try {
102
+ return annotate(source, options);
103
+ } catch (error) {
104
+ if (!/got 'EOF'|Expecting/.test(error.message)) throw error;
105
+ return annotate(`${source}\n()`, options);
106
+ }
107
+ }
108
+
109
+ function mergeMeta(state, meta = {}) {
110
+ for (const [hash, entry] of Object.entries(meta)) {
111
+ const origins = entry?.origins || [];
112
+ if (!state.meta[hash]) state.meta[hash] = { origins: [] };
113
+ state.meta[hash].origins.push(...origins);
114
+ }
115
+ }
116
+
117
+ function rememberOrigin(state, hash, name, kind, source = "<repl>") {
118
+ if (!state.meta[hash]) state.meta[hash] = { origins: [] };
119
+ const exists = state.meta[hash].origins.some((origin) =>
120
+ origin.name === name && origin.kind === kind && origin.source === source
121
+ );
122
+ if (!exists) {
123
+ state.meta[hash].origins.push({
124
+ source,
125
+ name,
126
+ kind,
127
+ compiledAt: new Date().toISOString()
128
+ });
129
+ }
130
+ }
131
+
132
+ function recoverAliasesFromMeta(state, lib) {
133
+ for (const [hash, entry] of Object.entries(lib.meta || {})) {
134
+ for (const origin of entry?.origins || []) {
135
+ if (!origin?.name || !NAME_RE.test(origin.name)) continue;
136
+ if (origin.kind === "code" || (!origin.kind && hash in state.codes && !(hash in state.rels))) {
137
+ state.typeAliases[origin.name] = hash;
138
+ } else if (origin.kind === "rel" || hash in state.rels) {
139
+ state.relAliases[origin.name] = hash;
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ function mergeLibrary(state, lib, source = "<load>") {
146
+ state.codes = { ...state.codes, ...(lib.codes || {}) };
147
+ state.rels = { ...state.rels, ...(lib.rels || {}) };
148
+ state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
149
+ mergeMeta(state, lib.meta);
150
+
151
+ for (const [name, hash] of Object.entries(lib.relAlias || {})) {
152
+ if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
153
+ state.relAliases[name] = hash;
154
+ rememberOrigin(state, hash, name, "rel", source);
155
+ }
156
+ }
157
+ recoverAliasesFromMeta(state, lib);
158
+ restoreCodes(state);
159
+ }
160
+
161
+ function savedLibrary(state) {
162
+ const meta = cloneJSON(state.meta);
163
+ for (const [name, hash] of Object.entries(state.typeAliases)) {
164
+ if (!meta[hash]) meta[hash] = { origins: [] };
165
+ meta[hash].origins.push({ source: "<repl>", name, kind: "code" });
166
+ }
167
+ for (const [name, hash] of Object.entries(state.relAliases)) {
168
+ if (!meta[hash]) meta[hash] = { origins: [] };
169
+ meta[hash].origins.push({ source: "<repl>", name, kind: "rel" });
170
+ }
171
+ return {
172
+ format: "k-object",
173
+ version: 2,
174
+ codes: state.codes,
175
+ rels: state.rels,
176
+ relAlias: state.relAliases,
177
+ compileStats: { sccs: [], sccCount: 0 },
178
+ meta,
179
+ main: null
180
+ };
181
+ }
182
+
183
+ function executableObject(state, mainExpression) {
184
+ const main = (mainExpression || state.lastMain || "").trim();
185
+ if (!main) {
186
+ throw new Error(":ko requires a main expression unless one has already been evaluated or defined");
187
+ }
188
+
189
+ restoreCodes(state);
190
+ try {
191
+ return compileObject([aliasPreamble(state), main].filter(Boolean).join("\n"), {
192
+ source: "<repl>",
193
+ libraries: [stateLibrary(state)]
194
+ });
195
+ } finally {
196
+ restoreCodes(state);
197
+ }
198
+ }
199
+
200
+ function resolveRel(state, name) {
201
+ const hash = state.relAliases[name] || (name.startsWith("@") ? name : null);
202
+ return hash ? { hash, rel: state.rels[hash] } : { hash: null, rel: null };
203
+ }
204
+
205
+ function relTypeString(rel) {
206
+ const filters = patterns2filters(rel.typePatternGraph, ...rel.def.patterns);
207
+ return filters.map((filter) => prettyRel({ op: "filter", filter })).join(" --> ");
208
+ }
209
+
210
+ function listAliases(aliases) {
211
+ const entries = Object.entries(aliases).sort(([a], [b]) => a.localeCompare(b));
212
+ if (entries.length === 0) return "(none)";
213
+ return entries.map(([name, hash]) => `${name} = ${hash}`).join("\n");
214
+ }
215
+
216
+ function printValue(value = null) {
217
+ return valueWithEnvelopeToK(value);
218
+ }
219
+
220
+ function userDefinedNames(source) {
221
+ let parsed;
222
+ try {
223
+ parsed = parse(source);
224
+ } catch (error) {
225
+ if (!/got 'EOF'|Expecting/.test(error.message)) throw error;
226
+ parsed = parse(`${source}\n()`);
227
+ }
228
+
229
+ return {
230
+ typeNames: Object.keys(parsed.defs.codes).filter((name) =>
231
+ NAME_RE.test(name) && !name.startsWith("@") && !name.startsWith(":")
232
+ ),
233
+ relNames: Object.keys(parsed.defs.rels).filter((name) =>
234
+ NAME_RE.test(name) && !name.startsWith("@") && name !== "__main__"
235
+ )
236
+ };
237
+ }
238
+
239
+ function libraryOriginsFromSource(source, fullSource, lib, options) {
240
+ const { typeNames, relNames } = userDefinedNames(source);
241
+ const annotated = annotateWithOptionalIdentity(fullSource, options);
242
+ const meta = {};
243
+ const now = new Date().toISOString();
244
+
245
+ for (const name of typeNames) {
246
+ const hash = annotated.representatives?.[name];
247
+ if (!hash) continue;
248
+ if (!meta[hash]) meta[hash] = { origins: [] };
249
+ meta[hash].origins.push({ source: options.source || null, name, kind: "code", compiledAt: now });
250
+ }
251
+
252
+ const relAlias = Object.fromEntries(
253
+ relNames
254
+ .map((name) => [name, lib.relAlias?.[name]])
255
+ .filter(([, hash]) => hash != null)
256
+ );
257
+
258
+ for (const [name, hash] of Object.entries(relAlias)) {
259
+ if (!meta[hash]) meta[hash] = { origins: [] };
260
+ meta[hash].origins.push({ source: options.source || null, name, kind: "rel", compiledAt: now });
261
+ }
262
+
263
+ return { relAlias, meta };
264
+ }
265
+
266
+ function canonicalNames(state) {
267
+ return [...new Set([
268
+ ...Object.keys(state.codes),
269
+ ...Object.keys(state.rels),
270
+ ...Object.values(state.typeAliases),
271
+ ...Object.values(state.relAliases)
272
+ ])].filter((name) => name.startsWith("@")).sort();
273
+ }
274
+
275
+ function expandHome(input) {
276
+ if (input === "~") return os.homedir();
277
+ if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
278
+ return input;
279
+ }
280
+
281
+ function completionTokenStart(text) {
282
+ for (let i = text.length - 1; i >= 0; i--) {
283
+ if (/\s/.test(text[i])) return i + 1;
284
+ }
285
+ return 0;
286
+ }
287
+
288
+ function completeCommandName(line) {
289
+ const partial = line.slice(1);
290
+ const matches = COMMAND_NAMES
291
+ .filter((command) => command.startsWith(partial))
292
+ .map((command) => `:${command}`);
293
+ return [matches, line];
294
+ }
295
+
296
+ function completeCanonical(line, state) {
297
+ const match = line.match(/^(.*?)(@[A-Za-z0-9_?!+-]*)$/);
298
+ if (!match) return [[], line];
299
+ const [, prefix, partial] = match;
300
+ const matches = canonicalNames(state)
301
+ .filter((name) => name.startsWith(partial))
302
+ .map((name) => `${prefix}${name}`);
303
+ return [matches, line];
304
+ }
305
+
306
+ function completePath(line, tokenStart) {
307
+ const token = line.slice(tokenStart);
308
+ const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
309
+ const rawToken = quote ? token.slice(1) : token;
310
+ const expanded = expandHome(rawToken);
311
+ const hasTrailingSlash = rawToken.endsWith("/") || rawToken.endsWith(path.sep);
312
+ const partial = rawToken === "" || hasTrailingSlash ? "" : path.basename(rawToken);
313
+ const rawPrefix = rawToken.slice(0, rawToken.length - partial.length);
314
+ const searchDir = rawToken === ""
315
+ ? "."
316
+ : hasTrailingSlash
317
+ ? expanded
318
+ : (path.dirname(expanded) || ".");
319
+
320
+ let entries;
321
+ try {
322
+ entries = fs.readdirSync(searchDir, { withFileTypes: true });
323
+ } catch {
324
+ return [[], line];
325
+ }
326
+
327
+ const matches = entries
328
+ .filter((entry) => entry.name.startsWith(partial))
329
+ .map((entry) => {
330
+ const suffix = entry.isDirectory() ? "/" : "";
331
+ return `${line.slice(0, tokenStart)}${quote}${rawPrefix}${entry.name}${suffix}`;
332
+ })
333
+ .sort();
334
+
335
+ return [matches, line];
336
+ }
337
+
338
+ function commandArgIndex(arg) {
339
+ const trimmed = arg.trimStart();
340
+ if (trimmed === "") return 0;
341
+ return trimmed.split(/\s+/).length - 1;
342
+ }
343
+
344
+ function completeCommandArgument(line, state) {
345
+ const body = line.slice(1);
346
+ const firstSpace = body.search(/\s/);
347
+ if (firstSpace === -1) return completeCommandName(line);
348
+
349
+ const command = body.slice(0, firstSpace);
350
+ const argStart = 1 + firstSpace + 1;
351
+ const arg = line.slice(argStart);
352
+
353
+ if (PATH_COMMANDS.has(command) && commandArgIndex(arg) === 0) {
354
+ const tokenStart = argStart + completionTokenStart(arg);
355
+ return completePath(line, tokenStart);
356
+ }
357
+
358
+ if (/@[A-Za-z0-9_?!+-]*$/.test(line)) {
359
+ return completeCanonical(line, state);
360
+ }
361
+
362
+ return [[], line];
363
+ }
364
+
365
+ function completeInput(line, state) {
366
+ if (line.startsWith(":")) {
367
+ return completeCommandArgument(line, state);
368
+ }
369
+ if (/@[A-Za-z0-9_?!+-]*$/.test(line)) {
370
+ return completeCanonical(line, state);
371
+ }
372
+ return [[], line];
373
+ }
374
+
375
+ function createCompleter(state) {
376
+ return (line) => completeInput(line, state);
377
+ }
378
+
379
+ function ensureSemicolon(source) {
380
+ return source.trim().endsWith(";") ? source : `${source};`;
381
+ }
382
+
383
+ async function defineType(input, state) {
384
+ const typeMatch = input.match(TYPE_DEF_RE);
385
+ if (!typeMatch) {
386
+ throw new Error("Type definitions use: :type name = <...>");
387
+ }
388
+
389
+ const name = typeMatch[1];
390
+ restoreCodes(state);
391
+ const source = [aliasPreamble(state, name), ensureSemicolon(input), "()"].filter(Boolean).join("\n");
392
+ const annotated = annotate(source, { libraries: [stateLibrary(state)] });
393
+ const hash = annotated.representatives[name];
394
+ if (!hash) throw new Error(`Type definition did not produce an alias for '${name}'`);
395
+ state.codes = codes.dump();
396
+ state.typeAliases[name] = hash;
397
+ rememberOrigin(state, hash, name, "code");
398
+ return [`$ ${name} = ${hash}`];
399
+ }
400
+
401
+ async function defineRelation(input, state) {
402
+ const relMatch = input.match(REL_DEF_RE);
403
+ if (!relMatch) {
404
+ throw new Error("Relation definitions use: :rel name = expression");
405
+ }
406
+
407
+ const name = relMatch[1];
408
+ restoreCodes(state);
409
+ const source = [aliasPreamble(state, name), ensureSemicolon(input), name].filter(Boolean).join("\n");
410
+ const lib = hydrateObject(compileLibrary(source, { source: "<repl>", libraries: [stateLibrary(state)] }));
411
+ const hash = lib.relAlias?.[name];
412
+ if (!hash || !lib.rels?.[hash]) throw new Error(`Relation definition did not produce an alias for '${name}'`);
413
+ state.codes = codes.dump();
414
+ state.rels[hash] = lib.rels[hash];
415
+ state.relAliases[name] = hash;
416
+ state.lastMain = name;
417
+ mergeMeta(state, lib.meta);
418
+ rememberOrigin(state, hash, name, "rel");
419
+ return [`${name} = ${hash}`];
420
+ }
421
+
422
+ async function runExpression(input, state) {
423
+ const expression = input.trim();
424
+ if (!expression) throw new Error(":run requires an expression");
425
+
426
+ restoreCodes(state);
427
+ const source = [aliasPreamble(state), expression].filter(Boolean).join("\n");
428
+ const annotated = annotate(source, { libraries: [stateLibrary(state)] });
429
+ const mainRel = annotated.rels.__main__;
430
+ run.defs = annotated;
431
+ state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
432
+ state.lastMain = expression;
433
+ restoreCodes(state);
434
+ return [printValue(state.value)];
435
+ }
436
+
437
+ async function evaluateInput(input, state) {
438
+ const line = input.trim();
439
+ if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
440
+
441
+ if (line.startsWith(":")) {
442
+ return evaluateCommand(line, state);
443
+ }
444
+
445
+ if (input.match(TYPE_DEF_RE)) return defineType(input, state);
446
+ if (input.match(REL_DEF_RE)) return defineRelation(input, state);
447
+ return runExpression(input, state);
448
+ }
449
+
450
+ function parseCommand(line) {
451
+ const body = line.slice(1).trim();
452
+ const [command, ...rest] = body.split(/\s+/);
453
+ return { command, arg: rest.join(" ") };
454
+ }
455
+
456
+ async function evaluateCommand(line, state) {
457
+ const { command, arg } = parseCommand(line);
458
+ const usagePrefix = ":";
459
+
460
+ switch (command) {
461
+ case "quit":
462
+ case "exit":
463
+ exit(0);
464
+ case "help":
465
+ return [helpText()];
466
+ case "type": {
467
+ if (!arg) throw new Error(":type requires a name or definition");
468
+ if (arg.includes("=")) {
469
+ const definition = arg.trim().startsWith("$") ? arg : `$ ${arg}`;
470
+ return defineType(definition, state);
471
+ }
472
+ return evaluateCommand(`:C ${arg}`, state);
473
+ }
474
+ case "code":
475
+ return evaluateCommand(`:C ${arg}`, state);
476
+ case "rel":
477
+ case "def":
478
+ if (!arg) throw new Error(`:${command} requires a relation definition`);
479
+ return defineRelation(arg, state);
480
+ case "run":
481
+ case "eval":
482
+ return runExpression(arg, state);
483
+ case "codes":
484
+ return [listAliases(state.typeAliases)];
485
+ case "rels":
486
+ return [listAliases(state.relAliases)];
487
+ case "val":
488
+ return [
489
+ printValue(state.value),
490
+ JSON.stringify(state.value, null, 2)
491
+ ];
492
+ case "reset": {
493
+ const fresh = createState();
494
+ Object.assign(state, fresh);
495
+ return ["reset"];
496
+ }
497
+ case "save": {
498
+ if (!arg) throw new Error(`${usagePrefix}save requires a file path`);
499
+ fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
500
+ return [`saved ${arg}`];
501
+ }
502
+ case "klib": {
503
+ if (!arg) throw new Error(`${usagePrefix}klib requires a file path`);
504
+ fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
505
+ return [`saved ${arg}`];
506
+ }
507
+ case "ko": {
508
+ if (!arg) throw new Error(`${usagePrefix}ko requires a file path`);
509
+ const [path, ...mainParts] = arg.split(/\s+/);
510
+ const object = executableObject(state, mainParts.join(" "));
511
+ fs.writeFileSync(path, encodeObject(object));
512
+ const main = (mainParts.join(" ") || state.lastMain).trim();
513
+ return [`saved ${path} (${main})`];
514
+ }
515
+ case "export": {
516
+ if (!arg) throw new Error(`${usagePrefix}export requires a file path`);
517
+ const [path, ...mainParts] = arg.split(/\s+/);
518
+ if (path.endsWith(".klib")) {
519
+ fs.writeFileSync(path, encodeLibrary(savedLibrary(state)));
520
+ return [`saved ${path}`];
521
+ }
522
+ if (!path.endsWith(".ko")) {
523
+ throw new Error(":export writes .klib or .ko files; use :klib or :ko for explicit export");
524
+ }
525
+ const object = executableObject(state, mainParts.join(" "));
526
+ fs.writeFileSync(path, encodeObject(object));
527
+ const main = (mainParts.join(" ") || state.lastMain).trim();
528
+ return [`saved ${path} (${main})`];
529
+ }
530
+ case "load": {
531
+ if (!arg) throw new Error(`${usagePrefix}load requires a file path`);
532
+ if (arg.endsWith(".klib")) {
533
+ const lib = loadLibrary(decodeLibrary(fs.readFileSync(arg)));
534
+ mergeLibrary(state, lib, arg);
535
+ } else {
536
+ restoreCodes(state);
537
+ const source = fs.readFileSync(arg, "utf8");
538
+ const fullSource = [aliasPreamble(state), source].filter(Boolean).join("\n");
539
+ const options = {
540
+ source: arg,
541
+ libraries: [stateLibrary(state)]
542
+ };
543
+ const lib = compileWithOptionalIdentity(fullSource, options);
544
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
545
+ mergeLibrary(state, { ...lib, relAlias, meta }, arg);
546
+ }
547
+ return [`loaded ${arg}`];
548
+ }
549
+ case "t": {
550
+ if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
551
+ const { hash, rel } = resolveRel(state, arg);
552
+ if (!rel) throw new Error(`Unknown relation '${arg}'`);
553
+ return [`${arg} : ${relTypeString(rel)} (${hash})`];
554
+ }
555
+ case "d": {
556
+ if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
557
+ const { hash, rel } = resolveRel(state, arg);
558
+ if (!rel) throw new Error(`Unknown relation '${arg}'`);
559
+ return [`${arg} = ${prettyRel(rel.def)}; -- ${hash}`];
560
+ }
561
+ case "C": {
562
+ if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
563
+ const hash = state.typeAliases[arg] || (arg.startsWith("@") ? arg : null);
564
+ if (!hash || !(hash in state.codes)) throw new Error(`Unknown type '${arg}'`);
565
+ return [`$ ${arg} = ${prettyCode({}, codes.find, codes.find(hash))}; -- ${hash}`];
566
+ }
567
+ default:
568
+ throw new Error(`Unknown command ':${command}'. Try :help`);
569
+ }
570
+ }
571
+
572
+ function helpText() {
573
+ return [
574
+ ":type name = <...> define a type",
575
+ ":rel name = expr define a relation",
576
+ ":run expr run an expression on the current value",
577
+ ":t name show relation type",
578
+ ":d name show relation definition",
579
+ ":type name show type definition",
580
+ ":codes list type aliases",
581
+ ":rels list relation aliases",
582
+ ":load file load .k source or .klib",
583
+ ":klib file export state as .klib",
584
+ ":ko file [expr] export executable .ko",
585
+ ":export file [expr] export by extension: .klib or .ko",
586
+ ":val print current value",
587
+ ":reset clear state",
588
+ ":help show this help",
589
+ "",
590
+ "Raw k definitions and expressions are still accepted as shorthand."
591
+ ].join("\n");
592
+ }
593
+
594
+ function startRepl() {
595
+ const state = createState();
596
+ const rl = readline.createInterface({
597
+ input: stdin,
598
+ output: stdout,
599
+ prompt: "> ",
600
+ completer: createCompleter(state)
601
+ });
602
+ const buffer = [];
603
+ let pending = Promise.resolve();
604
+ let closed = false;
605
+
606
+ console.log("k interpreter (.klib-backed). Type :help for commands.");
607
+ rl.prompt();
608
+ async function handleLine(line) {
609
+ if (line.trim().endsWith("\\")) {
610
+ buffer.push(line.replace(/\\\s*$/, ""));
611
+ rl.setPrompt(" ");
612
+ if (!closed) rl.prompt();
613
+ return;
614
+ }
615
+
616
+ const input = [...buffer, line].join("\n");
617
+ buffer.length = 0;
618
+ rl.setPrompt("> ");
619
+
620
+ try {
621
+ for (const output of await evaluateInput(input, state)) {
622
+ console.log(output);
623
+ }
624
+ } catch (error) {
625
+ console.error(error.message || String(error));
626
+ }
627
+ if (!closed) rl.prompt();
628
+ }
629
+
630
+ rl.on("line", (line) => {
631
+ pending = pending.then(() => handleLine(line));
632
+ }).on("close", () => {
633
+ closed = true;
634
+ pending.finally(() => exit(0));
635
+ });
636
+ }
637
+
638
+ if (argv[1] && fileURLToPath(import.meta.url) === argv[1]) {
639
+ startRepl();
640
+ }
641
+
642
+ export {
643
+ canonicalNames,
644
+ completeInput,
645
+ createCompleter,
646
+ createState,
647
+ evaluateInput,
648
+ helpText,
649
+ printValue,
650
+ propertyListToFilter,
651
+ valueToK
652
+ };