@fraczak/k 6.0.1 → 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/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/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/README.md +71 -27
- package/codecs/README.md +8 -0
- package/codecs/pandoc.css +71 -0
- package/codecs/runtime/test-codec.mjs +89 -0
- package/package.json +2 -2
- package/{codecs/k-pattern.mjs → patterns/from-k.mjs} +3 -3
- package/repl2.mjs +259 -50
- package/scripts/generate-dependency-graph.mjs +121 -0
- package/test-repl2.mjs +55 -8
- package/DOCS/book-combined.html +0 -3798
- package/DOCS/book-combined.md +0 -3745
- package/DOCS/book-combined.pdf +0 -0
- package/DOCS/book-combined.tex +0 -5682
- package/DOCS/book.html +0 -65
- package/DOCS/book.pdf +0 -0
- package/DOCS/book.tex +0 -150
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test the binary codec with basic k types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { deriveClosedPattern } from './codec.mjs';
|
|
7
|
+
import { encodeToWire, decodeWire } from './prefix-codec.mjs';
|
|
8
|
+
import { patternToPropertyList } from './pattern-json.mjs';
|
|
9
|
+
import { Product, Variant } from '../../Value.mjs';
|
|
10
|
+
import hash from '../../hash.mjs';
|
|
11
|
+
|
|
12
|
+
// Type repository
|
|
13
|
+
const types = {};
|
|
14
|
+
|
|
15
|
+
// Define unit type: {}
|
|
16
|
+
const unitTypeDef = { code: 'product', product: {} };
|
|
17
|
+
const unitTypeStr = '$C0={};';
|
|
18
|
+
const unitTypeName = hash(unitTypeStr);
|
|
19
|
+
types[unitTypeName] = unitTypeDef;
|
|
20
|
+
|
|
21
|
+
console.log('Unit type name:', unitTypeName);
|
|
22
|
+
|
|
23
|
+
// Define bool type: <{} false, {} true>
|
|
24
|
+
const boolTypeDef = {
|
|
25
|
+
code: 'union',
|
|
26
|
+
union: {
|
|
27
|
+
false: unitTypeName,
|
|
28
|
+
true: unitTypeName
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const boolTypeStr = `$C0=<${unitTypeName}"false",${unitTypeName}"true">;`;
|
|
32
|
+
const boolTypeName = hash(boolTypeStr);
|
|
33
|
+
types[boolTypeName] = boolTypeDef;
|
|
34
|
+
|
|
35
|
+
console.log('Bool type name:', boolTypeName);
|
|
36
|
+
|
|
37
|
+
// Resolver function
|
|
38
|
+
function resolveType(typeName) {
|
|
39
|
+
if (!types[typeName]) {
|
|
40
|
+
throw new Error(`Unknown type: ${typeName}`);
|
|
41
|
+
}
|
|
42
|
+
return types[typeName];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test 1: Unit value
|
|
46
|
+
console.log('\n=== Test 1: Unit ===');
|
|
47
|
+
const unitValue = new Product({});
|
|
48
|
+
console.log('Encoding unit:', unitValue.toString());
|
|
49
|
+
|
|
50
|
+
const unitPattern = patternToPropertyList(deriveClosedPattern(unitTypeName, unitTypeDef, resolveType));
|
|
51
|
+
const unitEncoded = encodeToWire(unitValue, unitPattern);
|
|
52
|
+
console.log('Encoded bytes:', unitEncoded.length, 'bytes');
|
|
53
|
+
console.log('Hex:', unitEncoded.toString('hex'));
|
|
54
|
+
|
|
55
|
+
const unitDecoded = decodeWire(unitEncoded);
|
|
56
|
+
console.log('Decoded value:', unitDecoded.value.toString());
|
|
57
|
+
console.log('Match:', unitDecoded.value instanceof Product &&
|
|
58
|
+
Object.keys(unitDecoded.value.product).length === 0);
|
|
59
|
+
|
|
60
|
+
// Test 2: Bool false
|
|
61
|
+
console.log('\n=== Test 2: Bool (false) ===');
|
|
62
|
+
const falseValue = new Variant('false', new Product({}));
|
|
63
|
+
console.log('Encoding false:', falseValue.toString());
|
|
64
|
+
|
|
65
|
+
const boolPattern = patternToPropertyList(deriveClosedPattern(boolTypeName, boolTypeDef, resolveType));
|
|
66
|
+
const falseEncoded = encodeToWire(falseValue, boolPattern);
|
|
67
|
+
console.log('Encoded bytes:', falseEncoded.length, 'bytes');
|
|
68
|
+
console.log('Hex:', falseEncoded.toString('hex'));
|
|
69
|
+
|
|
70
|
+
const falseDecoded = decodeWire(falseEncoded);
|
|
71
|
+
console.log('Decoded value:', falseDecoded.value.toString());
|
|
72
|
+
console.log('Match:', falseDecoded.value instanceof Variant &&
|
|
73
|
+
falseDecoded.value.tag === 'false');
|
|
74
|
+
|
|
75
|
+
// Test 3: Bool true
|
|
76
|
+
console.log('\n=== Test 3: Bool (true) ===');
|
|
77
|
+
const trueValue = new Variant('true', new Product({}));
|
|
78
|
+
console.log('Encoding true:', trueValue.toString());
|
|
79
|
+
|
|
80
|
+
const trueEncoded = encodeToWire(trueValue, boolPattern);
|
|
81
|
+
console.log('Encoded bytes:', trueEncoded.length, 'bytes');
|
|
82
|
+
console.log('Hex:', trueEncoded.toString('hex'));
|
|
83
|
+
|
|
84
|
+
const trueDecoded = decodeWire(trueEncoded);
|
|
85
|
+
console.log('Decoded value:', trueDecoded.value.toString());
|
|
86
|
+
console.log('Match:', trueDecoded.value instanceof Variant &&
|
|
87
|
+
trueDecoded.value.tag === 'true');
|
|
88
|
+
|
|
89
|
+
console.log('\n=== All tests complete ===');
|
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": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"k-compile-object": "./objects/compile.mjs",
|
|
20
20
|
"k-decompile-object": "./objects/decompile.mjs",
|
|
21
21
|
"k-parse": "./codecs/k-parse.mjs",
|
|
22
|
-
"k-pattern": "./
|
|
22
|
+
"k-pattern": "./patterns/from-k.mjs",
|
|
23
23
|
"k-print": "./codecs/k-print.mjs",
|
|
24
24
|
"k-repl": "./repl2.mjs",
|
|
25
25
|
"k-repl2": "./repl2.mjs"
|
|
@@ -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
|
|
|
@@ -389,6 +456,76 @@ function ensureSemicolon(source) {
|
|
|
389
456
|
return source.trim().endsWith(";") ? source : `${source};`;
|
|
390
457
|
}
|
|
391
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
|
+
|
|
392
529
|
async function defineType(input, state) {
|
|
393
530
|
const typeMatch = input.match(TYPE_DEF_RE);
|
|
394
531
|
if (!typeMatch) {
|
|
@@ -397,8 +534,14 @@ async function defineType(input, state) {
|
|
|
397
534
|
|
|
398
535
|
const name = typeMatch[1];
|
|
399
536
|
restoreCodes(state);
|
|
400
|
-
const
|
|
401
|
-
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
|
+
}
|
|
402
545
|
const hash = annotated.representatives[name];
|
|
403
546
|
if (!hash) throw new Error(`Type definition did not produce an alias for '${name}'`);
|
|
404
547
|
state.codes = codes.dump();
|
|
@@ -415,8 +558,14 @@ async function defineRelation(input, state) {
|
|
|
415
558
|
|
|
416
559
|
const name = relMatch[1];
|
|
417
560
|
restoreCodes(state);
|
|
418
|
-
const
|
|
419
|
-
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
|
+
}
|
|
420
569
|
const hash = lib.relAlias?.[name];
|
|
421
570
|
if (!hash || !lib.rels?.[hash]) throw new Error(`Relation definition did not produce an alias for '${name}'`);
|
|
422
571
|
state.codes = codes.dump();
|
|
@@ -433,16 +582,52 @@ async function runExpression(input, state) {
|
|
|
433
582
|
if (!expression) throw new Error(":run requires an expression");
|
|
434
583
|
|
|
435
584
|
restoreCodes(state);
|
|
436
|
-
const
|
|
437
|
-
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
|
+
}
|
|
438
593
|
const mainRel = annotated.rels.__main__;
|
|
439
594
|
run.defs = annotated;
|
|
440
|
-
|
|
595
|
+
try {
|
|
596
|
+
state.value = run(codes.find, mainRel.def, state.value, mainRel.typePatternGraph);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
throw remapError(error, preambleLineCount(preamble));
|
|
599
|
+
}
|
|
441
600
|
state.lastMain = expression;
|
|
442
601
|
restoreCodes(state);
|
|
443
602
|
return [printValue(state.value)];
|
|
444
603
|
}
|
|
445
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
|
+
|
|
446
631
|
async function evaluateInput(input, state) {
|
|
447
632
|
const line = input.trim();
|
|
448
633
|
if (line === "" || line.startsWith("#") || line.startsWith("//") || line.startsWith("--")) return [];
|
|
@@ -451,9 +636,7 @@ async function evaluateInput(input, state) {
|
|
|
451
636
|
return evaluateCommand(line, state);
|
|
452
637
|
}
|
|
453
638
|
|
|
454
|
-
|
|
455
|
-
if (input.match(REL_DEF_RE)) return defineRelation(input, state);
|
|
456
|
-
return runExpression(input, state);
|
|
639
|
+
return runSnippet(input, state);
|
|
457
640
|
}
|
|
458
641
|
|
|
459
642
|
function parseCommand(line) {
|
|
@@ -503,37 +686,20 @@ async function evaluateCommand(line, state) {
|
|
|
503
686
|
Object.assign(state, fresh);
|
|
504
687
|
return ["reset"];
|
|
505
688
|
}
|
|
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
689
|
case "klib": {
|
|
512
690
|
if (!arg) throw new Error(`${usagePrefix}klib requires a file path`);
|
|
513
691
|
fs.writeFileSync(arg, encodeLibrary(savedLibrary(state)));
|
|
514
692
|
return [`saved ${arg}`];
|
|
515
693
|
}
|
|
516
694
|
case "ko": {
|
|
517
|
-
if (!arg) throw new Error(`${usagePrefix}ko requires a file path`);
|
|
695
|
+
if (!arg) throw new Error(`${usagePrefix}ko requires a file path and main expression`);
|
|
518
696
|
const [path, ...mainParts] = arg.split(/\s+/);
|
|
519
|
-
|
|
520
|
-
|
|
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`);
|
|
526
|
-
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");
|
|
697
|
+
if (mainParts.length === 0) {
|
|
698
|
+
throw new Error(":ko file expr requires a main expression");
|
|
533
699
|
}
|
|
534
700
|
const object = executableObject(state, mainParts.join(" "));
|
|
535
701
|
fs.writeFileSync(path, encodeObject(object));
|
|
536
|
-
const main =
|
|
702
|
+
const main = mainParts.join(" ").trim();
|
|
537
703
|
return [`saved ${path} (${main})`];
|
|
538
704
|
}
|
|
539
705
|
case "load": {
|
|
@@ -544,14 +710,19 @@ async function evaluateCommand(line, state) {
|
|
|
544
710
|
} else {
|
|
545
711
|
restoreCodes(state);
|
|
546
712
|
const source = fs.readFileSync(arg, "utf8");
|
|
547
|
-
const
|
|
713
|
+
const preamble = aliasPreamble(state);
|
|
714
|
+
const fullSource = [preamble, source].filter(Boolean).join("\n");
|
|
548
715
|
const options = {
|
|
549
716
|
source: arg,
|
|
550
717
|
libraries: [stateLibrary(state)]
|
|
551
718
|
};
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
+
}
|
|
555
726
|
}
|
|
556
727
|
return [`loaded ${arg}`];
|
|
557
728
|
}
|
|
@@ -589,14 +760,15 @@ function helpText() {
|
|
|
589
760
|
":codes list type aliases",
|
|
590
761
|
":rels list relation aliases",
|
|
591
762
|
":load file load .k source or .klib",
|
|
592
|
-
":klib file export state as
|
|
593
|
-
":ko file
|
|
594
|
-
":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",
|
|
595
765
|
":val print current value",
|
|
596
766
|
":reset clear state",
|
|
597
767
|
":help show this help",
|
|
598
768
|
"",
|
|
599
|
-
"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."
|
|
600
772
|
].join("\n");
|
|
601
773
|
}
|
|
602
774
|
|
|
@@ -615,19 +787,50 @@ function startRepl() {
|
|
|
615
787
|
console.log("k interpreter (.klib-backed). Type :help for commands.");
|
|
616
788
|
rl.prompt();
|
|
617
789
|
async function handleLine(line) {
|
|
618
|
-
if (line
|
|
619
|
-
buffer.push(line
|
|
790
|
+
if (lineHasExplicitContinuation(line)) {
|
|
791
|
+
buffer.push(lineForContinuation(line));
|
|
620
792
|
rl.setPrompt(" ");
|
|
621
793
|
if (!closed) rl.prompt();
|
|
622
794
|
return;
|
|
623
795
|
}
|
|
624
796
|
|
|
625
|
-
|
|
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
|
+
|
|
626
830
|
buffer.length = 0;
|
|
627
831
|
rl.setPrompt("> ");
|
|
628
|
-
|
|
629
832
|
try {
|
|
630
|
-
for (const output of await
|
|
833
|
+
for (const output of await runSnippet(input, state, { explicitTerminated })) {
|
|
631
834
|
console.log(output);
|
|
632
835
|
}
|
|
633
836
|
} catch (error) {
|
|
@@ -649,13 +852,19 @@ if (isMainEntrypoint()) {
|
|
|
649
852
|
}
|
|
650
853
|
|
|
651
854
|
export {
|
|
855
|
+
aliasNames,
|
|
856
|
+
analyzeAcceptedSnippet,
|
|
857
|
+
analyzeRawSnippet,
|
|
652
858
|
canonicalNames,
|
|
653
859
|
completeInput,
|
|
654
860
|
createCompleter,
|
|
655
861
|
createState,
|
|
656
862
|
evaluateInput,
|
|
863
|
+
explicitSnippetTerminated,
|
|
657
864
|
helpText,
|
|
658
865
|
isMainEntrypoint,
|
|
866
|
+
lineHasExplicitContinuation,
|
|
867
|
+
lineTerminatesSnippet,
|
|
659
868
|
printValue,
|
|
660
869
|
propertyListToFilter,
|
|
661
870
|
valueToK
|