@fraczak/k 6.6.0 → 6.8.0

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.
@@ -3,19 +3,18 @@ import {
3
3
  evaluateInput,
4
4
  createCompleter,
5
5
  promptForState,
6
- helpText,
7
6
  analyzeRawSnippet,
8
7
  lineTerminatesSnippet,
9
8
  lineHasExplicitContinuation,
10
9
  savedLibrary,
11
- listCodecs,
12
- loadCodecModule,
13
10
  registerCodec,
14
11
  unregisterCodec,
15
12
  resolveCodec,
16
13
  codeHashToPattern,
17
- BUILTIN_CODECS,
18
- formatDuration
14
+ formatDuration,
15
+ resolveInputTypeHash,
16
+ resolveInputPattern,
17
+ valueForPattern
19
18
  } from "../repl.mjs";
20
19
  import { Value, isProduct, isVariant } from "../Value.mjs";
21
20
  import { encodeLibrary } from "../object.mjs";
@@ -71,6 +70,7 @@ let inputPopupCodec;
71
70
  let inputPopupText;
72
71
  let inputPopupStatus;
73
72
  let inputPopupHint;
73
+ let inputPopupCodecHint;
74
74
  let inputSamplesList;
75
75
 
76
76
  function escapeHtml(str) {
@@ -569,19 +569,19 @@ function exportKlib() {
569
569
  const CODEC_PRESETS = {
570
570
  yn: {
571
571
  name: "yn",
572
- type: "bool",
572
+ type: "yes_no",
573
573
  universal: false,
574
- parse: `// Parse 'yes', 'no', 'true', 'false' into $bool
574
+ parse: `// Parse 'yes', 'no', 'true', 'false' into $yes_no (< {} yes, {} no >)
575
575
  const tag = text.trim().toLowerCase();
576
576
  if (tag === "yes" || tag === "true" || tag === "1") {
577
- return Value.variant("true", Value.product({}));
577
+ return Value.variant("yes", Value.product({}));
578
578
  }
579
579
  if (tag === "no" || tag === "false" || tag === "0") {
580
- return Value.variant("false", Value.product({}));
580
+ return Value.variant("no", Value.product({}));
581
581
  }
582
582
  throw new Error("Expected yes/no or true/false");`,
583
- print: `// Serialize boolean value to YES / NO
584
- return value.tag === "true" ? "YES" : "NO";`
583
+ print: `// Serialize yes_no value to YES / NO
584
+ return value.tag === "yes" ? "YES" : "NO";`
585
585
  },
586
586
  hex: {
587
587
  name: "hex",
@@ -652,8 +652,9 @@ return "$" + dollars.toString() + "." + cents;`
652
652
  parse: `// Parse text string and return a K Value
653
653
  // Available: Value.product({ field: val }), Value.variant("tag", val)
654
654
  return Value.product({});`,
655
- print: `// Serialize K Value into string or Buffer
656
- return JSON.stringify(value);`
655
+ print: `// Serialize K Value into string
656
+ // value is a Value instance: inspect value.fields or value.tag & value.value
657
+ return String(value);`
657
658
  }
658
659
  };
659
660
 
@@ -696,7 +697,7 @@ function renderCodecsModal() {
696
697
  for (const [alias, hash] of aliases) {
697
698
  const opt = document.createElement("option");
698
699
  opt.value = alias;
699
- opt.textContent = `$${alias} (${hash.slice(0, 10)}...)`;
700
+ opt.textContent = `$ ${alias} (${hash.slice(0, 10)}...)`;
700
701
  typeGroup.appendChild(opt);
701
702
  }
702
703
  codecTypeSelect.appendChild(typeGroup);
@@ -838,9 +839,9 @@ function registerCustomCodecFromForm() {
838
839
  );
839
840
  }
840
841
 
841
- let codeHash = rawType === "*" ? "*" : state.typeAliases[rawType] || rawType;
842
- if (!codeHash.startsWith("@") && codeHash !== "*") {
843
- throw new Error(`Cannot resolve type '${rawType}' to a canonical code hash. Define the type first (e.g. :type ${rawType} = ...).`);
842
+ let codeHash = "*";
843
+ if (rawType !== "*") {
844
+ codeHash = resolveInputTypeHash(state, rawType);
844
845
  }
845
846
 
846
847
  registerCodec(state, {
@@ -945,8 +946,8 @@ export function print(value, context) {
945
946
 
946
947
  const CODEC_SAMPLES = {
947
948
  int: ["0", "10", "42", "-15", "1000000"],
948
- utf8: ["hello", "hello world", "k repl", "λ"],
949
- json: ['{"name":"Alice"}', '{"x":10,"y":20}', 'true', '[1, 2, 3]'],
949
+ utf8: ["hello", "hello world", "k repl", "abc"],
950
+ json: ['{"x":12,"n":"Woj"}', '{"name":"Alice"}', 'true', '[1, 2, 3]'],
950
951
  ieee: ["0.0", "3.14159", "-2.718", "1e6"],
951
952
  yn: ["yes", "no", "true", "false"],
952
953
  hex: ["0x2A", "0xFF", "0x1000", "-0x10"],
@@ -997,6 +998,10 @@ function renderInputSamples(codecName) {
997
998
  btn.onclick = () => {
998
999
  if (inputPopupText) {
999
1000
  inputPopupText.value = s;
1001
+ if (codecName === "json" && s === '{"x":12,"n":"Woj"}' && (!inputPopupType?.value || inputPopupType.value === "*")) {
1002
+ inputPopupType.value = "{float64 x, string n}";
1003
+ updateInputPopupCodecs();
1004
+ }
1000
1005
  inputPopupText.focus();
1001
1006
  updateLiveValidation();
1002
1007
  }
@@ -1007,19 +1012,36 @@ function renderInputSamples(codecName) {
1007
1012
 
1008
1013
  function updateInputPopupHint() {
1009
1014
  if (!inputPopupHint) return;
1015
+ const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
1016
+ const codecName = inputPopupCodec?.value || state.pendingInput?.codecName;
1010
1017
  if (state.pendingInput) {
1011
- const rawType = inputPopupType?.value || state.pendingInput.codeHash;
1012
- const codecName = inputPopupCodec?.value || state.pendingInput.codecName;
1013
- inputPopupHint.innerHTML = `Enter input value for <b>$${escapeHtml(rawType)}</b> using codec <b>${escapeHtml(codecName || "default")}</b>:`;
1018
+ const displayPattern = rawPattern || state.pendingInput.rawExpr || "(...)";
1019
+ inputPopupHint.innerHTML = `Enter input value for <b>${escapeHtml(displayPattern)}</b> using codec <b>${escapeHtml(codecName || "default")}</b>:`;
1014
1020
  } else {
1015
- inputPopupHint.innerHTML = `Choose target type &amp; deserializer, then enter input string:`;
1021
+ inputPopupHint.innerHTML = `Choose target pattern &amp; deserializer, then enter input string:`;
1022
+ }
1023
+
1024
+ if (inputPopupCodecHint) {
1025
+ if (codecName === "json") {
1026
+ inputPopupCodecHint.textContent = "Parameterized codec: deserializes JSON into the specified Target Pattern.";
1027
+ } else {
1028
+ inputPopupCodecHint.textContent = "";
1029
+ }
1016
1030
  }
1017
1031
  }
1018
1032
 
1019
1033
  function updateInputPopupCodecs() {
1020
1034
  if (!inputPopupCodec) return;
1021
- const rawType = inputPopupType?.value || "*";
1022
- const codeHash = rawType === "*" ? "*" : state.typeAliases?.[rawType] || rawType;
1035
+ const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
1036
+ let codeHash = null;
1037
+ if (rawPattern && rawPattern !== "*" && rawPattern !== "(...)") {
1038
+ try {
1039
+ const resolved = resolveInputPattern(state, rawPattern);
1040
+ codeHash = resolved.codeHash;
1041
+ } catch {
1042
+ codeHash = null;
1043
+ }
1044
+ }
1023
1045
  const prev = inputPopupCodec.value;
1024
1046
 
1025
1047
  inputPopupCodec.innerHTML = "";
@@ -1027,8 +1049,8 @@ function updateInputPopupCodecs() {
1027
1049
  const store = state.codecs || {};
1028
1050
 
1029
1051
  const candidates = [
1030
- ...(store[codeHash] || []),
1031
- ...(codeHash !== "*" ? (store["*"] || []) : [])
1052
+ ...(codeHash ? (store[codeHash] || []) : []),
1053
+ ...(store["*"] || [])
1032
1054
  ];
1033
1055
 
1034
1056
  const seen = new Set();
@@ -1039,11 +1061,26 @@ function updateInputPopupCodecs() {
1039
1061
  }
1040
1062
  }
1041
1063
 
1042
- if (matchingCodecs.length === 0) {
1043
- if (rawType === "int") matchingCodecs.push("int");
1044
- if (rawType === "bool") matchingCodecs.push("yn");
1045
- if (rawType === "string") matchingCodecs.push("utf8");
1064
+ const bareName = rawPattern.replace(/^(\?|\$)\s*/, "").trim();
1065
+ if (!seen.has("json")) {
1046
1066
  matchingCodecs.push("json");
1067
+ seen.add("json");
1068
+ }
1069
+ if ((bareName === "int" || (codeHash && codeHash === state.typeAliases?.int)) && !seen.has("int")) {
1070
+ matchingCodecs.push("int");
1071
+ seen.add("int");
1072
+ }
1073
+ if ((bareName === "string" || bareName === "utf8" || (codeHash && (codeHash === state.typeAliases?.string || codeHash === state.typeAliases?.utf8))) && !seen.has("utf8")) {
1074
+ matchingCodecs.push("utf8");
1075
+ seen.add("utf8");
1076
+ }
1077
+ if ((bareName === "float64" || bareName === "ieee" || (codeHash && codeHash === state.typeAliases?.float64)) && !seen.has("ieee")) {
1078
+ matchingCodecs.push("ieee");
1079
+ seen.add("ieee");
1080
+ }
1081
+ if ((bareName === "yes_no" || bareName === "bool" || (codeHash && codeHash === state.typeAliases?.yes_no)) && !seen.has("yn")) {
1082
+ matchingCodecs.push("yn");
1083
+ seen.add("yn");
1047
1084
  }
1048
1085
 
1049
1086
  for (const name of matchingCodecs) {
@@ -1064,39 +1101,38 @@ function updateInputPopupCodecs() {
1064
1101
  updateLiveValidation();
1065
1102
  }
1066
1103
 
1067
- function populateInputPopupDropdowns(preselectedType = null, preselectedCodec = null) {
1104
+ function populateInputPopupDropdowns(preselectedPattern = null, preselectedCodec = null) {
1068
1105
  if (!inputPopupType) return;
1069
1106
 
1070
- let prevType = preselectedType;
1071
- if (!prevType && state.pendingInput) {
1072
- const foundAlias = Object.entries(state.typeAliases || {}).find(([, h]) => h === state.pendingInput.codeHash);
1073
- prevType = foundAlias ? foundAlias[0] : state.pendingInput.codeHash;
1074
- }
1075
- if (!prevType) {
1076
- prevType = inputPopupType.value;
1107
+ const datalist = document.getElementById("input-popup-type-list");
1108
+ if (datalist) {
1109
+ datalist.innerHTML = "";
1110
+ const aliases = Object.entries(state.typeAliases || {}).sort(([a], [b]) => a.localeCompare(b));
1111
+ for (const [alias, hash] of aliases) {
1112
+ const opt = document.createElement("option");
1113
+ opt.value = `$ ${alias}`;
1114
+ opt.textContent = `$ ${alias} (${hash.slice(0, 10)}...)`;
1115
+ datalist.appendChild(opt);
1116
+ }
1117
+ const universalOpt = document.createElement("option");
1118
+ universalOpt.value = "(...)";
1119
+ universalOpt.textContent = "(...) (Any document)";
1120
+ datalist.appendChild(universalOpt);
1077
1121
  }
1078
1122
 
1079
- inputPopupType.innerHTML = "";
1080
-
1081
- const aliases = Object.entries(state.typeAliases || {}).sort(([a], [b]) => a.localeCompare(b));
1082
- for (const [alias, hash] of aliases) {
1083
- const opt = document.createElement("option");
1084
- opt.value = alias;
1085
- opt.textContent = `$${alias} (${hash.slice(0, 10)}...)`;
1086
- inputPopupType.appendChild(opt);
1123
+ let prevPattern = preselectedPattern;
1124
+ if (!prevPattern && state.pendingInput) {
1125
+ prevPattern = state.pendingInput.rawExpr;
1126
+ if (!prevPattern && state.pendingInput.codeHash) {
1127
+ const foundAlias = Object.entries(state.typeAliases || {}).find(([, h]) => h === state.pendingInput.codeHash);
1128
+ prevPattern = foundAlias ? `$ ${foundAlias[0]}` : state.pendingInput.codeHash;
1129
+ }
1087
1130
  }
1088
-
1089
- const universalOpt = document.createElement("option");
1090
- universalOpt.value = "*";
1091
- universalOpt.textContent = "* (Universal / Any)";
1092
- inputPopupType.appendChild(universalOpt);
1093
-
1094
- if (prevType && (aliases.some(([a]) => a === prevType) || prevType === "*")) {
1095
- inputPopupType.value = prevType;
1096
- } else if (aliases.length > 0) {
1097
- inputPopupType.value = aliases[0][0];
1131
+ if (!prevPattern) {
1132
+ prevPattern = inputPopupType.value || "(...)";
1098
1133
  }
1099
1134
 
1135
+ inputPopupType.value = prevPattern;
1100
1136
  updateInputPopupCodecs();
1101
1137
 
1102
1138
  const targetCodec = preselectedCodec || state.pendingInput?.codecName;
@@ -1114,26 +1150,39 @@ function updateLiveValidation() {
1114
1150
  return;
1115
1151
  }
1116
1152
 
1117
- const rawType = inputPopupType?.value;
1153
+ const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
1118
1154
  const codecName = inputPopupCodec?.value;
1119
- if (!rawType) {
1120
- inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">Select a target type first.</span>`;
1155
+ if (!rawPattern && !codecName) {
1156
+ inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">Specify a target pattern or codec first.</span>`;
1121
1157
  return;
1122
1158
  }
1123
1159
 
1124
- const codeHash = rawType === "*" ? "*" : state.typeAliases?.[rawType] || rawType;
1160
+ let pattern = null;
1161
+ let codeHash = null;
1162
+ if (rawPattern && rawPattern !== "*" && rawPattern !== "(...)") {
1163
+ try {
1164
+ const resolved = resolveInputPattern(state, rawPattern);
1165
+ pattern = resolved.pattern;
1166
+ codeHash = resolved.codeHash;
1167
+ } catch (err) {
1168
+ inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">⚠ Pattern Error: ${escapeHtml(err.message || String(err))}</span>`;
1169
+ return;
1170
+ }
1171
+ }
1172
+
1125
1173
  try {
1126
1174
  const codec = resolveCodec(state, codeHash, codecName || null, "parse");
1127
- const pattern = codeHashToPattern(codeHash, (h) => state.codes?.[h]);
1175
+ const resolvedPattern = pattern || (codeHash && codeHash !== "*" ? codeHashToPattern(codeHash, (h) => state.codes?.[h]) : null);
1128
1176
  const parsed = codec.parse(text, {
1129
1177
  codeHash,
1130
- pattern,
1178
+ pattern: resolvedPattern,
1131
1179
  state,
1132
1180
  Value,
1133
1181
  isProduct,
1134
1182
  isVariant
1135
1183
  });
1136
- const repr = parsed instanceof Value ? parsed.toJSON() : (typeof parsed === "object" ? JSON.stringify(parsed) : String(parsed));
1184
+ const validated = valueForPattern(parsed, resolvedPattern, text, codec.name);
1185
+ const repr = validated instanceof Value ? validated.toJSON() : (typeof validated === "object" ? JSON.stringify(validated) : String(validated));
1137
1186
  inputPopupStatus.innerHTML = `<span class="line-output" style="margin:0;padding:2px 6px;">✓ Valid: <code>${escapeHtml(String(repr))}</code></span>`;
1138
1187
  } catch (err) {
1139
1188
  inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">⚠ ${escapeHtml(err.message || String(err))}</span>`;
@@ -1142,25 +1191,43 @@ function updateLiveValidation() {
1142
1191
 
1143
1192
  export async function submitInputPopup() {
1144
1193
  const text = inputPopupText?.value ?? "";
1194
+ const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
1195
+ const codecName = inputPopupCodec?.value;
1145
1196
 
1146
- // If state.pendingInput is not set, configure it from the popup selections
1147
- if (!state.pendingInput) {
1148
- const rawType = inputPopupType?.value;
1149
- const codecName = inputPopupCodec?.value;
1150
- if (!rawType) {
1151
- if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">Please select a target type.</span>`;
1152
- return;
1153
- }
1154
- const codeHash = rawType === "*" ? "*" : state.typeAliases?.[rawType] || rawType;
1197
+ if (!rawPattern && !codecName) {
1198
+ if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">Please specify a target pattern or codec.</span>`;
1199
+ return;
1200
+ }
1201
+
1202
+ let pattern = null;
1203
+ let codeHash = null;
1204
+ let rawExpr = rawPattern || "(...)";
1205
+ if (rawPattern && rawPattern !== "*" && rawPattern !== "(...)") {
1155
1206
  try {
1156
- const codec = resolveCodec(state, codeHash, codecName || null, "parse");
1157
- state.pendingInput = { codeHash, codecName: codec.name, promptName: codec.name };
1207
+ const resolved = resolveInputPattern(state, rawPattern);
1208
+ pattern = resolved.pattern;
1209
+ codeHash = resolved.codeHash;
1210
+ rawExpr = rawPattern;
1158
1211
  } catch (err) {
1159
- if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">${escapeHtml(err.message)}</span>`;
1212
+ if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">Pattern Error: ${escapeHtml(err.message || String(err))}</span>`;
1160
1213
  return;
1161
1214
  }
1162
1215
  }
1163
1216
 
1217
+ try {
1218
+ const codec = resolveCodec(state, codeHash, codecName || null, "parse");
1219
+ state.pendingInput = {
1220
+ pattern,
1221
+ codeHash,
1222
+ codecName: codec.name,
1223
+ promptName: codec.name,
1224
+ rawExpr: rawExpr || codec.name
1225
+ };
1226
+ } catch (err) {
1227
+ if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">${escapeHtml(err.message || String(err))}</span>`;
1228
+ return;
1229
+ }
1230
+
1164
1231
  closeInputPopup();
1165
1232
  await executeCommand(text);
1166
1233
  }
@@ -1190,6 +1257,7 @@ export function initRepl() {
1190
1257
  inputPopupText = document.getElementById("input-popup-text");
1191
1258
  inputPopupStatus = document.getElementById("input-popup-status");
1192
1259
  inputPopupHint = document.getElementById("input-popup-hint");
1260
+ inputPopupCodecHint = document.getElementById("input-popup-codec-hint");
1193
1261
  inputSamplesList = document.getElementById("input-samples-list");
1194
1262
 
1195
1263
  updatePrompt();
@@ -1197,16 +1265,7 @@ export function initRepl() {
1197
1265
  // Welcome message
1198
1266
  appendSystemMessage(`
1199
1267
  <div class="welcome-banner">
1200
- <div class="welcome-title">λ k interactive repl</div>
1201
- <div class="welcome-desc">First-order partial functions over algebraic data types &bull; WebAssembly execution engine &bull; Custom Serializers &amp; Deserializers (:codecs)</div>
1202
- <div class="welcome-tips">
1203
- <span>💡 Try: <a href="javascript:void(0)" class="quick-link" data-code=":load core.k">:load core.k</a></span>
1204
- <span>• <a href="javascript:void(0)" class="quick-link" data-code=":codec load int">:codec load int</a></span>
1205
- <span>• <a href="javascript:void(0)" class="quick-link" data-code="10 int">10 int</a></span>
1206
- <span>• <a href="javascript:void(0)" class="quick-link" data-code="{10 int x, 5 int y} plus">{10 int x, 5 int y} plus</a></span>
1207
- <span>• <a href="javascript:void(0)" class="quick-link" data-code=":codecs">:codecs</a></span>
1208
- <span>• <a href="javascript:void(0)" class="quick-link" data-code=":help">:help</a></span>
1209
- </div>
1268
+ <div class="welcome-title">k interactive repl</div>
1210
1269
  </div>
1211
1270
  `, false, true);
1212
1271
 
@@ -1365,14 +1424,6 @@ export function initRepl() {
1365
1424
  const clearBtn = document.getElementById("btn-clear");
1366
1425
  if (clearBtn) clearBtn.onclick = clearOutput;
1367
1426
 
1368
- // Reset button
1369
- const resetBtn = document.getElementById("btn-reset");
1370
- if (resetBtn) {
1371
- resetBtn.onclick = () => {
1372
- executeCommand(":reset");
1373
- };
1374
- }
1375
-
1376
1427
  // Help button
1377
1428
  const helpBtn = document.getElementById("btn-help");
1378
1429
  if (helpBtn) {
@@ -1458,6 +1509,7 @@ export function initRepl() {
1458
1509
 
1459
1510
  if (inputPopupType) {
1460
1511
  inputPopupType.addEventListener("change", updateInputPopupCodecs);
1512
+ inputPopupType.addEventListener("input", updateInputPopupCodecs);
1461
1513
  }
1462
1514
 
1463
1515
  if (inputPopupCodec) {