@fraczak/k 6.0.1 → 6.0.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.
Files changed (55) hide show
  1. package/AGENTS.md +35 -0
  2. package/CHANGELOG.md +48 -0
  3. package/CLAUDE.md +64 -0
  4. package/DOCS/BIG_PICTURE.md +4 -8
  5. package/DOCS/BINARY_CODEC_STATUS_AND_TODO.md +74 -0
  6. package/DOCS/CONVERGENCE.md +92 -0
  7. package/DOCS/DIVERGENCE_DETECTION.md +24 -0
  8. package/DOCS/Makefile +99 -0
  9. package/DOCS/OBJECT_FILE_AND_PATTERN.md +221 -0
  10. package/DOCS/PATTERNS.md +114 -0
  11. package/DOCS/PATTERNS_AND_TYPED_VALUES.md +665 -0
  12. package/DOCS/REPL.md +127 -126
  13. package/DOCS/TEXTUAL_VALUES.md +108 -0
  14. package/DOCS/TYPE_DERIVATION.md +230 -0
  15. package/DOCS/book/06-values-in-memory.md +1085 -0
  16. package/DOCS/book/07-the-partial-function-abi.md +143 -0
  17. package/DOCS/book/08-operational-semantics-and-execution.md +201 -0
  18. package/DOCS/book/09-compiler-architecture.md +160 -0
  19. package/DOCS/book/10-from-ast-to-intermediate-representation.md +167 -0
  20. package/DOCS/book/14-canonical-serialization.md +176 -0
  21. package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +155 -0
  22. package/DOCS/book/overview.txt +68 -0
  23. package/README.md +78 -30
  24. package/UU/ta.k +1 -0
  25. package/UU/ta.k~ +0 -0
  26. package/UU/tb.k +0 -0
  27. package/UU/tc.k +0 -0
  28. package/codecs/README.md +12 -0
  29. package/codecs/pandoc.css +71 -0
  30. package/codecs/runtime/test-codec.mjs +89 -0
  31. package/core.klib +0 -0
  32. package/core.klib~ +0 -0
  33. package/ieee.klib +0 -0
  34. package/kkk.k +197 -0
  35. package/kkk.klib +0 -0
  36. package/kkk.klib~ +0 -0
  37. package/object.mjs +162 -8
  38. package/objects/README.md +10 -0
  39. package/objects/compile-lib.mjs +26 -2
  40. package/objects/compile.mjs +26 -2
  41. package/objects/decompile.mjs +25 -2
  42. package/objects/extract-aliases.mjs +59 -0
  43. package/package.json +3 -2
  44. package/{codecs/k-pattern.mjs → patterns/from-k.mjs} +3 -3
  45. package/repl2.mjs +345 -83
  46. package/scripts/generate-dependency-graph.mjs +121 -0
  47. package/test-k-object.mjs +77 -1
  48. package/test-repl2.mjs +65 -8
  49. package/DOCS/book-combined.html +0 -3798
  50. package/DOCS/book-combined.md +0 -3745
  51. package/DOCS/book-combined.pdf +0 -0
  52. package/DOCS/book-combined.tex +0 -5682
  53. package/DOCS/book.html +0 -65
  54. package/DOCS/book.pdf +0 -0
  55. package/DOCS/book.tex +0 -150
package/repl2.mjs CHANGED
@@ -28,10 +28,10 @@ const TYPE_DEF_RE = /^\s*\$\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
28
28
  const REL_DEF_RE = /^\s*([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)\s*=/;
29
29
  const COMMAND_NAMES = [
30
30
  "help", "type", "code", "rel", "def", "run", "eval", "t", "d", "C",
31
- "codes", "rels", "val", "reset", "save", "klib", "ko", "export", "load",
31
+ "codes", "rels", "val", "reset", "klib", "ko", "load",
32
32
  "quit", "exit"
33
33
  ];
34
- const PATH_COMMANDS = new Set(["save", "klib", "ko", "export", "load"]);
34
+ const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
35
35
  const initialCodes = codes.dump();
36
36
 
37
37
  function cloneJSON(value) {
@@ -88,12 +88,49 @@ function aliasPreamble(state, omitName = null) {
88
88
  return lines.filter(Boolean).join("\n");
89
89
  }
90
90
 
91
+ function preambleLineCount(preamble) {
92
+ if (!preamble) return 0;
93
+ return preamble.split("\n").length;
94
+ }
95
+
96
+ function remapLineNumber(line, offset) {
97
+ return Math.max(1, line - offset);
98
+ }
99
+
100
+ function remapDiagnosticMessage(message, lineOffset) {
101
+ if (!lineOffset) return message;
102
+
103
+ let rewritten = message.replace(
104
+ /\(lines (\d+):(\d+)\.\.\.(\d+):(\d+)\)/g,
105
+ (_, startLine, startCol, endLine, endCol) =>
106
+ `(lines ${remapLineNumber(Number(startLine), lineOffset)}:${startCol}...${remapLineNumber(Number(endLine), lineOffset)}:${endCol})`
107
+ );
108
+
109
+ rewritten = rewritten.replace(
110
+ /Parse error on line (\d+):/g,
111
+ (_, line) => `Parse error on line ${remapLineNumber(Number(line), lineOffset)}:`
112
+ );
113
+
114
+ return rewritten;
115
+ }
116
+
117
+ function remapError(error, lineOffset) {
118
+ if (!lineOffset || !error?.message) return error;
119
+ const remapped = new Error(remapDiagnosticMessage(error.message, lineOffset));
120
+ remapped.name = error.name;
121
+ if (error.stack) {
122
+ remapped.stack = error.stack.replace(error.message, remapped.message);
123
+ }
124
+ if ("cause" in error) remapped.cause = error.cause;
125
+ return remapped;
126
+ }
127
+
91
128
  function compileWithOptionalIdentity(source, options) {
92
129
  try {
93
130
  return hydrateObject(compileLibrary(source, options));
94
131
  } catch (error) {
95
132
  if (!/got 'EOF'|Expecting/.test(error.message)) throw error;
96
- return hydrateObject(compileLibrary(`${source}\n()`, options));
133
+ return hydrateObject(compileLibrary(`${ensureSemicolon(source)}\n()`, options));
97
134
  }
98
135
  }
99
136
 
@@ -102,28 +139,34 @@ function annotateWithOptionalIdentity(source, options) {
102
139
  return annotate(source, options);
103
140
  } catch (error) {
104
141
  if (!/got 'EOF'|Expecting/.test(error.message)) throw error;
105
- return annotate(`${source}\n()`, options);
142
+ return annotate(`${ensureSemicolon(source)}\n()`, options);
106
143
  }
107
144
  }
108
145
 
109
146
  function mergeMeta(state, meta = {}) {
110
147
  for (const [hash, entry] of Object.entries(meta)) {
111
148
  const origins = entry?.origins || [];
112
- if (!state.meta[hash]) state.meta[hash] = { origins: [] };
113
- state.meta[hash].origins.push(...origins);
149
+ const entryType = entry?.type;
150
+ if (!state.meta[hash]) state.meta[hash] = { type: entryType, origins: [] };
151
+ if (state.meta[hash].type == null && entryType != null) state.meta[hash].type = entryType;
152
+ state.meta[hash].origins.push(...origins.map(({ source, name, compiledAt }) => ({
153
+ source,
154
+ name,
155
+ ...(compiledAt == null ? {} : { compiledAt })
156
+ })));
114
157
  }
115
158
  }
116
159
 
117
- function rememberOrigin(state, hash, name, kind, source = "<repl>") {
118
- if (!state.meta[hash]) state.meta[hash] = { origins: [] };
160
+ function rememberOrigin(state, hash, name, type, source = "<repl>") {
161
+ if (!state.meta[hash]) state.meta[hash] = { type, origins: [] };
162
+ if (state.meta[hash].type == null) state.meta[hash].type = type;
119
163
  const exists = state.meta[hash].origins.some((origin) =>
120
- origin.name === name && origin.kind === kind && origin.source === source
164
+ origin.name === name && state.meta[hash].type === type && origin.source === source
121
165
  );
122
166
  if (!exists) {
123
167
  state.meta[hash].origins.push({
124
168
  source,
125
169
  name,
126
- kind,
127
170
  compiledAt: new Date().toISOString()
128
171
  });
129
172
  }
@@ -133,40 +176,47 @@ function recoverAliasesFromMeta(state, lib) {
133
176
  for (const [hash, entry] of Object.entries(lib.meta || {})) {
134
177
  for (const origin of entry?.origins || []) {
135
178
  if (!origin?.name || !NAME_RE.test(origin.name)) continue;
136
- if (origin.kind === "code" || (!origin.kind && hash in state.codes && !(hash in state.rels))) {
179
+ const type = entry?.type;
180
+ if (type === "code" || (!type && hash in state.codes && !(hash in state.rels))) {
137
181
  state.typeAliases[origin.name] = hash;
138
- } else if (origin.kind === "rel" || hash in state.rels) {
182
+ } else if (type === "rel" || hash in state.rels) {
139
183
  state.relAliases[origin.name] = hash;
140
184
  }
141
185
  }
142
186
  }
143
187
  }
144
188
 
145
- function mergeLibrary(state, lib, source = "<load>") {
189
+ function mergeLibrary(state, lib, source = "<load>", options = {}) {
190
+ const loadAliases = options.loadAliases ?? true;
146
191
  state.codes = { ...state.codes, ...(lib.codes || {}) };
147
192
  state.rels = { ...state.rels, ...(lib.rels || {}) };
148
- state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
149
193
  mergeMeta(state, lib.meta);
150
194
 
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);
195
+ if (loadAliases) {
196
+ state.relAliases = { ...state.relAliases, ...(lib.relAlias || {}) };
197
+ for (const [name, hash] of Object.entries(lib.relAlias || {})) {
198
+ if (name !== "__main__" && NAME_RE.test(name) && hash in state.rels) {
199
+ state.relAliases[name] = hash;
200
+ rememberOrigin(state, hash, name, "rel", source);
201
+ }
155
202
  }
203
+ recoverAliasesFromMeta(state, lib);
156
204
  }
157
- recoverAliasesFromMeta(state, lib);
158
205
  restoreCodes(state);
159
206
  }
160
207
 
161
208
  function savedLibrary(state) {
162
209
  const meta = cloneJSON(state.meta);
210
+ const now = new Date().toISOString();
163
211
  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" });
212
+ if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
213
+ if (meta[hash].type == null) meta[hash].type = "code";
214
+ meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
166
215
  }
167
216
  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" });
217
+ if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
218
+ if (meta[hash].type == null) meta[hash].type = "rel";
219
+ meta[hash].origins.push({ source: "<repl>", name, compiledAt: now });
170
220
  }
171
221
  return {
172
222
  format: "k-object",
@@ -183,15 +233,18 @@ function savedLibrary(state) {
183
233
  function executableObject(state, mainExpression) {
184
234
  const main = (mainExpression || state.lastMain || "").trim();
185
235
  if (!main) {
186
- throw new Error(":ko requires a main expression unless one has already been evaluated or defined");
236
+ throw new Error(":ko file expr requires a main expression unless one has already been evaluated or defined");
187
237
  }
188
238
 
189
239
  restoreCodes(state);
240
+ const preamble = aliasPreamble(state);
190
241
  try {
191
- return compileObject([aliasPreamble(state), main].filter(Boolean).join("\n"), {
242
+ return compileObject([preamble, main].filter(Boolean).join("\n"), {
192
243
  source: "<repl>",
193
244
  libraries: [stateLibrary(state)]
194
245
  });
246
+ } catch (error) {
247
+ throw remapError(error, preambleLineCount(preamble));
195
248
  } finally {
196
249
  restoreCodes(state);
197
250
  }
@@ -245,8 +298,8 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
245
298
  for (const name of typeNames) {
246
299
  const hash = annotated.representatives?.[name];
247
300
  if (!hash) continue;
248
- if (!meta[hash]) meta[hash] = { origins: [] };
249
- meta[hash].origins.push({ source: options.source || null, name, kind: "code", compiledAt: now });
301
+ if (!meta[hash]) meta[hash] = { type: "code", origins: [] };
302
+ meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
250
303
  }
251
304
 
252
305
  const relAlias = Object.fromEntries(
@@ -256,8 +309,8 @@ function libraryOriginsFromSource(source, fullSource, lib, options) {
256
309
  );
257
310
 
258
311
  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 });
312
+ if (!meta[hash]) meta[hash] = { type: "rel", origins: [] };
313
+ meta[hash].origins.push({ source: options.source || null, name, compiledAt: now });
261
314
  }
262
315
 
263
316
  return { relAlias, meta };
@@ -272,6 +325,13 @@ function canonicalNames(state) {
272
325
  ])].filter((name) => name.startsWith("@")).sort();
273
326
  }
274
327
 
328
+ function aliasNames(state) {
329
+ return [...new Set([
330
+ ...Object.keys(state.typeAliases),
331
+ ...Object.keys(state.relAliases)
332
+ ])].sort();
333
+ }
334
+
275
335
  function expandHome(input) {
276
336
  if (input === "~") return os.homedir();
277
337
  if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
@@ -303,6 +363,19 @@ function completeCanonical(line, state) {
303
363
  return [matches, line];
304
364
  }
305
365
 
366
+ function completeIdentifier(line, state) {
367
+ const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
368
+ if (!match) return [[], line];
369
+ const [, prefix, partial] = match;
370
+ const isTypeToken = partial.startsWith("$");
371
+ const barePartial = isTypeToken ? partial.slice(1) : partial;
372
+ const names = isTypeToken ? Object.keys(state.typeAliases).sort() : aliasNames(state);
373
+ const matches = names
374
+ .filter((name) => name.startsWith(barePartial))
375
+ .map((name) => `${prefix}${isTypeToken ? "$" : ""}${name}`);
376
+ return [matches, line];
377
+ }
378
+
306
379
  function completePath(line, tokenStart) {
307
380
  const token = line.slice(tokenStart);
308
381
  const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
@@ -341,6 +414,26 @@ function commandArgIndex(arg) {
341
414
  return trimmed.split(/\s+/).length - 1;
342
415
  }
343
416
 
417
+ function loadPathCompletionStart(argStart, arg) {
418
+ const leadingWhitespace = arg.length - arg.trimStart().length;
419
+ const trimmed = arg.trimStart();
420
+ if (trimmed === "" || !trimmed.startsWith("--no-alias")) {
421
+ if (commandArgIndex(arg) !== 0) return null;
422
+ return argStart + completionTokenStart(arg);
423
+ }
424
+ if (!"--no-alias".startsWith(trimmed) && !trimmed.startsWith("--no-alias ")) {
425
+ return null;
426
+ }
427
+ if (trimmed !== "--no-alias" && !trimmed.startsWith("--no-alias ")) {
428
+ return null;
429
+ }
430
+ if (trimmed === "--no-alias") return null;
431
+ const restStart = leadingWhitespace + "--no-alias".length;
432
+ const rest = arg.slice(restStart);
433
+ const restLeadingWhitespace = rest.length - rest.trimStart().length;
434
+ return argStart + restStart + restLeadingWhitespace + completionTokenStart(rest.trimStart());
435
+ }
436
+
344
437
  function completeCommandArgument(line, state) {
345
438
  const body = line.slice(1);
346
439
  const firstSpace = body.search(/\s/);
@@ -350,8 +443,13 @@ function completeCommandArgument(line, state) {
350
443
  const argStart = 1 + firstSpace + 1;
351
444
  const arg = line.slice(argStart);
352
445
 
353
- if (PATH_COMMANDS.has(command) && commandArgIndex(arg) === 0) {
354
- const tokenStart = argStart + completionTokenStart(arg);
446
+ if (PATH_COMMANDS.has(command)) {
447
+ const tokenStart = command === "load"
448
+ ? loadPathCompletionStart(argStart, arg)
449
+ : commandArgIndex(arg) === 0
450
+ ? argStart + completionTokenStart(arg)
451
+ : null;
452
+ if (tokenStart == null) return [[], line];
355
453
  return completePath(line, tokenStart);
356
454
  }
357
455
 
@@ -359,6 +457,10 @@ function completeCommandArgument(line, state) {
359
457
  return completeCanonical(line, state);
360
458
  }
361
459
 
460
+ if (/\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*$/.test(line)) {
461
+ return completeIdentifier(line, state);
462
+ }
463
+
362
464
  return [[], line];
363
465
  }
364
466
 
@@ -369,6 +471,9 @@ function completeInput(line, state) {
369
471
  if (/@[A-Za-z0-9_?!+-]*$/.test(line)) {
370
472
  return completeCanonical(line, state);
371
473
  }
474
+ if (/\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*$/.test(line)) {
475
+ return completeIdentifier(line, state);
476
+ }
372
477
  return [[], line];
373
478
  }
374
479
 
@@ -389,6 +494,90 @@ function ensureSemicolon(source) {
389
494
  return source.trim().endsWith(";") ? source : `${source};`;
390
495
  }
391
496
 
497
+ function isEofParseError(error) {
498
+ return /got 'EOF'|Unexpected end of input/.test(error.message || "");
499
+ }
500
+
501
+ function parseLoadArgs(arg, usagePrefix = ":") {
502
+ const trimmed = arg.trim();
503
+ if (!trimmed) throw new Error(`${usagePrefix}load requires a file path`);
504
+ if (trimmed === "--no-alias") {
505
+ throw new Error(`${usagePrefix}load --no-alias requires a file path`);
506
+ }
507
+ if (trimmed.startsWith("--no-alias ")) {
508
+ const path = trimmed.slice("--no-alias".length).trim();
509
+ if (!path) throw new Error(`${usagePrefix}load --no-alias requires a file path`);
510
+ return { path, loadAliases: false };
511
+ }
512
+ return { path: arg.trim(), loadAliases: true };
513
+ }
514
+
515
+ function lineForContinuation(line) {
516
+ return line.replace(/\\\s*$/, "");
517
+ }
518
+
519
+ function lineHasExplicitContinuation(line) {
520
+ return /\\\s*$/.test(line);
521
+ }
522
+
523
+ function lineTerminatesSnippet(line) {
524
+ return /;\s*$/.test(line);
525
+ }
526
+
527
+ function explicitSnippetTerminated(source) {
528
+ const lines = source.split("\n");
529
+ const lastLine = lines[lines.length - 1] || "";
530
+ return lineTerminatesSnippet(lastLine);
531
+ }
532
+
533
+ function analyzeRawSnippet(source) {
534
+ try {
535
+ const parsed = parse(source);
536
+ return { kind: "withMain", parsed };
537
+ } catch (error) {
538
+ if (!isEofParseError(error)) throw error;
539
+ }
540
+
541
+ return { kind: "incomplete", parsed: null };
542
+ }
543
+
544
+ function analyzeAcceptedSnippet(source, explicitTerminated = explicitSnippetTerminated(source)) {
545
+ try {
546
+ const parsed = parse(source);
547
+ return { kind: "withMain", parsed };
548
+ } catch (error) {
549
+ if (!explicitTerminated) {
550
+ if (isEofParseError(error)) return { kind: "incomplete", parsed: null };
551
+ throw error;
552
+ }
553
+ }
554
+
555
+ const parsed = parse(`${source}\n()`);
556
+ return { kind: "definitionsOnly", parsed };
557
+ }
558
+
559
+ function compileSnippetArtifacts(source, state, sourceName = "<repl>") {
560
+ restoreCodes(state);
561
+ const preamble = aliasPreamble(state);
562
+ const fullSource = [preamble, source].filter(Boolean).join("\n");
563
+ const options = {
564
+ source: sourceName,
565
+ libraries: [stateLibrary(state)]
566
+ };
567
+ try {
568
+ const lib = compileWithOptionalIdentity(fullSource, options);
569
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
570
+ const annotated = annotateWithOptionalIdentity(fullSource, options);
571
+ return {
572
+ annotated,
573
+ lib: { ...lib, relAlias, meta },
574
+ lineOffset: preambleLineCount(preamble)
575
+ };
576
+ } catch (error) {
577
+ throw remapError(error, preambleLineCount(preamble));
578
+ }
579
+ }
580
+
392
581
  async function defineType(input, state) {
393
582
  const typeMatch = input.match(TYPE_DEF_RE);
394
583
  if (!typeMatch) {
@@ -397,8 +586,14 @@ async function defineType(input, state) {
397
586
 
398
587
  const name = typeMatch[1];
399
588
  restoreCodes(state);
400
- const source = [aliasPreamble(state, name), ensureSemicolon(input), "()"].filter(Boolean).join("\n");
401
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
589
+ const preamble = aliasPreamble(state, name);
590
+ const source = [preamble, ensureSemicolon(input), "()"].filter(Boolean).join("\n");
591
+ let annotated;
592
+ try {
593
+ annotated = annotate(source, { libraries: [stateLibrary(state)] });
594
+ } catch (error) {
595
+ throw remapError(error, preambleLineCount(preamble));
596
+ }
402
597
  const hash = annotated.representatives[name];
403
598
  if (!hash) throw new Error(`Type definition did not produce an alias for '${name}'`);
404
599
  state.codes = codes.dump();
@@ -415,8 +610,14 @@ async function defineRelation(input, state) {
415
610
 
416
611
  const name = relMatch[1];
417
612
  restoreCodes(state);
418
- const source = [aliasPreamble(state, name), ensureSemicolon(input), name].filter(Boolean).join("\n");
419
- const lib = hydrateObject(compileLibrary(source, { source: "<repl>", libraries: [stateLibrary(state)] }));
613
+ const preamble = aliasPreamble(state, name);
614
+ const source = [preamble, ensureSemicolon(input), name].filter(Boolean).join("\n");
615
+ let lib;
616
+ try {
617
+ lib = hydrateObject(compileLibrary(source, { source: "<repl>", libraries: [stateLibrary(state)] }));
618
+ } catch (error) {
619
+ throw remapError(error, preambleLineCount(preamble));
620
+ }
420
621
  const hash = lib.relAlias?.[name];
421
622
  if (!hash || !lib.rels?.[hash]) throw new Error(`Relation definition did not produce an alias for '${name}'`);
422
623
  state.codes = codes.dump();
@@ -433,16 +634,52 @@ async function runExpression(input, state) {
433
634
  if (!expression) throw new Error(":run requires an expression");
434
635
 
435
636
  restoreCodes(state);
436
- const source = [aliasPreamble(state), expression].filter(Boolean).join("\n");
437
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
637
+ const preamble = aliasPreamble(state);
638
+ const source = [preamble, expression].filter(Boolean).join("\n");
639
+ let annotated;
640
+ try {
641
+ annotated = annotate(source, { libraries: [stateLibrary(state)] });
642
+ } catch (error) {
643
+ throw remapError(error, preambleLineCount(preamble));
644
+ }
438
645
  const mainRel = annotated.rels.__main__;
439
646
  run.defs = annotated;
440
- state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
647
+ try {
648
+ state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
649
+ } catch (error) {
650
+ throw remapError(error, preambleLineCount(preamble));
651
+ }
441
652
  state.lastMain = expression;
442
653
  restoreCodes(state);
443
654
  return [printValue(state.value)];
444
655
  }
445
656
 
657
+ async function runSnippet(input, state, options = {}) {
658
+ const snippet = input.trim();
659
+ if (!snippet) return [];
660
+
661
+ const explicitTerminated = options.explicitTerminated ?? explicitSnippetTerminated(input);
662
+ const analysis = analyzeAcceptedSnippet(snippet, explicitTerminated);
663
+ const { annotated, lib, lineOffset } = compileSnippetArtifacts(snippet, state);
664
+ mergeLibrary(state, lib, "<repl>");
665
+
666
+ if (analysis.kind === "definitionsOnly") {
667
+ restoreCodes(state);
668
+ return [];
669
+ }
670
+
671
+ const mainRel = annotated.rels.__main__;
672
+ run.defs = annotated;
673
+ try {
674
+ state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
675
+ } catch (error) {
676
+ throw remapError(error, lineOffset);
677
+ }
678
+ state.lastMain = snippet;
679
+ restoreCodes(state);
680
+ return [printValue(state.value)];
681
+ }
682
+
446
683
  async function evaluateInput(input, state) {
447
684
  const line = input.trim();
448
685
  if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
@@ -451,9 +688,7 @@ async function evaluateInput(input, state) {
451
688
  return evaluateCommand(line, state);
452
689
  }
453
690
 
454
- if (input.match(TYPE_DEF_RE)) return defineType(input, state);
455
- if (input.match(REL_DEF_RE)) return defineRelation(input, state);
456
- return runExpression(input, state);
691
+ return runSnippet(input, state);
457
692
  }
458
693
 
459
694
  function parseCommand(line) {
@@ -503,57 +738,45 @@ async function evaluateCommand(line, state) {
503
738
  Object.assign(state, fresh);
504
739
  return ["reset"];
505
740
  }
506
- case "save": {
507
- if (!arg) throw new Error(`${usagePrefix}save requires a file path`);
508
- fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
509
- return [`saved ${arg}`];
510
- }
511
741
  case "klib": {
512
742
  if (!arg) throw new Error(`${usagePrefix}klib requires a file path`);
513
743
  fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
514
744
  return [`saved ${arg}`];
515
745
  }
516
746
  case "ko": {
517
- if (!arg) throw new Error(`${usagePrefix}ko requires a file path`);
518
- const [path, ...mainParts] = arg.split(/\s+/);
519
- const object = executableObject(state, mainParts.join(" "));
520
- fs.writeFileSync(path, encodeObject(object));
521
- const main = (mainParts.join(" ") || state.lastMain).trim();
522
- return [`saved ${path} (${main})`];
523
- }
524
- case "export": {
525
- if (!arg) throw new Error(`${usagePrefix}export requires a file path`);
747
+ if (!arg) throw new Error(`${usagePrefix}ko requires a file path and main expression`);
526
748
  const [path, ...mainParts] = arg.split(/\s+/);
527
- if (path.endsWith(".klib")) {
528
- fs.writeFileSync(path, encodeLibrary(savedLibrary(state)));
529
- return [`saved ${path}`];
530
- }
531
- if (!path.endsWith(".ko")) {
532
- throw new Error(":export writes .klib or .ko files; use :klib or :ko for explicit export");
749
+ if (mainParts.length === 0) {
750
+ throw new Error(":ko file expr requires a main expression");
533
751
  }
534
752
  const object = executableObject(state, mainParts.join(" "));
535
753
  fs.writeFileSync(path, encodeObject(object));
536
- const main = (mainParts.join(" ") || state.lastMain).trim();
754
+ const main = mainParts.join(" ").trim();
537
755
  return [`saved ${path} (${main})`];
538
756
  }
539
757
  case "load": {
540
- if (!arg) throw new Error(`${usagePrefix}load requires a file path`);
541
- if (arg.endsWith(".klib")) {
542
- const lib = loadLibrary(decodeLibrary(fs.readFileSync(arg)));
543
- mergeLibrary(state, lib, arg);
758
+ const { path: loadPath, loadAliases } = parseLoadArgs(arg, usagePrefix);
759
+ if (loadPath.endsWith(".klib")) {
760
+ const lib = loadLibrary(decodeLibrary(fs.readFileSync(loadPath)));
761
+ mergeLibrary(state, lib, loadPath, { loadAliases });
544
762
  } else {
545
763
  restoreCodes(state);
546
- const source = fs.readFileSync(arg, "utf8");
547
- const fullSource = [aliasPreamble(state), source].filter(Boolean).join("\n");
764
+ const source = fs.readFileSync(loadPath, "utf8");
765
+ const preamble = aliasPreamble(state);
766
+ const fullSource = [preamble, source].filter(Boolean).join("\n");
548
767
  const options = {
549
- source: arg,
768
+ source: loadPath,
550
769
  libraries: [stateLibrary(state)]
551
770
  };
552
- const lib = compileWithOptionalIdentity(fullSource, options);
553
- const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
554
- mergeLibrary(state, { ...lib, relAlias, meta }, arg);
771
+ try {
772
+ const lib = compileWithOptionalIdentity(fullSource, options);
773
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
774
+ mergeLibrary(state, { ...lib, relAlias, meta }, loadPath, { loadAliases });
775
+ } catch (error) {
776
+ throw remapError(error, preambleLineCount(preamble));
777
+ }
555
778
  }
556
- return [`loaded ${arg}`];
779
+ return [`loaded ${loadPath}`];
557
780
  }
558
781
  case "t": {
559
782
  if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
@@ -588,15 +811,17 @@ function helpText() {
588
811
  ":type name show type definition",
589
812
  ":codes list type aliases",
590
813
  ":rels list relation aliases",
591
- ":load file load .k source or .klib",
592
- ":klib file export state as .klib",
593
- ":ko file [expr] export executable .ko",
594
- ":export file [expr] export by extension: .klib or .ko",
814
+ ":load [--no-alias] file",
815
+ " load .k source or .klib",
816
+ ":klib file export state as a library",
817
+ ":ko file expr export executable .ko using expr as main",
595
818
  ":val print current value",
596
819
  ":reset clear state",
597
820
  ":help show this help",
598
821
  "",
599
- "Raw k definitions and expressions are still accepted as shorthand."
822
+ "Raw k input is compiled as a snippet on top of the current state.",
823
+ "A line ending with ';' plus only spaces closes the snippet.",
824
+ "Use '\\' to force continuation."
600
825
  ].join("\n");
601
826
  }
602
827
 
@@ -615,19 +840,50 @@ function startRepl() {
615
840
  console.log("k interpreter (.klib-backed). Type :help for commands.");
616
841
  rl.prompt();
617
842
  async function handleLine(line) {
618
- if (line.trim().endsWith("\\")) {
619
- buffer.push(line.replace(/\\\s*$/, ""));
843
+ if (lineHasExplicitContinuation(line)) {
844
+ buffer.push(lineForContinuation(line));
620
845
  rl.setPrompt(" ");
621
846
  if (!closed) rl.prompt();
622
847
  return;
623
848
  }
624
849
 
625
- const input = [...buffer, line].join("\n");
850
+ if (buffer.length === 0 && line.trim().startsWith(":")) {
851
+ rl.setPrompt("> ");
852
+ try {
853
+ for (const output of await evaluateInput(line, state)) {
854
+ console.log(output);
855
+ }
856
+ } catch (error) {
857
+ console.error(error.message || String(error));
858
+ }
859
+ if (!closed) rl.prompt();
860
+ return;
861
+ }
862
+
863
+ buffer.push(line);
864
+ const input = buffer.join("\n");
865
+ const explicitTerminated = lineTerminatesSnippet(line);
866
+ if (!explicitTerminated) {
867
+ try {
868
+ const analysis = analyzeRawSnippet(input);
869
+ if (analysis.kind !== "withMain") {
870
+ rl.setPrompt(" ");
871
+ if (!closed) rl.prompt();
872
+ return;
873
+ }
874
+ } catch (error) {
875
+ buffer.length = 0;
876
+ rl.setPrompt("> ");
877
+ console.error(error.message || String(error));
878
+ if (!closed) rl.prompt();
879
+ return;
880
+ }
881
+ }
882
+
626
883
  buffer.length = 0;
627
884
  rl.setPrompt("> ");
628
-
629
885
  try {
630
- for (const output of await evaluateInput(input, state)) {
886
+ for (const output of await runSnippet(input, state, { explicitTerminated })) {
631
887
  console.log(output);
632
888
  }
633
889
  } catch (error) {
@@ -649,13 +905,19 @@ if (isMainEntrypoint()) {
649
905
  }
650
906
 
651
907
  export {
908
+ aliasNames,
909
+ analyzeAcceptedSnippet,
910
+ analyzeRawSnippet,
652
911
  canonicalNames,
653
912
  completeInput,
654
913
  createCompleter,
655
914
  createState,
656
915
  evaluateInput,
916
+ explicitSnippetTerminated,
657
917
  helpText,
658
918
  isMainEntrypoint,
919
+ lineHasExplicitContinuation,
920
+ lineTerminatesSnippet,
659
921
  printValue,
660
922
  propertyListToFilter,
661
923
  valueToK