@fraczak/k 6.8.0 → 6.10.1
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/CHANGELOG.md +40 -0
- package/CLAUDE.md +1 -1
- package/DOCS/BIG_PICTURE.md +3 -5
- package/DOCS/CODECS.md +80 -181
- package/DOCS/CODEC_ARCHITECTURE.md +194 -0
- package/DOCS/REPL.md +69 -51
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/backends/llvm/runtime/krt.c +4 -0
- package/backends/llvm/src/kvm2llvm.mjs +2 -2
- package/backends/llvm/tests/test-llvm.mjs +19 -0
- package/backends/wasm/src/kvm2wasm.mjs +4 -0
- package/backends/wasm/tests/test-wasm.mjs +4 -0
- package/browser/repl-browser.mjs +131 -779
- package/browser/shims/process.mjs +7 -0
- package/browser/tree-view.mjs +5 -2
- package/browser/vfs-data.mjs +14 -14
- package/codecs/README.md +7 -0
- package/codecs/ieee.mjs +50 -75
- package/codecs/int.mjs +83 -62
- package/codecs/json-codec.mjs +5 -77
- package/codecs/json.mjs +12 -56
- package/codecs/k-parse.mjs +3 -19
- package/codecs/runtime/codec-sdk.mjs +94 -0
- package/codecs/runtime/pattern-k.mjs +45 -0
- package/codecs/string-codec.mjs +48 -74
- package/codecs/unit.mjs +13 -61
- package/codecs/utf16.mjs +13 -56
- package/codecs/utf8.mjs +11 -55
- package/package.json +1 -1
- package/patterns/from-k.mjs +2 -22
- package/repl-codecs.mjs +69 -196
- package/repl.mjs +177 -281
- package/scripts/build-repl-html.mjs +114 -385
- package/scripts/run-tests.mjs +1 -0
- package/tests/test-codecs.mjs +186 -0
- package/tests/test-poly.mjs +44 -0
- package/tests/test-repl-html.mjs +163 -68
- package/tests/test-repl.mjs +210 -156
- package/tests/test-structural-values.mjs +17 -0
- package/tests/test-tree-view.mjs +3 -0
- package/codecs/example-pipeline.mjs +0 -163
- package/codecs/runtime/ieee-pattern.mjs +0 -30
- package/codecs/runtime/test-codec.mjs +0 -89
- package/codecs/runtime/typeFromValue.mjs +0 -34
package/repl.mjs
CHANGED
|
@@ -8,18 +8,20 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { argv, exit, stdin, stdout } from "node:process";
|
|
9
9
|
|
|
10
10
|
import { annotate, parse } from "./index.mjs";
|
|
11
|
-
import { constrainWithPattern } from "./run.mjs";
|
|
11
|
+
import run, { constrainWithPattern, run_rel } from "./run.mjs";
|
|
12
12
|
import { compileWasmArtifactFromObject, instantiateWasmArtifact } from "./backends/wasm/src/wasm.mjs";
|
|
13
13
|
import { exportPatternGraph } from "./codecs/runtime/codec.mjs";
|
|
14
14
|
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
15
15
|
import codes from "./codes.mjs";
|
|
16
16
|
import { Value, isProduct, isVariant } from "./Value.mjs";
|
|
17
|
+
import { patternFromFilter } from "./codecs/runtime/pattern-k.mjs";
|
|
17
18
|
if (typeof globalThis !== "undefined") {
|
|
18
19
|
globalThis.Value = Value;
|
|
19
20
|
globalThis.isProduct = isProduct;
|
|
20
21
|
globalThis.isVariant = isVariant;
|
|
22
|
+
globalThis.patternFromFilter = patternFromFilter;
|
|
21
23
|
}
|
|
22
|
-
import {
|
|
24
|
+
import { prettyCode } from "./pretty.mjs";
|
|
23
25
|
import {
|
|
24
26
|
compileObject,
|
|
25
27
|
compileLibrary,
|
|
@@ -39,18 +41,16 @@ import {
|
|
|
39
41
|
registerCodec,
|
|
40
42
|
resolveCodec,
|
|
41
43
|
unregisterCodec,
|
|
42
|
-
BUILTIN_CODECS,
|
|
43
44
|
closedPatternToCodeHash,
|
|
44
|
-
UNIVERSAL_CODE,
|
|
45
45
|
valueForPattern,
|
|
46
|
-
|
|
46
|
+
ensureEnveloped
|
|
47
47
|
} from "./repl-codecs.mjs";
|
|
48
48
|
|
|
49
49
|
const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
|
|
50
50
|
const COMMAND_NAMES = [
|
|
51
|
-
"help", "type", "code", "
|
|
52
|
-
"
|
|
53
|
-
"
|
|
51
|
+
"help", "type", "code", "rel", "C",
|
|
52
|
+
"types", "codecs", "rels", "codec", "input", "reset", "klib", "ko", "load",
|
|
53
|
+
"engine", "wasm", "js",
|
|
54
54
|
"quit", "exit"
|
|
55
55
|
];
|
|
56
56
|
const CODEC_COMMAND_NAMES = ["load", "unload", "list"];
|
|
@@ -73,9 +73,14 @@ function emptyValue() {
|
|
|
73
73
|
return Value.product({}, [["closed-product", []]]);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function createState() {
|
|
76
|
+
function createState(options = {}) {
|
|
77
77
|
codes.load(cloneJSON(initialCodes));
|
|
78
|
+
const engine = (options.engine || "wasm").toLowerCase();
|
|
79
|
+
if (engine !== "wasm" && engine !== "js") {
|
|
80
|
+
throw new Error(`Invalid engine '${options.engine}'. Valid engines: wasm, js`);
|
|
81
|
+
}
|
|
78
82
|
return {
|
|
83
|
+
engine,
|
|
79
84
|
codes: codes.dump(),
|
|
80
85
|
rels: {},
|
|
81
86
|
relAliases: {},
|
|
@@ -337,11 +342,6 @@ function resolveRel(state, name) {
|
|
|
337
342
|
return hash ? { hash, rel: state.rels[hash] } : { hash: null, rel: null };
|
|
338
343
|
}
|
|
339
344
|
|
|
340
|
-
function relTypeString(rel) {
|
|
341
|
-
const filters = patterns2filters(rel.typePatternGraph, ...rel.def.patterns);
|
|
342
|
-
return filters.map((filter) => prettyRel({ op: "filter", filter })).join(" --> ");
|
|
343
|
-
}
|
|
344
|
-
|
|
345
345
|
function listAliases(aliases) {
|
|
346
346
|
const entries = Object.entries(aliases).sort(([a], [b]) => a.localeCompare(b));
|
|
347
347
|
if (entries.length === 0) return "(none)";
|
|
@@ -349,7 +349,8 @@ function listAliases(aliases) {
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
function formatCodecOutput(output) {
|
|
352
|
-
if (Buffer.isBuffer(output)) return output.toString("utf8");
|
|
352
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(output)) return output.toString("utf8");
|
|
353
|
+
if (typeof Uint8Array !== "undefined" && output instanceof Uint8Array) return new TextDecoder().decode(output);
|
|
353
354
|
return String(output);
|
|
354
355
|
}
|
|
355
356
|
|
|
@@ -357,19 +358,16 @@ function printValue(value, state = null) {
|
|
|
357
358
|
const lines = [valueWithEnvelopeToK(value)];
|
|
358
359
|
if (!state || value === undefined) return lines[0];
|
|
359
360
|
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
...(codeHash ? state.codecs?.[codeHash] || [] : []),
|
|
363
|
-
...(state.codecs?.[UNIVERSAL_CODE] || [])
|
|
364
|
-
];
|
|
365
|
-
if (codecs.length === 0) return lines[0];
|
|
366
|
-
for (const codec of codecs) {
|
|
361
|
+
const store = state.codecs || {};
|
|
362
|
+
for (const codec of Object.values(store)) {
|
|
367
363
|
if (typeof codec.print !== "function") continue;
|
|
368
364
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
365
|
+
const output = codec.print(value, { state });
|
|
366
|
+
if (output != null) {
|
|
367
|
+
lines.push(`${codec.name}: ${formatCodecOutput(output)}`);
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
// Codec cannot format this value; silently skip
|
|
373
371
|
}
|
|
374
372
|
}
|
|
375
373
|
return lines.join("\n");
|
|
@@ -649,13 +647,7 @@ function completeCodecCommand(line, argStart, arg, state) {
|
|
|
649
647
|
const tokenStart = codecLoadPathCompletionStart(argStart, arg);
|
|
650
648
|
if (tokenStart == null) return [[], line];
|
|
651
649
|
const [pathMatches] = completePath(line, tokenStart);
|
|
652
|
-
|
|
653
|
-
const partial = match && match[2] != null ? match[2].trim() : "";
|
|
654
|
-
const prefix = line.slice(0, tokenStart);
|
|
655
|
-
const builtinMatches = ["int", "utf8", "json", "ieee"]
|
|
656
|
-
.filter((name) => name.startsWith(partial))
|
|
657
|
-
.map((name) => `${prefix}${name}`);
|
|
658
|
-
return [[...builtinMatches, ...pathMatches], line];
|
|
650
|
+
return [pathMatches, line];
|
|
659
651
|
}
|
|
660
652
|
|
|
661
653
|
function resolveTypeHash(state, rawName) {
|
|
@@ -785,37 +777,16 @@ function resolveInputPattern(state, rawExpr) {
|
|
|
785
777
|
}
|
|
786
778
|
|
|
787
779
|
function completeInputCommand(line, state, argStart, arg) {
|
|
788
|
-
const match = arg.match(/^(\s*)(\S
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!firstToken || !afterWhitespace) {
|
|
793
|
-
if (/@[A-Za-z0-9_?!+-]*$/.test(line)) return completeCanonicalCode(line, state);
|
|
794
|
-
const [types] = completeTypeIdentifier(line, state);
|
|
780
|
+
const match = arg.match(/^(\s*)(\S*)$/);
|
|
781
|
+
if (match) {
|
|
782
|
+
const [, leading, partial] = match;
|
|
795
783
|
const prefix = line.slice(0, argStart + leading.length);
|
|
796
|
-
const
|
|
797
|
-
|
|
784
|
+
const names = codecNames(state);
|
|
785
|
+
const matches = names
|
|
786
|
+
.filter((name) => name.startsWith(partial))
|
|
798
787
|
.map((name) => `${prefix}${name}`);
|
|
799
|
-
return [
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if (firstToken) {
|
|
803
|
-
const prefix = line.slice(0, argStart + leading.length + firstToken.length + afterWhitespace.length);
|
|
804
|
-
const names = (() => {
|
|
805
|
-
try {
|
|
806
|
-
return codecNames(state, resolveTypeHash(state, firstToken));
|
|
807
|
-
} catch {
|
|
808
|
-
return codecNames(state);
|
|
809
|
-
}
|
|
810
|
-
})();
|
|
811
|
-
return [
|
|
812
|
-
names
|
|
813
|
-
.filter((name) => name.startsWith(secondPartial))
|
|
814
|
-
.map((name) => `${prefix}${name}`),
|
|
815
|
-
line
|
|
816
|
-
];
|
|
788
|
+
return [matches, line];
|
|
817
789
|
}
|
|
818
|
-
|
|
819
790
|
return [[], line];
|
|
820
791
|
}
|
|
821
792
|
|
|
@@ -832,6 +803,10 @@ function completeCommandArgument(line, state) {
|
|
|
832
803
|
return completeCodecCommand(line, argStart, arg, state);
|
|
833
804
|
}
|
|
834
805
|
|
|
806
|
+
if (command === "engine") {
|
|
807
|
+
return completeCommandWord(line, argStart, arg, ["wasm", "js"]);
|
|
808
|
+
}
|
|
809
|
+
|
|
835
810
|
if (command === "input") {
|
|
836
811
|
return completeInputCommand(line, state, argStart, arg);
|
|
837
812
|
}
|
|
@@ -905,14 +880,6 @@ function parseLoadArgs(arg, usagePrefix = ":") {
|
|
|
905
880
|
return { path: arg.trim(), loadAliases: true };
|
|
906
881
|
}
|
|
907
882
|
|
|
908
|
-
const CODEC_FILES = {
|
|
909
|
-
int: "arithmetics.k",
|
|
910
|
-
utf8: "core.k",
|
|
911
|
-
json: "core.k",
|
|
912
|
-
ieee: "ieee.k",
|
|
913
|
-
unit: "core.k"
|
|
914
|
-
};
|
|
915
|
-
|
|
916
883
|
function normalizeFilePath(p) {
|
|
917
884
|
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
918
885
|
}
|
|
@@ -983,24 +950,6 @@ function loadSourceOrKlib(state, targetPath, options = {}) {
|
|
|
983
950
|
return targetPath;
|
|
984
951
|
}
|
|
985
952
|
|
|
986
|
-
function ensureCodecDependencies(state, codecTarget) {
|
|
987
|
-
const norm = normalizeFilePath(codecTarget).toLowerCase();
|
|
988
|
-
const base = path.basename(norm, path.extname(norm));
|
|
989
|
-
const depFile = CODEC_FILES[base] || CODEC_FILES[norm];
|
|
990
|
-
if (!depFile) return null;
|
|
991
|
-
if (!state.loadedFiles) state.loadedFiles = new Set();
|
|
992
|
-
const depNormKey = normalizeFilePath(depFile);
|
|
993
|
-
const baseName = path.basename(depNormKey);
|
|
994
|
-
const alreadyLoaded = state.loadedFiles.has(depNormKey) ||
|
|
995
|
-
state.loadedFiles.has(`Examples/${depNormKey}`) ||
|
|
996
|
-
Array.from(state.loadedFiles).some(f => path.basename(f) === baseName);
|
|
997
|
-
if (!alreadyLoaded) {
|
|
998
|
-
loadSourceOrKlib(state, depFile);
|
|
999
|
-
return depFile;
|
|
1000
|
-
}
|
|
1001
|
-
return null;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
953
|
function lineForContinuation(line) {
|
|
1005
954
|
return line.replace(/\\\s*$/, "");
|
|
1006
955
|
}
|
|
@@ -1102,47 +1051,24 @@ async function executeExpressionWithWasm(annotated, state, lineOffset = 0) {
|
|
|
1102
1051
|
return { result, wasmCompileMs, executeMs };
|
|
1103
1052
|
}
|
|
1104
1053
|
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
if (!
|
|
1108
|
-
|
|
1109
|
-
restoreCodes(state);
|
|
1110
|
-
const preamble = aliasPreamble(state);
|
|
1111
|
-
const lineOffset = preambleLineCount(preamble);
|
|
1112
|
-
const source = [preamble, expression].filter(Boolean).join("\n");
|
|
1113
|
-
const compileStart = performance.now();
|
|
1114
|
-
let annotated;
|
|
1115
|
-
try {
|
|
1116
|
-
annotated = annotate(source, { libraries: [stateLibrary(state)] });
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
throw remapError(error, lineOffset);
|
|
1119
|
-
}
|
|
1120
|
-
const snippetCompileMs = performance.now() - compileStart;
|
|
1054
|
+
function executeExpressionWithJs(annotated, state, lineOffset = 0) {
|
|
1055
|
+
const mainRel = annotated.rels.__main__;
|
|
1056
|
+
if (!mainRel) return { result: undefined, wasmCompileMs: 0, executeMs: 0 };
|
|
1121
1057
|
|
|
1122
|
-
let
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1058
|
+
let inputValue = state.value ?? emptyValue();
|
|
1059
|
+
if (mainRel.typePatternGraph && mainRel.def.patterns) {
|
|
1060
|
+
const graph = mainRel.typePatternGraph;
|
|
1061
|
+
const nodeId = graph.find(mainRel.def.patterns[0]);
|
|
1062
|
+
const inputPattern = patternToPropertyList(exportPatternGraph(graph, nodeId));
|
|
1063
|
+
inputValue = constrainWithPattern(inputValue, inputPattern, mainRel.def);
|
|
1127
1064
|
}
|
|
1128
|
-
state.codes = codes.dump();
|
|
1129
|
-
restoreCodes(state);
|
|
1130
1065
|
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
executeMs,
|
|
1136
|
-
totalMs: compileMs + executeMs,
|
|
1137
|
-
snippetCompileMs,
|
|
1138
|
-
wasmCompileMs: execRes.wasmCompileMs
|
|
1139
|
-
};
|
|
1066
|
+
const executeStart = performance.now();
|
|
1067
|
+
run.defs = { rels: annotated.rels };
|
|
1068
|
+
const result = run_rel(codes.find, mainRel, inputValue, "__main__");
|
|
1069
|
+
const executeMs = performance.now() - executeStart;
|
|
1140
1070
|
|
|
1141
|
-
|
|
1142
|
-
if (state.showTiming) {
|
|
1143
|
-
committed.push(`/* comp: ${formatDuration(compileMs)}, exec: ${formatDuration(executeMs)} */`);
|
|
1144
|
-
}
|
|
1145
|
-
return committed;
|
|
1071
|
+
return { result, wasmCompileMs: 0, executeMs };
|
|
1146
1072
|
}
|
|
1147
1073
|
|
|
1148
1074
|
async function runSnippet(input, state, options = {}) {
|
|
@@ -1163,7 +1089,8 @@ async function runSnippet(input, state, options = {}) {
|
|
|
1163
1089
|
executeMs: 0,
|
|
1164
1090
|
totalMs: snippetCompileMs,
|
|
1165
1091
|
snippetCompileMs,
|
|
1166
|
-
wasmCompileMs: 0
|
|
1092
|
+
wasmCompileMs: 0,
|
|
1093
|
+
engine: state.engine
|
|
1167
1094
|
};
|
|
1168
1095
|
if (state.showTiming) {
|
|
1169
1096
|
return [`/* comp: ${formatDuration(snippetCompileMs)} */`];
|
|
@@ -1173,26 +1100,32 @@ async function runSnippet(input, state, options = {}) {
|
|
|
1173
1100
|
|
|
1174
1101
|
let execRes;
|
|
1175
1102
|
try {
|
|
1176
|
-
|
|
1103
|
+
if (state.engine === "js") {
|
|
1104
|
+
execRes = executeExpressionWithJs(annotated, state, lineOffset);
|
|
1105
|
+
} else {
|
|
1106
|
+
execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
|
|
1107
|
+
}
|
|
1177
1108
|
} catch (error) {
|
|
1178
1109
|
throw remapError(error, lineOffset);
|
|
1179
1110
|
}
|
|
1180
1111
|
state.codes = codes.dump();
|
|
1181
1112
|
restoreCodes(state);
|
|
1182
1113
|
|
|
1183
|
-
const
|
|
1114
|
+
const wasmCompileMs = execRes.wasmCompileMs || 0;
|
|
1115
|
+
const compileMs = snippetCompileMs + wasmCompileMs;
|
|
1184
1116
|
const executeMs = execRes.executeMs;
|
|
1185
1117
|
state.lastTiming = {
|
|
1186
1118
|
compileMs,
|
|
1187
1119
|
executeMs,
|
|
1188
1120
|
totalMs: compileMs + executeMs,
|
|
1189
1121
|
snippetCompileMs,
|
|
1190
|
-
wasmCompileMs
|
|
1122
|
+
wasmCompileMs,
|
|
1123
|
+
engine: state.engine
|
|
1191
1124
|
};
|
|
1192
1125
|
|
|
1193
1126
|
const committed = commitResult(state, execRes.result, snippet);
|
|
1194
1127
|
if (state.showTiming) {
|
|
1195
|
-
committed.push(`/* comp: ${formatDuration(compileMs)}, exec: ${formatDuration(executeMs)} */`);
|
|
1128
|
+
committed.push(`/* comp: ${formatDuration(compileMs)}, exec: ${formatDuration(executeMs)} (${state.engine}) */`);
|
|
1196
1129
|
}
|
|
1197
1130
|
return committed;
|
|
1198
1131
|
}
|
|
@@ -1206,13 +1139,10 @@ async function loadCodec(input, state) {
|
|
|
1206
1139
|
const [subcommand, ...rest] = trimmed.split(/\s+/);
|
|
1207
1140
|
switch (subcommand) {
|
|
1208
1141
|
case "load": {
|
|
1209
|
-
const filePath = rest.join(" ");
|
|
1210
|
-
if (!filePath) throw new Error(":codec load requires a file path
|
|
1211
|
-
const autoLoaded = ensureCodecDependencies(state, filePath);
|
|
1142
|
+
const filePath = rest.join(" ").trim();
|
|
1143
|
+
if (!filePath) throw new Error(":codec load requires a file path");
|
|
1212
1144
|
const registered = await loadCodecModule(state, expandHome(filePath));
|
|
1213
|
-
return registered.map(({ name
|
|
1214
|
-
`loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}${autoLoaded ? ` (auto-loaded ${autoLoaded})` : ""}`
|
|
1215
|
-
);
|
|
1145
|
+
return registered.map(({ name }) => `loaded codec ${name}`);
|
|
1216
1146
|
}
|
|
1217
1147
|
case "unload": {
|
|
1218
1148
|
const codecName = rest.join(" ").trim();
|
|
@@ -1222,120 +1152,55 @@ async function loadCodec(input, state) {
|
|
|
1222
1152
|
return [`unloaded codec ${codecName}`];
|
|
1223
1153
|
}
|
|
1224
1154
|
case "define":
|
|
1225
|
-
throw new Error(":codec define has been removed;
|
|
1155
|
+
throw new Error(":codec define has been removed; all codecs are loaded from files via :codec load <file.mjs>");
|
|
1226
1156
|
case "list":
|
|
1227
1157
|
if (rest.length > 0) throw new Error(":codec list does not accept arguments");
|
|
1228
1158
|
return [listCodecs(state)];
|
|
1229
1159
|
default: {
|
|
1230
|
-
const autoLoaded = ensureCodecDependencies(state, trimmed);
|
|
1231
1160
|
const registered = await loadCodecModule(state, expandHome(trimmed));
|
|
1232
|
-
return registered.map(({ name
|
|
1233
|
-
`loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}${autoLoaded ? ` (auto-loaded ${autoLoaded})` : ""}`
|
|
1234
|
-
);
|
|
1161
|
+
return registered.map(({ name }) => `loaded codec ${name}`);
|
|
1235
1162
|
}
|
|
1236
1163
|
}
|
|
1237
1164
|
}
|
|
1238
1165
|
|
|
1239
1166
|
async function requestCodecInput(input, state) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
if (trimmed.startsWith("?") || trimmed.startsWith("$") || trimmed.startsWith("{") || trimmed.startsWith("<") || trimmed.startsWith("(")) {
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1252
|
-
const allCodecs = codecNames(state);
|
|
1253
|
-
const builtin = matchBuiltinCodec(trimmed);
|
|
1254
|
-
const isCodecName = allCodecs.includes(trimmed) || builtin != null;
|
|
1255
|
-
const isTypeAlias = Boolean(state.typeAliases && state.typeAliases[trimmed]);
|
|
1256
|
-
return isCodecName && !isTypeAlias;
|
|
1257
|
-
})();
|
|
1258
|
-
|
|
1259
|
-
if (isJustCodec) {
|
|
1260
|
-
codecName = trimmed;
|
|
1261
|
-
targetDisplay = trimmed;
|
|
1262
|
-
ensureCodecDependencies(state, codecName);
|
|
1263
|
-
if (!state.codecs?.[codecName] && matchBuiltinCodec(codecName)) {
|
|
1264
|
-
await loadCodecModule(state, codecName);
|
|
1265
|
-
}
|
|
1266
|
-
if (state.typeAliases?.[codecName]) {
|
|
1267
|
-
codeHash = state.typeAliases[codecName];
|
|
1268
|
-
pattern = codeHashToPattern(codeHash, codes.find);
|
|
1269
|
-
targetDisplay = codeHash;
|
|
1270
|
-
}
|
|
1271
|
-
} else {
|
|
1272
|
-
let splitPattern = null;
|
|
1273
|
-
let splitCodec = null;
|
|
1274
|
-
|
|
1275
|
-
const split = trimmed.match(/^(.*\S)\s+([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)$/);
|
|
1276
|
-
if (split && split[1].trim() !== "?" && split[1].trim() !== "$") {
|
|
1277
|
-
splitPattern = split[1];
|
|
1278
|
-
splitCodec = split[2];
|
|
1279
|
-
}
|
|
1167
|
+
let trimmed = input.trim();
|
|
1168
|
+
if (!trimmed) {
|
|
1169
|
+
const loaded = Object.keys(state.codecs || {});
|
|
1170
|
+
const loadedStr = loaded.length > 0 ? `Loaded: ${loaded.join(", ")}` : "None loaded";
|
|
1171
|
+
return [
|
|
1172
|
+
"Usage: :input <codec.mjs> [text]",
|
|
1173
|
+
`Loaded codecs: ${loadedStr}`
|
|
1174
|
+
];
|
|
1175
|
+
}
|
|
1280
1176
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
const resolved = resolveInputPattern(state, splitPattern);
|
|
1284
|
-
pattern = resolved.pattern;
|
|
1285
|
-
codeHash = resolved.codeHash;
|
|
1286
|
-
codecName = splitCodec;
|
|
1287
|
-
targetDisplay = resolved.display;
|
|
1288
|
-
rawExpr = splitPattern;
|
|
1289
|
-
} catch (patternErr) {
|
|
1290
|
-
const resolved = resolveInputPattern(state, trimmed);
|
|
1291
|
-
pattern = resolved.pattern;
|
|
1292
|
-
codeHash = resolved.codeHash;
|
|
1293
|
-
targetDisplay = resolved.display;
|
|
1294
|
-
rawExpr = trimmed;
|
|
1295
|
-
}
|
|
1296
|
-
} else {
|
|
1297
|
-
const resolved = resolveInputPattern(state, trimmed);
|
|
1298
|
-
pattern = resolved.pattern;
|
|
1299
|
-
codeHash = resolved.codeHash;
|
|
1300
|
-
targetDisplay = resolved.display;
|
|
1301
|
-
rawExpr = trimmed;
|
|
1302
|
-
}
|
|
1177
|
+
if (trimmed.startsWith("$")) {
|
|
1178
|
+
trimmed = trimmed.slice(1).trim();
|
|
1303
1179
|
}
|
|
1304
1180
|
|
|
1305
|
-
const
|
|
1306
|
-
if (!
|
|
1307
|
-
|
|
1308
|
-
if (hash === codeHash && (matchBuiltinCodec(alias) || codecNames(state).includes(alias))) {
|
|
1309
|
-
codecName = alias;
|
|
1310
|
-
break;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1181
|
+
const match = trimmed.match(/^([a-zA-Z0-9_?!+.-]+)(?:\s+([\s\S]*))?$/);
|
|
1182
|
+
if (!match) {
|
|
1183
|
+
throw new Error("Invalid :input syntax. Usage: :input <codec.mjs> [text]");
|
|
1313
1184
|
}
|
|
1314
1185
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
const
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1186
|
+
const codecName = match[1];
|
|
1187
|
+
const inlineText = match[2];
|
|
1188
|
+
|
|
1189
|
+
const codec = resolveCodec(state, codecName, "parse");
|
|
1190
|
+
|
|
1191
|
+
if (inlineText !== undefined) {
|
|
1192
|
+
const parsed = await codec.parse(inlineText, { state, codecName: codec.name });
|
|
1193
|
+
const value = ensureEnveloped(parsed, codec);
|
|
1194
|
+
state.value = value;
|
|
1195
|
+
state.lastResult = value;
|
|
1196
|
+
return [printValue(value, state)];
|
|
1326
1197
|
}
|
|
1327
1198
|
|
|
1328
|
-
const codec = resolveCodec(state, codeHash, codecName, "parse");
|
|
1329
1199
|
state.pendingInput = {
|
|
1330
|
-
pattern,
|
|
1331
|
-
codeHash,
|
|
1332
1200
|
codecName: codec.name,
|
|
1333
|
-
promptName: codec.name
|
|
1334
|
-
rawExpr
|
|
1201
|
+
promptName: codec.name
|
|
1335
1202
|
};
|
|
1336
|
-
|
|
1337
|
-
const usingPart = explicitCodec && codecName && codecName !== targetDisplay ? ` using ${codecName}` : "";
|
|
1338
|
-
return [`input ${targetDisplay}${usingPart}: enter value text`];
|
|
1203
|
+
return [];
|
|
1339
1204
|
}
|
|
1340
1205
|
|
|
1341
1206
|
async function consumeCodecInput(input, state) {
|
|
@@ -1344,14 +1209,12 @@ async function consumeCodecInput(input, state) {
|
|
|
1344
1209
|
state.pendingInput = null;
|
|
1345
1210
|
|
|
1346
1211
|
restoreCodes(state);
|
|
1347
|
-
const codec = resolveCodec(state, pending.
|
|
1348
|
-
const pattern = pending.pattern || (pending.codeHash ? codeHashToPattern(pending.codeHash, codes.find) : null);
|
|
1212
|
+
const codec = resolveCodec(state, pending.codecName, "parse");
|
|
1349
1213
|
const parsed = await codec.parse(input, {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
state
|
|
1214
|
+
state,
|
|
1215
|
+
codecName: codec.name
|
|
1353
1216
|
});
|
|
1354
|
-
const value =
|
|
1217
|
+
const value = ensureEnveloped(parsed, codec);
|
|
1355
1218
|
state.value = value;
|
|
1356
1219
|
state.lastResult = value;
|
|
1357
1220
|
return [printValue(value, state)];
|
|
@@ -1392,10 +1255,6 @@ async function evaluateCommand(line, state) {
|
|
|
1392
1255
|
return [helpText()];
|
|
1393
1256
|
case "timing":
|
|
1394
1257
|
throw new Error(":timing has been removed; timing is now always enabled");
|
|
1395
|
-
case "time": {
|
|
1396
|
-
if (!arg) throw new Error(":time requires an expression");
|
|
1397
|
-
return evaluateInput(arg, state);
|
|
1398
|
-
}
|
|
1399
1258
|
case "type": {
|
|
1400
1259
|
if (!arg) throw new Error(":type requires a type name");
|
|
1401
1260
|
if (arg.includes("=")) {
|
|
@@ -1405,13 +1264,18 @@ async function evaluateCommand(line, state) {
|
|
|
1405
1264
|
}
|
|
1406
1265
|
case "code":
|
|
1407
1266
|
return evaluateCommand(`:C ${arg}`, state);
|
|
1408
|
-
case "rel":
|
|
1409
1267
|
case "def":
|
|
1410
1268
|
throw new Error("Relation definitions use syntax: name = relExpr;");
|
|
1411
|
-
case "
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1269
|
+
case "rel": {
|
|
1270
|
+
if (!arg) throw new Error(`${usagePrefix}rel requires a relation name`);
|
|
1271
|
+
if (arg.includes("=")) {
|
|
1272
|
+
throw new Error("Relation definitions use syntax: name = relExpr; Use :rel <name> to show a relation definition");
|
|
1273
|
+
}
|
|
1274
|
+
const { hash, rel } = resolveRel(state, arg);
|
|
1275
|
+
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
1276
|
+
return [`${arg} = ${prettyRelation(rel, displayAliases(state), state.relAliases)}; -- ${hash}`];
|
|
1277
|
+
}
|
|
1278
|
+
case "types":
|
|
1415
1279
|
return [listAliases(state.typeAliases)];
|
|
1416
1280
|
case "rels":
|
|
1417
1281
|
return [listAliases(state.relAliases)];
|
|
@@ -1420,8 +1284,28 @@ async function evaluateCommand(line, state) {
|
|
|
1420
1284
|
return loadCodec(arg, state);
|
|
1421
1285
|
case "input":
|
|
1422
1286
|
return requestCodecInput(arg, state);
|
|
1287
|
+
case "engine": {
|
|
1288
|
+
const mode = (arg || "").trim().toLowerCase();
|
|
1289
|
+
if (!mode) {
|
|
1290
|
+
return [`Current engine: ${state.engine}`];
|
|
1291
|
+
}
|
|
1292
|
+
if (mode !== "wasm" && mode !== "js") {
|
|
1293
|
+
throw new Error(`Invalid engine '${arg.trim()}'. Valid engines: wasm, js`);
|
|
1294
|
+
}
|
|
1295
|
+
state.engine = mode;
|
|
1296
|
+
return [`engine set to ${mode}`];
|
|
1297
|
+
}
|
|
1298
|
+
case "wasm": {
|
|
1299
|
+
state.engine = "wasm";
|
|
1300
|
+
return ["engine set to wasm"];
|
|
1301
|
+
}
|
|
1302
|
+
case "js": {
|
|
1303
|
+
state.engine = "js";
|
|
1304
|
+
return ["engine set to js"];
|
|
1305
|
+
}
|
|
1423
1306
|
case "reset": {
|
|
1424
|
-
const
|
|
1307
|
+
const currentEngine = state.engine;
|
|
1308
|
+
const fresh = createState({ engine: currentEngine });
|
|
1425
1309
|
Object.assign(state, fresh);
|
|
1426
1310
|
return ["reset"];
|
|
1427
1311
|
}
|
|
@@ -1446,18 +1330,6 @@ async function evaluateCommand(line, state) {
|
|
|
1446
1330
|
loadSourceOrKlib(state, loadPath, { loadAliases });
|
|
1447
1331
|
return [`loaded ${loadPath}`];
|
|
1448
1332
|
}
|
|
1449
|
-
case "t": {
|
|
1450
|
-
if (!arg) throw new Error(`${usagePrefix}t requires a relation name`);
|
|
1451
|
-
const { hash, rel } = resolveRel(state, arg);
|
|
1452
|
-
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
1453
|
-
return [`${arg} : ${relTypeString(rel)} (${hash})`];
|
|
1454
|
-
}
|
|
1455
|
-
case "d": {
|
|
1456
|
-
if (!arg) throw new Error(`${usagePrefix}d requires a relation name`);
|
|
1457
|
-
const { hash, rel } = resolveRel(state, arg);
|
|
1458
|
-
if (!rel) throw new Error(`Unknown relation '${arg}'`);
|
|
1459
|
-
return [`${arg} = ${prettyRelation(rel, displayAliases(state), state.relAliases)}; -- ${hash}`];
|
|
1460
|
-
}
|
|
1461
1333
|
case "C": {
|
|
1462
1334
|
if (!arg) throw new Error(`${usagePrefix}C requires a type name`);
|
|
1463
1335
|
const hash = state.typeAliases[arg] || (arg.startsWith("@") ? arg : null);
|
|
@@ -1471,17 +1343,15 @@ async function evaluateCommand(line, state) {
|
|
|
1471
1343
|
|
|
1472
1344
|
function helpText() {
|
|
1473
1345
|
return [
|
|
1474
|
-
":
|
|
1475
|
-
":
|
|
1476
|
-
":t name show relation type",
|
|
1477
|
-
":d name show relation definition",
|
|
1346
|
+
":engine [wasm|js] display or switch evaluation engine (wasm or js)",
|
|
1347
|
+
":rel name show relation definition",
|
|
1478
1348
|
":type name show type definition",
|
|
1479
|
-
":
|
|
1349
|
+
":types list type aliases",
|
|
1480
1350
|
":rels list relation aliases",
|
|
1481
|
-
":codec load file load a
|
|
1351
|
+
":codec load file load a codec module from file (e.g. :codec load codecs/int.mjs)",
|
|
1482
1352
|
":codec unload name unload a registered codec",
|
|
1483
1353
|
":codec list list loaded codecs (or simply :codecs)",
|
|
1484
|
-
":input
|
|
1354
|
+
":input <codec.mjs> [text] parse input using specified codec",
|
|
1485
1355
|
":load [--no-alias] file",
|
|
1486
1356
|
" load .k source or .klib",
|
|
1487
1357
|
":klib file export state as a library",
|
|
@@ -1501,17 +1371,19 @@ function promptForState(state) {
|
|
|
1501
1371
|
}
|
|
1502
1372
|
|
|
1503
1373
|
function cliUsage() {
|
|
1504
|
-
console.log("Usage: k-repl");
|
|
1505
|
-
console.log(" k-repl -h");
|
|
1374
|
+
console.log("Usage: k-repl [options]");
|
|
1506
1375
|
console.log("");
|
|
1507
1376
|
console.log("Start the interactive k interpreter. Type :help inside the REPL for commands.");
|
|
1508
1377
|
console.log("");
|
|
1509
1378
|
console.log("Options:");
|
|
1510
|
-
console.log("
|
|
1379
|
+
console.log(" --engine=<wasm|js> Select evaluation engine (default: wasm)");
|
|
1380
|
+
console.log(" --js Shortcut for --engine=js");
|
|
1381
|
+
console.log(" --wasm Shortcut for --engine=wasm");
|
|
1382
|
+
console.log(" -h, --help Show this help.");
|
|
1511
1383
|
}
|
|
1512
1384
|
|
|
1513
|
-
function startRepl() {
|
|
1514
|
-
const state = createState();
|
|
1385
|
+
function startRepl(options = {}) {
|
|
1386
|
+
const state = createState(options);
|
|
1515
1387
|
const rl = readline.createInterface({
|
|
1516
1388
|
input: stdin,
|
|
1517
1389
|
output: stdout,
|
|
@@ -1522,7 +1394,7 @@ function startRepl() {
|
|
|
1522
1394
|
let pending = Promise.resolve();
|
|
1523
1395
|
let closed = false;
|
|
1524
1396
|
|
|
1525
|
-
console.log(
|
|
1397
|
+
console.log(`k interpreter (.klib-backed, ${state.engine}). Type :help for commands.`);
|
|
1526
1398
|
rl.prompt();
|
|
1527
1399
|
async function handleLine(line) {
|
|
1528
1400
|
if (state.pendingInput && buffer.length === 0) {
|
|
@@ -1601,15 +1473,38 @@ function startRepl() {
|
|
|
1601
1473
|
|
|
1602
1474
|
if (isMainEntrypoint()) {
|
|
1603
1475
|
const args = argv.slice(2);
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1476
|
+
let engine = "wasm";
|
|
1477
|
+
for (let i = 0; i < args.length; i++) {
|
|
1478
|
+
const arg = args[i];
|
|
1479
|
+
if (arg === "-h" || arg === "--help") {
|
|
1480
|
+
cliUsage();
|
|
1481
|
+
exit(0);
|
|
1482
|
+
} else if (arg === "--js") {
|
|
1483
|
+
engine = "js";
|
|
1484
|
+
} else if (arg === "--wasm") {
|
|
1485
|
+
engine = "wasm";
|
|
1486
|
+
} else if (arg.startsWith("--engine=")) {
|
|
1487
|
+
const val = arg.slice("--engine=".length).toLowerCase();
|
|
1488
|
+
if (val !== "wasm" && val !== "js") {
|
|
1489
|
+
console.error(`Invalid engine '${val}'. Valid engines: wasm, js`);
|
|
1490
|
+
exit(1);
|
|
1491
|
+
}
|
|
1492
|
+
engine = val;
|
|
1493
|
+
} else if (arg === "--engine") {
|
|
1494
|
+
i++;
|
|
1495
|
+
const val = (args[i] || "").toLowerCase();
|
|
1496
|
+
if (val !== "wasm" && val !== "js") {
|
|
1497
|
+
console.error(`Invalid engine '${val}'. Valid engines: wasm, js`);
|
|
1498
|
+
exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
engine = val;
|
|
1501
|
+
} else {
|
|
1502
|
+
console.error(`Unknown option '${arg}'.`);
|
|
1503
|
+
cliUsage();
|
|
1504
|
+
exit(1);
|
|
1505
|
+
}
|
|
1611
1506
|
}
|
|
1612
|
-
startRepl();
|
|
1507
|
+
startRepl({ engine });
|
|
1613
1508
|
}
|
|
1614
1509
|
|
|
1615
1510
|
export {
|
|
@@ -1635,13 +1530,14 @@ export {
|
|
|
1635
1530
|
resolveInputPattern,
|
|
1636
1531
|
valueForPattern,
|
|
1637
1532
|
codeHashToPattern,
|
|
1638
|
-
BUILTIN_CODECS,
|
|
1639
1533
|
printValue,
|
|
1640
1534
|
promptForState,
|
|
1641
1535
|
propertyListToFilter,
|
|
1642
1536
|
savedLibrary,
|
|
1643
1537
|
loadSourceOrKlib,
|
|
1644
|
-
ensureCodecDependencies,
|
|
1645
1538
|
valueToK,
|
|
1646
|
-
formatDuration
|
|
1539
|
+
formatDuration,
|
|
1540
|
+
codecNames,
|
|
1541
|
+
ensureEnveloped,
|
|
1542
|
+
patternFromFilter
|
|
1647
1543
|
};
|