@fraczak/k 6.0.0 → 6.0.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.
Files changed (40) hide show
  1. package/.codex +0 -0
  2. package/AGENTS.md +35 -0
  3. package/CHANGELOG.md +48 -0
  4. package/CLAUDE.md +64 -0
  5. package/DOCS/BIG_PICTURE.md +4 -8
  6. package/DOCS/BINARY_CODEC_STATUS_AND_TODO.md +74 -0
  7. package/DOCS/CONVERGENCE.md +92 -0
  8. package/DOCS/DIVERGENCE_DETECTION.md +24 -0
  9. package/DOCS/Makefile +99 -0
  10. package/DOCS/OBJECT_FILE_AND_PATTERN.md +217 -0
  11. package/DOCS/PATTERNS.md +114 -0
  12. package/DOCS/PATTERNS_AND_TYPED_VALUES.md +665 -0
  13. package/DOCS/REPL.md +125 -125
  14. package/DOCS/TEXTUAL_VALUES.md +108 -0
  15. package/DOCS/TYPE_DERIVATION.md +230 -0
  16. package/DOCS/a.html +398 -0
  17. package/DOCS/book/06-values-in-memory.md +1085 -0
  18. package/DOCS/book/07-the-partial-function-abi.md +143 -0
  19. package/DOCS/book/08-operational-semantics-and-execution.md +201 -0
  20. package/DOCS/book/09-compiler-architecture.md +160 -0
  21. package/DOCS/book/10-from-ast-to-intermediate-representation.md +167 -0
  22. package/DOCS/book/14-canonical-serialization.md +176 -0
  23. package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +155 -0
  24. package/DOCS/book/overview.txt +68 -0
  25. package/NAT.k~ +452 -0
  26. package/README.md +71 -27
  27. package/codecs/BINARY_FORMAT.html +471 -0
  28. package/codecs/POLYMORPHIC_BINARY_FORMAT.html +355 -0
  29. package/codecs/README.md +8 -0
  30. package/codecs/pandoc.css +71 -0
  31. package/codecs/runtime/test-codec.mjs +89 -0
  32. package/ieee.klib +0 -0
  33. package/k-vscode/package-lock.json +4176 -0
  34. package/package.json +10 -13
  35. package/{codecs/k-pattern.mjs → patterns/from-k.mjs} +3 -3
  36. package/repl2.mjs +270 -51
  37. package/scripts/generate-dependency-graph.mjs +121 -0
  38. package/test-repl2.mjs +67 -9
  39. package/type_registry/registry.mjs~ +105 -0
  40. package/type_registry/typeParser.jison~ +132 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.0.0",
3
+ "version": "6.0.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": {
@@ -10,22 +10,19 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/fraczak/k.git"
14
- },
15
- "publishConfig": {
16
- "access": "public"
13
+ "url": "https://github.com/fraczak/k.git"
17
14
  },
18
15
  "author": "Wojciech Fraczak",
19
16
  "license": "ISC",
20
17
  "bin": {
21
- "k": "k.mjs",
22
- "k-compile-object": "objects/compile.mjs",
23
- "k-decompile-object": "objects/decompile.mjs",
24
- "k-parse": "codecs/k-parse.mjs",
25
- "k-pattern": "codecs/k-pattern.mjs",
26
- "k-print": "codecs/k-print.mjs",
27
- "k-repl": "repl2.mjs",
28
- "k-repl2": "repl2.mjs"
18
+ "k": "./k.mjs",
19
+ "k-compile-object": "./objects/compile.mjs",
20
+ "k-decompile-object": "./objects/decompile.mjs",
21
+ "k-parse": "./codecs/k-parse.mjs",
22
+ "k-pattern": "./patterns/from-k.mjs",
23
+ "k-print": "./codecs/k-print.mjs",
24
+ "k-repl": "./repl2.mjs",
25
+ "k-repl2": "./repl2.mjs"
29
26
  },
30
27
  "devDependencies": {
31
28
  "jiwson": "^0.1.2"
@@ -3,14 +3,14 @@
3
3
  import fs from "node:fs";
4
4
  import { argv, exit, stdin, stdout } from "node:process";
5
5
  import k from "../index.mjs";
6
- import { exportPatternGraph } from "./runtime/codec.mjs";
7
- import { patternToPropertyList } from "./runtime/pattern-json.mjs";
6
+ import { exportPatternGraph } from "../codecs/runtime/codec.mjs";
7
+ import { patternToPropertyList } from "../codecs/runtime/pattern-json.mjs";
8
8
 
9
9
  function usage(prog) {
10
10
  console.error(`Usage: ${prog} [script-or-file]`);
11
11
  console.error(" Read a k script from a file argument or stdin.");
12
12
  console.error(" The main expression must be a filter or a type name.");
13
- console.error(" Output is the canonical JSON pattern array.");
13
+ console.error(" Output is the canonical property-list pattern array.");
14
14
  console.error(" Example:");
15
15
  console.error(` cat pattern.k | ${prog}`);
16
16
  console.error(` ${prog} '$ bits = < {} _, bits 0, bits 1 >; ?< {} nil, { $bits car, X cdr } cons > = X'`);
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,7 +139,7 @@ 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
 
@@ -183,15 +220,18 @@ function savedLibrary(state) {
183
220
  function executableObject(state, mainExpression) {
184
221
  const main = (mainExpression || state.lastMain || "").trim();
185
222
  if (!main) {
186
- throw new Error(":ko requires a main expression unless one has already been evaluated or defined");
223
+ throw new Error(":ko file expr requires a main expression unless one has already been evaluated or defined");
187
224
  }
188
225
 
189
226
  restoreCodes(state);
227
+ const preamble = aliasPreamble(state);
190
228
  try {
191
- return compileObject([aliasPreamble(state), main].filter(Boolean).join("\n"), {
229
+ return compileObject([preamble, main].filter(Boolean).join("\n"), {
192
230
  source: "<repl>",
193
231
  libraries: [stateLibrary(state)]
194
232
  });
233
+ } catch (error) {
234
+ throw remapError(error, preambleLineCount(preamble));
195
235
  } finally {
196
236
  restoreCodes(state);
197
237
  }
@@ -272,6 +312,13 @@ function canonicalNames(state) {
272
312
  ])].filter((name) => name.startsWith("@")).sort();
273
313
  }
274
314
 
315
+ function aliasNames(state) {
316
+ return [...new Set([
317
+ ...Object.keys(state.typeAliases),
318
+ ...Object.keys(state.relAliases)
319
+ ])].sort();
320
+ }
321
+
275
322
  function expandHome(input) {
276
323
  if (input === "~") return os.homedir();
277
324
  if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
@@ -303,6 +350,19 @@ function completeCanonical(line, state) {
303
350
  return [matches, line];
304
351
  }
305
352
 
353
+ function completeIdentifier(line, state) {
354
+ const match = line.match(/^(.*?)(\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*)$/);
355
+ if (!match) return [[], line];
356
+ const [, prefix, partial] = match;
357
+ const isTypeToken = partial.startsWith("$");
358
+ const barePartial = isTypeToken ? partial.slice(1) : partial;
359
+ const names = isTypeToken ? Object.keys(state.typeAliases).sort() : aliasNames(state);
360
+ const matches = names
361
+ .filter((name) => name.startsWith(barePartial))
362
+ .map((name) => `${prefix}${isTypeToken ? "$" : ""}${name}`);
363
+ return [matches, line];
364
+ }
365
+
306
366
  function completePath(line, tokenStart) {
307
367
  const token = line.slice(tokenStart);
308
368
  const quote = token[0] === "\"" || token[0] === "'" ? token[0] : "";
@@ -359,6 +419,10 @@ function completeCommandArgument(line, state) {
359
419
  return completeCanonical(line, state);
360
420
  }
361
421
 
422
+ if (/\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*$/.test(line)) {
423
+ return completeIdentifier(line, state);
424
+ }
425
+
362
426
  return [[], line];
363
427
  }
364
428
 
@@ -369,6 +433,9 @@ function completeInput(line, state) {
369
433
  if (/@[A-Za-z0-9_?!+-]*$/.test(line)) {
370
434
  return completeCanonical(line, state);
371
435
  }
436
+ if (/\$?[A-Za-z0-9_+-][A-Za-z0-9_?!+-]*$/.test(line)) {
437
+ return completeIdentifier(line, state);
438
+ }
372
439
  return [[], line];
373
440
  }
374
441
 
@@ -376,10 +443,89 @@ function createCompleter(state) {
376
443
  return (line) => completeInput(line, state);
377
444
  }
378
445
 
446
+ function isMainEntrypoint(entryArg = argv[1]) {
447
+ if (!entryArg) return false;
448
+ try {
449
+ return fs.realpathSync(entryArg) === fs.realpathSync(fileURLToPath(import.meta.url));
450
+ } catch {
451
+ return false;
452
+ }
453
+ }
454
+
379
455
  function ensureSemicolon(source) {
380
456
  return source.trim().endsWith(";") ? source : `${source};`;
381
457
  }
382
458
 
459
+ function isEofParseError(error) {
460
+ return /got 'EOF'|Unexpected end of input/.test(error.message || "");
461
+ }
462
+
463
+ function lineForContinuation(line) {
464
+ return line.replace(/\\\s*$/, "");
465
+ }
466
+
467
+ function lineHasExplicitContinuation(line) {
468
+ return /\\\s*$/.test(line);
469
+ }
470
+
471
+ function lineTerminatesSnippet(line) {
472
+ return /;\s*$/.test(line);
473
+ }
474
+
475
+ function explicitSnippetTerminated(source) {
476
+ const lines = source.split("\n");
477
+ const lastLine = lines[lines.length - 1] || "";
478
+ return lineTerminatesSnippet(lastLine);
479
+ }
480
+
481
+ function analyzeRawSnippet(source) {
482
+ try {
483
+ const parsed = parse(source);
484
+ return { kind: "withMain", parsed };
485
+ } catch (error) {
486
+ if (!isEofParseError(error)) throw error;
487
+ }
488
+
489
+ return { kind: "incomplete", parsed: null };
490
+ }
491
+
492
+ function analyzeAcceptedSnippet(source, explicitTerminated = explicitSnippetTerminated(source)) {
493
+ try {
494
+ const parsed = parse(source);
495
+ return { kind: "withMain", parsed };
496
+ } catch (error) {
497
+ if (!explicitTerminated) {
498
+ if (isEofParseError(error)) return { kind: "incomplete", parsed: null };
499
+ throw error;
500
+ }
501
+ }
502
+
503
+ const parsed = parse(`${source}\n()`);
504
+ return { kind: "definitionsOnly", parsed };
505
+ }
506
+
507
+ function compileSnippetArtifacts(source, state, sourceName = "<repl>") {
508
+ restoreCodes(state);
509
+ const preamble = aliasPreamble(state);
510
+ const fullSource = [preamble, source].filter(Boolean).join("\n");
511
+ const options = {
512
+ source: sourceName,
513
+ libraries: [stateLibrary(state)]
514
+ };
515
+ try {
516
+ const lib = compileWithOptionalIdentity(fullSource, options);
517
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
518
+ const annotated = annotateWithOptionalIdentity(fullSource, options);
519
+ return {
520
+ annotated,
521
+ lib: { ...lib, relAlias, meta },
522
+ lineOffset: preambleLineCount(preamble)
523
+ };
524
+ } catch (error) {
525
+ throw remapError(error, preambleLineCount(preamble));
526
+ }
527
+ }
528
+
383
529
  async function defineType(input, state) {
384
530
  const typeMatch = input.match(TYPE_DEF_RE);
385
531
  if (!typeMatch) {
@@ -388,8 +534,14 @@ async function defineType(input, state) {
388
534
 
389
535
  const name = typeMatch[1];
390
536
  restoreCodes(state);
391
- const source = [aliasPreamble(state, name), ensureSemicolon(input), "()"].filter(Boolean).join("\n");
392
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
537
+ const preamble = aliasPreamble(state, name);
538
+ const source = [preamble, ensureSemicolon(input), "()"].filter(Boolean).join("\n");
539
+ let annotated;
540
+ try {
541
+ annotated = annotate(source, { libraries: [stateLibrary(state)] });
542
+ } catch (error) {
543
+ throw remapError(error, preambleLineCount(preamble));
544
+ }
393
545
  const hash = annotated.representatives[name];
394
546
  if (!hash) throw new Error(`Type definition did not produce an alias for '${name}'`);
395
547
  state.codes = codes.dump();
@@ -406,8 +558,14 @@ async function defineRelation(input, state) {
406
558
 
407
559
  const name = relMatch[1];
408
560
  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)] }));
561
+ const preamble = aliasPreamble(state, name);
562
+ const source = [preamble, ensureSemicolon(input), name].filter(Boolean).join("\n");
563
+ let lib;
564
+ try {
565
+ lib = hydrateObject(compileLibrary(source, { source: "<repl>", libraries: [stateLibrary(state)] }));
566
+ } catch (error) {
567
+ throw remapError(error, preambleLineCount(preamble));
568
+ }
411
569
  const hash = lib.relAlias?.[name];
412
570
  if (!hash || !lib.rels?.[hash]) throw new Error(`Relation definition did not produce an alias for '${name}'`);
413
571
  state.codes = codes.dump();
@@ -424,16 +582,52 @@ async function runExpression(input, state) {
424
582
  if (!expression) throw new Error(":run requires an expression");
425
583
 
426
584
  restoreCodes(state);
427
- const source = [aliasPreamble(state), expression].filter(Boolean).join("\n");
428
- const annotated = annotate(source, { libraries: [stateLibrary(state)] });
585
+ const preamble = aliasPreamble(state);
586
+ const source = [preamble, expression].filter(Boolean).join("\n");
587
+ let annotated;
588
+ try {
589
+ annotated = annotate(source, { libraries: [stateLibrary(state)] });
590
+ } catch (error) {
591
+ throw remapError(error, preambleLineCount(preamble));
592
+ }
429
593
  const mainRel = annotated.rels.__main__;
430
594
  run.defs = annotated;
431
- state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
595
+ try {
596
+ state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
597
+ } catch (error) {
598
+ throw remapError(error, preambleLineCount(preamble));
599
+ }
432
600
  state.lastMain = expression;
433
601
  restoreCodes(state);
434
602
  return [printValue(state.value)];
435
603
  }
436
604
 
605
+ async function runSnippet(input, state, options = {}) {
606
+ const snippet = input.trim();
607
+ if (!snippet) return [];
608
+
609
+ const explicitTerminated = options.explicitTerminated ?? explicitSnippetTerminated(input);
610
+ const analysis = analyzeAcceptedSnippet(snippet, explicitTerminated);
611
+ const { annotated, lib, lineOffset } = compileSnippetArtifacts(snippet, state);
612
+ mergeLibrary(state, lib, "<repl>");
613
+
614
+ if (analysis.kind === "definitionsOnly") {
615
+ restoreCodes(state);
616
+ return [];
617
+ }
618
+
619
+ const mainRel = annotated.rels.__main__;
620
+ run.defs = annotated;
621
+ try {
622
+ state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
623
+ } catch (error) {
624
+ throw remapError(error, lineOffset);
625
+ }
626
+ state.lastMain = snippet;
627
+ restoreCodes(state);
628
+ return [printValue(state.value)];
629
+ }
630
+
437
631
  async function evaluateInput(input, state) {
438
632
  const line = input.trim();
439
633
  if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
@@ -442,9 +636,7 @@ async function evaluateInput(input, state) {
442
636
  return evaluateCommand(line, state);
443
637
  }
444
638
 
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);
639
+ return runSnippet(input, state);
448
640
  }
449
641
 
450
642
  function parseCommand(line) {
@@ -494,37 +686,20 @@ async function evaluateCommand(line, state) {
494
686
  Object.assign(state, fresh);
495
687
  return ["reset"];
496
688
  }
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
689
  case "klib": {
503
690
  if (!arg) throw new Error(`${usagePrefix}klib requires a file path`);
504
691
  fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
505
692
  return [`saved ${arg}`];
506
693
  }
507
694
  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`);
695
+ if (!arg) throw new Error(`${usagePrefix}ko requires a file path and main expression`);
517
696
  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");
697
+ if (mainParts.length === 0) {
698
+ throw new Error(":ko file expr requires a main expression");
524
699
  }
525
700
  const object = executableObject(state, mainParts.join(" "));
526
701
  fs.writeFileSync(path, encodeObject(object));
527
- const main = (mainParts.join(" ") || state.lastMain).trim();
702
+ const main = mainParts.join(" ").trim();
528
703
  return [`saved ${path} (${main})`];
529
704
  }
530
705
  case "load": {
@@ -535,14 +710,19 @@ async function evaluateCommand(line, state) {
535
710
  } else {
536
711
  restoreCodes(state);
537
712
  const source = fs.readFileSync(arg, "utf8");
538
- const fullSource = [aliasPreamble(state), source].filter(Boolean).join("\n");
713
+ const preamble = aliasPreamble(state);
714
+ const fullSource = [preamble, source].filter(Boolean).join("\n");
539
715
  const options = {
540
716
  source: arg,
541
717
  libraries: [stateLibrary(state)]
542
718
  };
543
- const lib = compileWithOptionalIdentity(fullSource, options);
544
- const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
545
- mergeLibrary(state, { ...lib, relAlias, meta }, arg);
719
+ try {
720
+ const lib = compileWithOptionalIdentity(fullSource, options);
721
+ const { relAlias, meta } = libraryOriginsFromSource(source, fullSource, lib, options);
722
+ mergeLibrary(state, { ...lib, relAlias, meta }, arg);
723
+ } catch (error) {
724
+ throw remapError(error, preambleLineCount(preamble));
725
+ }
546
726
  }
547
727
  return [`loaded ${arg}`];
548
728
  }
@@ -580,14 +760,15 @@ function helpText() {
580
760
  ":codes list type aliases",
581
761
  ":rels list relation aliases",
582
762
  ":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",
763
+ ":klib file export state as a library",
764
+ ":ko file expr export executable .ko using expr as main",
586
765
  ":val print current value",
587
766
  ":reset clear state",
588
767
  ":help show this help",
589
768
  "",
590
- "Raw k definitions and expressions are still accepted as shorthand."
769
+ "Raw k input is compiled as a snippet on top of the current state.",
770
+ "A line ending with ';' plus only spaces closes the snippet.",
771
+ "Use '\\' to force continuation."
591
772
  ].join("\n");
592
773
  }
593
774
 
@@ -606,19 +787,50 @@ function startRepl() {
606
787
  console.log("k interpreter (.klib-backed). Type :help for commands.");
607
788
  rl.prompt();
608
789
  async function handleLine(line) {
609
- if (line.trim().endsWith("\\")) {
610
- buffer.push(line.replace(/\\\s*$/, ""));
790
+ if (lineHasExplicitContinuation(line)) {
791
+ buffer.push(lineForContinuation(line));
611
792
  rl.setPrompt(" ");
612
793
  if (!closed) rl.prompt();
613
794
  return;
614
795
  }
615
796
 
616
- const input = [...buffer, line].join("\n");
797
+ if (buffer.length === 0 && line.trim().startsWith(":")) {
798
+ rl.setPrompt("> ");
799
+ try {
800
+ for (const output of await evaluateInput(line, state)) {
801
+ console.log(output);
802
+ }
803
+ } catch (error) {
804
+ console.error(error.message || String(error));
805
+ }
806
+ if (!closed) rl.prompt();
807
+ return;
808
+ }
809
+
810
+ buffer.push(line);
811
+ const input = buffer.join("\n");
812
+ const explicitTerminated = lineTerminatesSnippet(line);
813
+ if (!explicitTerminated) {
814
+ try {
815
+ const analysis = analyzeRawSnippet(input);
816
+ if (analysis.kind !== "withMain") {
817
+ rl.setPrompt(" ");
818
+ if (!closed) rl.prompt();
819
+ return;
820
+ }
821
+ } catch (error) {
822
+ buffer.length = 0;
823
+ rl.setPrompt("> ");
824
+ console.error(error.message || String(error));
825
+ if (!closed) rl.prompt();
826
+ return;
827
+ }
828
+ }
829
+
617
830
  buffer.length = 0;
618
831
  rl.setPrompt("> ");
619
-
620
832
  try {
621
- for (const output of await evaluateInput(input, state)) {
833
+ for (const output of await runSnippet(input, state, { explicitTerminated })) {
622
834
  console.log(output);
623
835
  }
624
836
  } catch (error) {
@@ -635,17 +847,24 @@ function startRepl() {
635
847
  });
636
848
  }
637
849
 
638
- if (argv[1] && fileURLToPath(import.meta.url) === argv[1]) {
850
+ if (isMainEntrypoint()) {
639
851
  startRepl();
640
852
  }
641
853
 
642
854
  export {
855
+ aliasNames,
856
+ analyzeAcceptedSnippet,
857
+ analyzeRawSnippet,
643
858
  canonicalNames,
644
859
  completeInput,
645
860
  createCompleter,
646
861
  createState,
647
862
  evaluateInput,
863
+ explicitSnippetTerminated,
648
864
  helpText,
865
+ isMainEntrypoint,
866
+ lineHasExplicitContinuation,
867
+ lineTerminatesSnippet,
649
868
  printValue,
650
869
  propertyListToFilter,
651
870
  valueToK
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { execSync } from "node:child_process";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const root = path.resolve(__dirname, "..");
11
+ const outPath = path.join(root, "DOCS", "dependency-graph.md");
12
+
13
+ function getTrackedMjsFiles() {
14
+ const raw = execSync("git ls-files '*.mjs'", {
15
+ cwd: root,
16
+ encoding: "utf8"
17
+ });
18
+
19
+ const all = raw
20
+ .split("\n")
21
+ .map((s) => s.trim())
22
+ .filter(Boolean);
23
+
24
+ return all.filter((relPath) => {
25
+ if (relPath.startsWith("codecs/")) return true;
26
+ if (relPath.includes("/")) return false;
27
+ if (relPath.startsWith("test")) return false;
28
+ if (relPath.includes("-test")) return false;
29
+ return true;
30
+ });
31
+ }
32
+
33
+ function parseLocalImports(absPath) {
34
+ const source = fs.readFileSync(absPath, "utf8");
35
+ const regex = /^\s*import\s+[^;]*?from\s+["']([^"']+)["'];?\s*$/gm;
36
+ const imports = [];
37
+ let match;
38
+ while ((match = regex.exec(source)) !== null) {
39
+ const specifier = match[1];
40
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
41
+ imports.push(specifier);
42
+ }
43
+ }
44
+ return imports;
45
+ }
46
+
47
+ function toNodeId(relPath) {
48
+ return relPath.replace(/[^a-zA-Z0-9]/g, "_");
49
+ }
50
+
51
+ function makeBar(sizeKb) {
52
+ const units = Math.max(1, Math.min(20, Math.round(sizeKb)));
53
+ return "█".repeat(units);
54
+ }
55
+
56
+ function renderGraph(files, sizesKb, edges) {
57
+ const lines = [];
58
+ lines.push("# Project Dependency Graph");
59
+ lines.push("");
60
+ lines.push("_Auto-generated by scripts/generate-dependency-graph.mjs._");
61
+ lines.push("");
62
+ lines.push("```mermaid");
63
+ lines.push("graph TD");
64
+ lines.push(" classDef default fill:#fff,stroke:#333,stroke-width:2px,color:#000;");
65
+ lines.push("");
66
+
67
+ for (const relPath of files) {
68
+ const id = toNodeId(relPath);
69
+ const sizeKb = sizesKb[relPath];
70
+ const bar = makeBar(sizeKb);
71
+ lines.push(` ${id}[\"${relPath} (${sizeKb.toFixed(1)} KB)<br/>${bar}\"]`);
72
+ }
73
+
74
+ if (edges.length > 0) {
75
+ lines.push("");
76
+ }
77
+
78
+ for (const { src, dst } of edges) {
79
+ lines.push(` ${toNodeId(src)} --> ${toNodeId(dst)}`);
80
+ }
81
+
82
+ lines.push("```");
83
+ lines.push("");
84
+ return lines.join("\n");
85
+ }
86
+
87
+ function main() {
88
+ const files = getTrackedMjsFiles().sort();
89
+ const fileSet = new Set(files);
90
+
91
+ const sizesKb = {};
92
+ for (const relPath of files) {
93
+ const absPath = path.join(root, relPath);
94
+ const bytes = fs.statSync(absPath).size;
95
+ sizesKb[relPath] = bytes / 1024;
96
+ }
97
+
98
+ const edgeKeys = new Set();
99
+ const edges = [];
100
+ for (const relPath of files) {
101
+ const absPath = path.join(root, relPath);
102
+ const imports = parseLocalImports(absPath);
103
+
104
+ for (const specifier of imports) {
105
+ const resolved = path.normalize(path.join(path.dirname(relPath), specifier));
106
+ if (!resolved.endsWith(".mjs")) continue;
107
+ if (!fileSet.has(resolved)) continue;
108
+ const key = `${relPath}->${resolved}`;
109
+ if (edgeKeys.has(key)) continue;
110
+ edgeKeys.add(key);
111
+ edges.push({ src: relPath, dst: resolved });
112
+ }
113
+ }
114
+
115
+ const graph = renderGraph(files, sizesKb, edges);
116
+ fs.writeFileSync(outPath, graph, "utf8");
117
+ console.log(`Wrote ${path.relative(root, outPath)}`);
118
+ console.log(`Nodes: ${files.length}, Edges: ${edges.length}`);
119
+ }
120
+
121
+ main();