@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CLAUDE.md +1 -1
  3. package/DOCS/BIG_PICTURE.md +3 -5
  4. package/DOCS/CODECS.md +80 -181
  5. package/DOCS/CODEC_ARCHITECTURE.md +194 -0
  6. package/DOCS/REPL.md +69 -51
  7. package/Examples/arithmetics.k +1 -0
  8. package/Examples/poly.k +46 -0
  9. package/backends/llvm/runtime/krt.c +4 -0
  10. package/backends/llvm/src/kvm2llvm.mjs +2 -2
  11. package/backends/llvm/tests/test-llvm.mjs +19 -0
  12. package/backends/wasm/src/kvm2wasm.mjs +4 -0
  13. package/backends/wasm/tests/test-wasm.mjs +4 -0
  14. package/browser/repl-browser.mjs +131 -779
  15. package/browser/shims/process.mjs +7 -0
  16. package/browser/tree-view.mjs +5 -2
  17. package/browser/vfs-data.mjs +14 -14
  18. package/codecs/README.md +7 -0
  19. package/codecs/ieee.mjs +50 -75
  20. package/codecs/int.mjs +83 -62
  21. package/codecs/json-codec.mjs +5 -77
  22. package/codecs/json.mjs +12 -56
  23. package/codecs/k-parse.mjs +3 -19
  24. package/codecs/runtime/codec-sdk.mjs +94 -0
  25. package/codecs/runtime/pattern-k.mjs +45 -0
  26. package/codecs/string-codec.mjs +48 -74
  27. package/codecs/unit.mjs +13 -61
  28. package/codecs/utf16.mjs +13 -56
  29. package/codecs/utf8.mjs +11 -55
  30. package/package.json +1 -1
  31. package/patterns/from-k.mjs +2 -22
  32. package/repl-codecs.mjs +69 -196
  33. package/repl.mjs +177 -281
  34. package/scripts/build-repl-html.mjs +114 -385
  35. package/scripts/run-tests.mjs +1 -0
  36. package/tests/test-codecs.mjs +186 -0
  37. package/tests/test-poly.mjs +44 -0
  38. package/tests/test-repl-html.mjs +163 -68
  39. package/tests/test-repl.mjs +210 -156
  40. package/tests/test-structural-values.mjs +17 -0
  41. package/tests/test-tree-view.mjs +3 -0
  42. package/codecs/example-pipeline.mjs +0 -163
  43. package/codecs/runtime/ieee-pattern.mjs +0 -30
  44. package/codecs/runtime/test-codec.mjs +0 -89
  45. 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 { patterns2filters, prettyCode, prettyRel } from "./pretty.mjs";
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
- matchBuiltinCodec
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", "run", "eval", "t", "d", "C",
52
- "codes", "codecs", "rels", "codec", "input", "reset", "klib", "ko", "load",
53
- "time",
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 codeHash = closedPatternToCodeHash(value.pattern);
361
- const codecs = [
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
- lines.push(`${codec.name}: ${formatCodecOutput(codec.print(value, { codeHash, state }))}`);
370
- } catch (error) {
371
- if (codec.universal) continue;
372
- lines.push(`${codec.name}: <error: ${error.message || String(error)}>`);
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
- const match = arg.match(/^(\s*)load(?:\s+(.*))?$/);
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+)?(\s+)?(\S*)?$/);
789
- if (!match) return [[], line];
790
- const [, leading, firstToken = "", afterWhitespace = "", secondPartial = ""] = match;
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 codecMatches = codecNames(state)
797
- .filter((name) => name.startsWith(firstToken))
784
+ const names = codecNames(state);
785
+ const matches = names
786
+ .filter((name) => name.startsWith(partial))
798
787
  .map((name) => `${prefix}${name}`);
799
- return [[...new Set([...types, ...codecMatches])], line];
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
- async function runExpression(input, state) {
1106
- const expression = input.trim();
1107
- if (!expression) throw new Error(":run requires an expression");
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 execRes;
1123
- try {
1124
- execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
1125
- } catch (error) {
1126
- throw remapError(error, lineOffset);
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 compileMs = snippetCompileMs + execRes.wasmCompileMs;
1132
- const executeMs = execRes.executeMs;
1133
- state.lastTiming = {
1134
- compileMs,
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
- const committed = commitResult(state, execRes.result, expression);
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
- execRes = await executeExpressionWithWasm(annotated, state, lineOffset);
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 compileMs = snippetCompileMs + execRes.wasmCompileMs;
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: execRes.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 or codec name");
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, codeHash }) =>
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; use :codec load <name|file> or the Custom Codec Studio UI");
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, codeHash }) =>
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
- const trimmed = input.trim() || "(...)";
1241
-
1242
- let pattern = null;
1243
- let codeHash = null;
1244
- let codecName = null;
1245
- let targetDisplay = null;
1246
- let rawExpr = trimmed;
1247
-
1248
- const isJustCodec = (() => {
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
- if (splitCodec) {
1282
- try {
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 explicitCodec = Boolean(codecName);
1306
- if (!codecName && codeHash) {
1307
- for (const [alias, hash] of Object.entries(state.typeAliases || {})) {
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
- if (codecName) {
1316
- ensureCodecDependencies(state, codecName);
1317
- if (!state.codecs?.[codecName] && matchBuiltinCodec(codecName)) {
1318
- await loadCodecModule(state, codecName);
1319
- }
1320
- } else {
1321
- const hasExactCodec = Boolean(codeHash && state.codecs?.[codeHash]?.some((c) => typeof c.parse === "function"));
1322
- const hasUniversal = Boolean(state.codecs?.[UNIVERSAL_CODE]?.some((c) => typeof c.parse === "function"));
1323
- if (!hasExactCodec && !hasUniversal && matchBuiltinCodec("json")) {
1324
- await loadCodecModule(state, "json");
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.codeHash, pending.codecName, "parse");
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
- codeHash: pending.codeHash,
1351
- pattern,
1352
- state
1214
+ state,
1215
+ codecName: codec.name
1353
1216
  });
1354
- const value = valueForPattern(parsed, pattern, input, codec.name);
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 "run":
1412
- case "eval":
1413
- return runExpression(arg, state);
1414
- case "codes":
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 fresh = createState();
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
- ":run expr run an expression on the current value",
1475
- ":time expr run an expression and report compilation/execution time",
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
- ":codes list type aliases",
1349
+ ":types list type aliases",
1480
1350
  ":rels list relation aliases",
1481
- ":codec load file load a REPL codec module (or built-in: int, utf8, json, ieee, unit)",
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 [<filter=(...)> [codec]] read next line as codec 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(" -h, --help Show this help.");
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("k interpreter (.klib-backed). Type :help for commands.");
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
- if (args.includes("-h") || args.includes("--help")) {
1605
- cliUsage();
1606
- exit(0);
1607
- }
1608
- if (args.length > 0) {
1609
- cliUsage();
1610
- exit(1);
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
  };