@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.
- package/.codex +0 -0
- package/AGENTS.md +35 -0
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +64 -0
- package/DOCS/BIG_PICTURE.md +4 -8
- package/DOCS/BINARY_CODEC_STATUS_AND_TODO.md +74 -0
- package/DOCS/CONVERGENCE.md +92 -0
- package/DOCS/DIVERGENCE_DETECTION.md +24 -0
- package/DOCS/Makefile +99 -0
- package/DOCS/OBJECT_FILE_AND_PATTERN.md +217 -0
- package/DOCS/PATTERNS.md +114 -0
- package/DOCS/PATTERNS_AND_TYPED_VALUES.md +665 -0
- package/DOCS/REPL.md +125 -125
- package/DOCS/TEXTUAL_VALUES.md +108 -0
- package/DOCS/TYPE_DERIVATION.md +230 -0
- package/DOCS/a.html +398 -0
- package/DOCS/book/06-values-in-memory.md +1085 -0
- package/DOCS/book/07-the-partial-function-abi.md +143 -0
- package/DOCS/book/08-operational-semantics-and-execution.md +201 -0
- package/DOCS/book/09-compiler-architecture.md +160 -0
- package/DOCS/book/10-from-ast-to-intermediate-representation.md +167 -0
- package/DOCS/book/14-canonical-serialization.md +176 -0
- package/DOCS/book/appendix-b-incorporating-external-predefined-types-and-functions.md +155 -0
- package/DOCS/book/overview.txt +68 -0
- package/NAT.k~ +452 -0
- package/README.md +71 -27
- package/codecs/BINARY_FORMAT.html +471 -0
- package/codecs/POLYMORPHIC_BINARY_FORMAT.html +355 -0
- package/codecs/README.md +8 -0
- package/codecs/pandoc.css +71 -0
- package/codecs/runtime/test-codec.mjs +89 -0
- package/ieee.klib +0 -0
- package/k-vscode/package-lock.json +4176 -0
- package/package.json +10 -13
- package/{codecs/k-pattern.mjs → patterns/from-k.mjs} +3 -3
- package/repl2.mjs +270 -51
- package/scripts/generate-dependency-graph.mjs +121 -0
- package/test-repl2.mjs +67 -9
- package/type_registry/registry.mjs~ +105 -0
- 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.
|
|
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": "
|
|
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": "
|
|
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 "
|
|
7
|
-
import { patternToPropertyList } from "
|
|
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
|
|
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", "
|
|
31
|
+
"codes", "rels", "val", "reset", "klib", "ko", "load",
|
|
32
32
|
"quit", "exit"
|
|
33
33
|
];
|
|
34
|
-
const PATH_COMMANDS = new Set(["
|
|
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([
|
|
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
|
|
392
|
-
const
|
|
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
|
|
410
|
-
const
|
|
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
|
|
428
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
519
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
584
|
-
":ko file
|
|
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
|
|
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
|
|
610
|
-
buffer.push(line
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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();
|