@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/browser/repl-browser.mjs
CHANGED
|
@@ -14,18 +14,25 @@ import {
|
|
|
14
14
|
formatDuration,
|
|
15
15
|
resolveInputTypeHash,
|
|
16
16
|
resolveInputPattern,
|
|
17
|
-
valueForPattern
|
|
17
|
+
valueForPattern,
|
|
18
|
+
codecNames,
|
|
19
|
+
ensureEnveloped,
|
|
20
|
+
patternFromFilter
|
|
18
21
|
} from "../repl.mjs";
|
|
19
|
-
import { Value, isProduct, isVariant } from "../Value.mjs";
|
|
22
|
+
import { Value, isProduct, isVariant, withPattern } from "../Value.mjs";
|
|
20
23
|
import { encodeLibrary } from "../object.mjs";
|
|
21
24
|
import { setVfsFile, getVfsFile, getAllVfsFiles } from "./shims/fs.mjs";
|
|
22
25
|
import { createOutputEntryElement } from "./tree-view.mjs";
|
|
23
26
|
|
|
24
|
-
// Make Value and helpers globally available
|
|
27
|
+
// Make Value and helpers globally available in browser console
|
|
25
28
|
if (typeof window !== "undefined") {
|
|
26
|
-
window
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
Object.assign(window, {
|
|
30
|
+
Value,
|
|
31
|
+
isProduct,
|
|
32
|
+
isVariant,
|
|
33
|
+
withPattern,
|
|
34
|
+
patternFromFilter
|
|
35
|
+
});
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
// Global REPL state
|
|
@@ -60,18 +67,8 @@ let helpModal;
|
|
|
60
67
|
let dropOverlay;
|
|
61
68
|
let codecsModal;
|
|
62
69
|
let codecsBadgeEl;
|
|
63
|
-
let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Input Popup Modal Elements
|
|
67
|
-
let inputPopupModal;
|
|
68
|
-
let inputPopupType;
|
|
69
|
-
let inputPopupCodec;
|
|
70
|
-
let inputPopupText;
|
|
71
|
-
let inputPopupStatus;
|
|
72
|
-
let inputPopupHint;
|
|
73
|
-
let inputPopupCodecHint;
|
|
74
|
-
let inputSamplesList;
|
|
70
|
+
let enabledCodecsGrid;
|
|
71
|
+
|
|
75
72
|
|
|
76
73
|
function escapeHtml(str) {
|
|
77
74
|
return String(str)
|
|
@@ -115,17 +112,7 @@ function ansiToHtml(str) {
|
|
|
115
112
|
|
|
116
113
|
function countActiveCodecs() {
|
|
117
114
|
const store = state.codecs || {};
|
|
118
|
-
|
|
119
|
-
const seen = new Set();
|
|
120
|
-
for (const list of Object.values(store)) {
|
|
121
|
-
for (const c of list) {
|
|
122
|
-
if (!seen.has(c.name)) {
|
|
123
|
-
seen.add(c.name);
|
|
124
|
-
count++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return count;
|
|
115
|
+
return Object.keys(store).length;
|
|
129
116
|
}
|
|
130
117
|
|
|
131
118
|
function updateCodecsBadge() {
|
|
@@ -264,12 +251,6 @@ export async function executeCommand(input) {
|
|
|
264
251
|
const trimmed = input.trim();
|
|
265
252
|
if (!trimmed && !state.pendingInput) return;
|
|
266
253
|
|
|
267
|
-
// Intercept :input without args -> open input popup
|
|
268
|
-
if (trimmed === ":input") {
|
|
269
|
-
openInputPopup();
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
254
|
// Add to history
|
|
274
255
|
if (history.length === 0 || history[history.length - 1] !== input) {
|
|
275
256
|
history.push(input);
|
|
@@ -321,14 +302,21 @@ export async function executeCommand(input) {
|
|
|
321
302
|
// Re-create completer with updated state
|
|
322
303
|
completer = createCompleter(state);
|
|
323
304
|
updateCodecsBadge();
|
|
305
|
+
updateEngineUI();
|
|
324
306
|
if (codecsModal && codecsModal.classList.contains("open")) {
|
|
325
307
|
renderCodecsModal();
|
|
326
308
|
}
|
|
309
|
+
}
|
|
327
310
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
311
|
+
function updateEngineUI() {
|
|
312
|
+
const badge = document.getElementById("engine-badge");
|
|
313
|
+
if (!badge) return;
|
|
314
|
+
badge.textContent = state.engine === "js" ? "js-in-process" : "wasm-in-process";
|
|
315
|
+
badge.title = `Engine: ${state.engine} (click to toggle wasm/js)`;
|
|
316
|
+
if (state.engine === "js") {
|
|
317
|
+
badge.classList.add("js-mode");
|
|
318
|
+
} else {
|
|
319
|
+
badge.classList.remove("js-mode");
|
|
332
320
|
}
|
|
333
321
|
}
|
|
334
322
|
|
|
@@ -353,14 +341,7 @@ function handleAutocomplete() {
|
|
|
353
341
|
}
|
|
354
342
|
|
|
355
343
|
if (matches.length === 1) {
|
|
356
|
-
|
|
357
|
-
const tokenStart = lineBeforeCursor.lastIndexOf(" ") + 1;
|
|
358
|
-
const match = matches[0];
|
|
359
|
-
const beforeToken = text.slice(0, cursorPos - (lineBeforeCursor.length - tokenStart));
|
|
360
|
-
const afterCursor = text.slice(cursorPos);
|
|
361
|
-
inputEl.value = beforeToken + match + (afterCursor.startsWith(" ") ? "" : " ") + afterCursor;
|
|
362
|
-
inputEl.selectionStart = inputEl.selectionEnd = beforeToken.length + match.length + 1;
|
|
363
|
-
adjustInputHeight();
|
|
344
|
+
applyAutocomplete(matches[0]);
|
|
364
345
|
return;
|
|
365
346
|
}
|
|
366
347
|
|
|
@@ -402,11 +383,12 @@ function applyAutocomplete(match) {
|
|
|
402
383
|
const text = inputEl.value;
|
|
403
384
|
const cursorPos = inputEl.selectionStart;
|
|
404
385
|
const lineBeforeCursor = text.slice(0, cursorPos).split("\n").pop();
|
|
405
|
-
const
|
|
406
|
-
const
|
|
386
|
+
const lineStart = cursorPos - lineBeforeCursor.length;
|
|
387
|
+
const beforeLine = text.slice(0, lineStart);
|
|
407
388
|
const afterCursor = text.slice(cursorPos);
|
|
408
|
-
|
|
409
|
-
inputEl.
|
|
389
|
+
const insertSpace = (match.endsWith("/") || afterCursor.startsWith(" ")) ? "" : " ";
|
|
390
|
+
inputEl.value = beforeLine + match + insertSpace + afterCursor;
|
|
391
|
+
inputEl.selectionStart = inputEl.selectionEnd = beforeLine.length + match.length + insertSpace.length;
|
|
410
392
|
hideAutocomplete();
|
|
411
393
|
adjustInputHeight();
|
|
412
394
|
inputEl.focus();
|
|
@@ -566,98 +548,6 @@ function exportKlib() {
|
|
|
566
548
|
// CODECS SUBSYSTEM UI & CONTROLS
|
|
567
549
|
// ==========================================
|
|
568
550
|
|
|
569
|
-
const CODEC_PRESETS = {
|
|
570
|
-
yn: {
|
|
571
|
-
name: "yn",
|
|
572
|
-
type: "yes_no",
|
|
573
|
-
universal: false,
|
|
574
|
-
parse: `// Parse 'yes', 'no', 'true', 'false' into $yes_no (< {} yes, {} no >)
|
|
575
|
-
const tag = text.trim().toLowerCase();
|
|
576
|
-
if (tag === "yes" || tag === "true" || tag === "1") {
|
|
577
|
-
return Value.variant("yes", Value.product({}));
|
|
578
|
-
}
|
|
579
|
-
if (tag === "no" || tag === "false" || tag === "0") {
|
|
580
|
-
return Value.variant("no", Value.product({}));
|
|
581
|
-
}
|
|
582
|
-
throw new Error("Expected yes/no or true/false");`,
|
|
583
|
-
print: `// Serialize yes_no value to YES / NO
|
|
584
|
-
return value.tag === "yes" ? "YES" : "NO";`
|
|
585
|
-
},
|
|
586
|
-
hex: {
|
|
587
|
-
name: "hex",
|
|
588
|
-
type: "int",
|
|
589
|
-
universal: false,
|
|
590
|
-
parse: `// Parse decimal or hex (e.g. 0x2a, 42, -0x10) into $int
|
|
591
|
-
let str = text.trim();
|
|
592
|
-
let sign = "+";
|
|
593
|
-
if (str.startsWith("-")) { sign = "-"; str = str.slice(1).trim(); }
|
|
594
|
-
else if (str.startsWith("+")) { str = str.slice(1).trim(); }
|
|
595
|
-
|
|
596
|
-
const num = str.startsWith("0x") || str.startsWith("0X") ? BigInt(str) : BigInt(str);
|
|
597
|
-
if (num === 0n) sign = "+";
|
|
598
|
-
|
|
599
|
-
const bitChars = num === 0n ? ["0"] : num.toString(2).split("");
|
|
600
|
-
let v = Value.variant("_", Value.product({}));
|
|
601
|
-
for (let i = bitChars.length - 1; i >= 0; i--) {
|
|
602
|
-
v = Value.variant(bitChars[i], v);
|
|
603
|
-
}
|
|
604
|
-
return Value.variant(sign, v);`,
|
|
605
|
-
print: `// Serialize $int to hexadecimal string (0x...)
|
|
606
|
-
if (!isVariant(value) || (value.tag !== "+" && value.tag !== "-")) {
|
|
607
|
-
throw new Error("Not a valid int value");
|
|
608
|
-
}
|
|
609
|
-
let bits = "";
|
|
610
|
-
let node = value.value;
|
|
611
|
-
while (isVariant(node) && node.tag !== "_") {
|
|
612
|
-
bits += node.tag;
|
|
613
|
-
node = node.value;
|
|
614
|
-
}
|
|
615
|
-
const n = bits === "" ? 0n : BigInt("0b" + bits);
|
|
616
|
-
const hex = "0x" + n.toString(16).toUpperCase();
|
|
617
|
-
return (value.tag === "-" && n !== 0n ? "-" : "") + hex;`
|
|
618
|
-
},
|
|
619
|
-
currency: {
|
|
620
|
-
name: "currency",
|
|
621
|
-
type: "int",
|
|
622
|
-
universal: false,
|
|
623
|
-
parse: `// Parse currency e.g. $42.50 or $100 into cents int
|
|
624
|
-
const clean = text.trim().replace(/^\\$/, "").trim();
|
|
625
|
-
const parts = clean.split(".");
|
|
626
|
-
const dollars = BigInt(parts[0] || "0");
|
|
627
|
-
const cents = BigInt((parts[1] || "0").padEnd(2, "0").slice(0, 2));
|
|
628
|
-
const total = dollars * 100n + cents;
|
|
629
|
-
|
|
630
|
-
const bitChars = total === 0n ? ["0"] : total.toString(2).split("");
|
|
631
|
-
let v = Value.variant("_", Value.product({}));
|
|
632
|
-
for (let i = bitChars.length - 1; i >= 0; i--) {
|
|
633
|
-
v = Value.variant(bitChars[i], v);
|
|
634
|
-
}
|
|
635
|
-
return Value.variant("+", v);`,
|
|
636
|
-
print: `// Format cents int as currency string ($X.XX)
|
|
637
|
-
let bits = "";
|
|
638
|
-
let node = value.value;
|
|
639
|
-
while (isVariant(node) && node.tag !== "_") {
|
|
640
|
-
bits += node.tag;
|
|
641
|
-
node = node.value;
|
|
642
|
-
}
|
|
643
|
-
const n = bits === "" ? 0n : BigInt("0b" + bits);
|
|
644
|
-
const dollars = n / 100n;
|
|
645
|
-
const cents = (n % 100n).toString().padStart(2, "0");
|
|
646
|
-
return "$" + dollars.toString() + "." + cents;`
|
|
647
|
-
},
|
|
648
|
-
blank: {
|
|
649
|
-
name: "custom",
|
|
650
|
-
type: "",
|
|
651
|
-
universal: false,
|
|
652
|
-
parse: `// Parse text string and return a K Value
|
|
653
|
-
// Available: Value.product({ field: val }), Value.variant("tag", val)
|
|
654
|
-
return Value.product({});`,
|
|
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);`
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
551
|
function openCodecsModal() {
|
|
662
552
|
if (!codecsModal) return;
|
|
663
553
|
renderCodecsModal();
|
|
@@ -670,567 +560,105 @@ function closeCodecsModal() {
|
|
|
670
560
|
|
|
671
561
|
function renderCodecsModal() {
|
|
672
562
|
updateCodecsBadge();
|
|
563
|
+
const grid = enabledCodecsGrid || document.getElementById("enabled-codecs-grid");
|
|
564
|
+
if (!grid) return;
|
|
565
|
+
grid.innerHTML = "";
|
|
673
566
|
|
|
674
|
-
// 1. Built-in codec buttons state
|
|
675
|
-
const standardCodecs = ["int", "utf8", "json", "ieee"];
|
|
676
|
-
for (const name of standardCodecs) {
|
|
677
|
-
const btn = document.getElementById(`btn-toggle-codec-${name}`);
|
|
678
|
-
const badge = document.getElementById(`codec-badge-${name}`);
|
|
679
|
-
const isLoaded = isCodecRegistered(name);
|
|
680
|
-
if (btn) {
|
|
681
|
-
btn.textContent = isLoaded ? "Unload" : "Load";
|
|
682
|
-
btn.className = isLoaded ? "btn btn-sm btn-secondary" : "btn btn-sm btn-primary";
|
|
683
|
-
}
|
|
684
|
-
if (badge) {
|
|
685
|
-
badge.textContent = isLoaded ? "Active" : "Inactive";
|
|
686
|
-
badge.className = isLoaded ? "codec-status-badge active" : "codec-status-badge";
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// 2. Populate target type dropdown
|
|
691
|
-
if (codecTypeSelect) {
|
|
692
|
-
const prev = codecTypeSelect.value;
|
|
693
|
-
codecTypeSelect.innerHTML = `<option value="">-- Choose Type Alias or Code Hash --</option>`;
|
|
694
|
-
const typeGroup = document.createElement("optgroup");
|
|
695
|
-
typeGroup.label = "Available Type Aliases in State";
|
|
696
|
-
const aliases = Object.entries(state.typeAliases || {}).sort(([a], [b]) => a.localeCompare(b));
|
|
697
|
-
for (const [alias, hash] of aliases) {
|
|
698
|
-
const opt = document.createElement("option");
|
|
699
|
-
opt.value = alias;
|
|
700
|
-
opt.textContent = `$ ${alias} (${hash.slice(0, 10)}...)`;
|
|
701
|
-
typeGroup.appendChild(opt);
|
|
702
|
-
}
|
|
703
|
-
codecTypeSelect.appendChild(typeGroup);
|
|
704
|
-
|
|
705
|
-
const universalOpt = document.createElement("option");
|
|
706
|
-
universalOpt.value = "*";
|
|
707
|
-
universalOpt.textContent = "* (Universal - all types)";
|
|
708
|
-
codecTypeSelect.appendChild(universalOpt);
|
|
709
|
-
|
|
710
|
-
if (prev) codecTypeSelect.value = prev;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// 3. Render active codecs table
|
|
714
|
-
if (activeCodecsTbody) {
|
|
715
|
-
activeCodecsTbody.innerHTML = "";
|
|
716
|
-
const store = state.codecs || {};
|
|
717
|
-
const entries = [];
|
|
718
|
-
for (const [codeHash, list] of Object.entries(store)) {
|
|
719
|
-
for (const c of list) {
|
|
720
|
-
entries.push({ codeHash, ...c });
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
if (entries.length === 0) {
|
|
725
|
-
const tr = document.createElement("tr");
|
|
726
|
-
tr.innerHTML = `<td colspan="5" style="text-align:center;color:var(--text-muted);padding:16px;">No codecs currently loaded. Load a built-in codec or register a custom serializer/deserializer below.</td>`;
|
|
727
|
-
activeCodecsTbody.appendChild(tr);
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Invert type aliases for display
|
|
732
|
-
const hashToAlias = {};
|
|
733
|
-
for (const [name, hash] of Object.entries(state.typeAliases || {})) {
|
|
734
|
-
hashToAlias[hash] = name;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
for (const entry of entries) {
|
|
738
|
-
const tr = document.createElement("tr");
|
|
739
|
-
|
|
740
|
-
const nameTd = document.createElement("td");
|
|
741
|
-
nameTd.className = "codec-cell-name";
|
|
742
|
-
nameTd.textContent = entry.name;
|
|
743
|
-
tr.appendChild(nameTd);
|
|
744
|
-
|
|
745
|
-
const typeTd = document.createElement("td");
|
|
746
|
-
typeTd.className = "codec-cell-type";
|
|
747
|
-
if (entry.codeHash === "*") {
|
|
748
|
-
typeTd.innerHTML = `<span class="badge-type universal">Universal (*)</span>`;
|
|
749
|
-
} else {
|
|
750
|
-
const alias = hashToAlias[entry.codeHash];
|
|
751
|
-
typeTd.innerHTML = `<span class="badge-type">${alias ? `$${alias} ` : ""}<code title="${entry.codeHash}">${entry.codeHash.slice(0, 10)}...</code></span>`;
|
|
752
|
-
}
|
|
753
|
-
tr.appendChild(typeTd);
|
|
754
|
-
|
|
755
|
-
const capTd = document.createElement("td");
|
|
756
|
-
const caps = [];
|
|
757
|
-
if (typeof entry.print === "function") caps.push(`<span class="cap-badge print">Serializer</span>`);
|
|
758
|
-
if (typeof entry.parse === "function") caps.push(`<span class="cap-badge parse">Deserializer</span>`);
|
|
759
|
-
capTd.innerHTML = caps.join(" ");
|
|
760
|
-
tr.appendChild(capTd);
|
|
761
|
-
|
|
762
|
-
const srcTd = document.createElement("td");
|
|
763
|
-
srcTd.className = "codec-cell-src";
|
|
764
|
-
srcTd.textContent = entry.source ? (entry.source === "built-in" ? "Built-in" : entry.source.split("/").pop()) : "Custom";
|
|
765
|
-
tr.appendChild(srcTd);
|
|
766
|
-
|
|
767
|
-
const actTd = document.createElement("td");
|
|
768
|
-
const unloadBtn = document.createElement("button");
|
|
769
|
-
unloadBtn.className = "btn btn-sm btn-danger";
|
|
770
|
-
unloadBtn.textContent = "Unload";
|
|
771
|
-
unloadBtn.onclick = () => {
|
|
772
|
-
executeCommand(`:codec unload ${entry.name}`);
|
|
773
|
-
renderCodecsModal();
|
|
774
|
-
};
|
|
775
|
-
actTd.appendChild(unloadBtn);
|
|
776
|
-
tr.appendChild(actTd);
|
|
777
|
-
|
|
778
|
-
activeCodecsTbody.appendChild(tr);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
function isCodecRegistered(name) {
|
|
784
567
|
const store = state.codecs || {};
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
codecTypeSelect.value = preset.type;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function registerCustomCodecFromForm() {
|
|
809
|
-
const nameInput = document.getElementById("codec-custom-name");
|
|
810
|
-
const parseArea = document.getElementById("codec-custom-parse");
|
|
811
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
812
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
813
|
-
|
|
814
|
-
const name = nameInput?.value.trim();
|
|
815
|
-
const rawType = codecTypeSelect?.value.trim() || "*";
|
|
816
|
-
const parseCode = parseArea?.value.trim();
|
|
817
|
-
const printCode = printArea?.value.trim();
|
|
818
|
-
|
|
819
|
-
if (!name) {
|
|
820
|
-
if (statusBox) statusBox.innerHTML = `<span class="line-error">Codec name is required.</span>`;
|
|
568
|
+
const entries = Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
|
|
569
|
+
|
|
570
|
+
if (entries.length === 0) {
|
|
571
|
+
const empty = document.createElement("div");
|
|
572
|
+
empty.className = "codecs-empty-state";
|
|
573
|
+
empty.style.gridColumn = "1 / -1";
|
|
574
|
+
empty.style.padding = "32px 16px";
|
|
575
|
+
empty.style.textAlign = "center";
|
|
576
|
+
empty.style.color = "var(--text-muted)";
|
|
577
|
+
empty.style.background = "var(--bg-base)";
|
|
578
|
+
empty.style.border = "1px dashed var(--border-color)";
|
|
579
|
+
empty.style.borderRadius = "6px";
|
|
580
|
+
empty.innerHTML = `
|
|
581
|
+
<div style="font-size: 24px; margin-bottom: 8px;">📦</div>
|
|
582
|
+
<div style="font-size: 13px; font-weight: 600; color: var(--text-main); margin-bottom: 6px;">No codecs currently loaded</div>
|
|
583
|
+
<div style="font-size: 12px; line-height: 1.5;">Codecs are ordinary <code>.mjs</code> files.<br>Load a codec using <code>:codec load <file.mjs></code> in the terminal, or select a codec file in <b>Files</b> and click <b>Load into REPL</b>.</div>
|
|
584
|
+
`;
|
|
585
|
+
grid.appendChild(empty);
|
|
821
586
|
return;
|
|
822
587
|
}
|
|
823
588
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
if (statusBox) {
|
|
884
|
-
statusBox.innerHTML = `<span class="line-error">Parse Error: ${escapeHtml(err.message)}</span>`;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function testCustomPrint() {
|
|
890
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
891
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
892
|
-
|
|
893
|
-
try {
|
|
894
|
-
const printFn = new Function(
|
|
895
|
-
"value", "context",
|
|
896
|
-
`const { Value, isProduct, isVariant } = globalThis;\n${printArea.value}`
|
|
897
|
-
);
|
|
898
|
-
const result = printFn(state.value, { Value, isProduct, isVariant, state });
|
|
899
|
-
if (statusBox) {
|
|
900
|
-
statusBox.innerHTML = `<span class="line-output"><b>Print Output:</b> ${escapeHtml(String(result))}</span>`;
|
|
901
|
-
}
|
|
902
|
-
} catch (err) {
|
|
903
|
-
if (statusBox) {
|
|
904
|
-
statusBox.innerHTML = `<span class="line-error">Print Error: ${escapeHtml(err.message)}</span>`;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
function saveCustomCodecToVfs() {
|
|
910
|
-
const nameInput = document.getElementById("codec-custom-name");
|
|
911
|
-
const parseArea = document.getElementById("codec-custom-parse");
|
|
912
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
913
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
914
|
-
|
|
915
|
-
const name = nameInput?.value.trim() || "custom";
|
|
916
|
-
const rawType = codecTypeSelect?.value.trim() || "*";
|
|
917
|
-
const codeHash = rawType === "*" ? "*" : state.typeAliases[rawType] || rawType;
|
|
918
|
-
const fileName = `codecs/${name}-codec.mjs`;
|
|
919
|
-
|
|
920
|
-
const fileContent = `// Custom K Codec: ${name}
|
|
921
|
-
import { Value, isProduct, isVariant } from "../Value.mjs";
|
|
922
|
-
|
|
923
|
-
export const name = ${JSON.stringify(name)};
|
|
924
|
-
export const codes = [${JSON.stringify(codeHash)}];
|
|
925
|
-
export const universal = ${codeHash === "*"};
|
|
926
|
-
|
|
927
|
-
export function parse(text, context) {
|
|
928
|
-
${parseArea?.value || ""}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
export function print(value, context) {
|
|
932
|
-
${printArea?.value || ""}
|
|
933
|
-
}
|
|
934
|
-
`;
|
|
935
|
-
|
|
936
|
-
setVfsFile(fileName, fileContent);
|
|
937
|
-
if (statusBox) {
|
|
938
|
-
statusBox.innerHTML = `<span class="line-output">💾 Saved codec file to VFS as <b>${fileName}</b></span>`;
|
|
939
|
-
}
|
|
940
|
-
appendSystemMessage(`💾 Saved codec file to VFS: <b>${fileName}</b>. You can reload it anytime via <code>:codec load ${fileName}</code>.`);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// ==========================================
|
|
944
|
-
// CODEC INPUT POPUP SUBSYSTEM (:input)
|
|
945
|
-
// ==========================================
|
|
946
|
-
|
|
947
|
-
const CODEC_SAMPLES = {
|
|
948
|
-
int: ["0", "10", "42", "-15", "1000000"],
|
|
949
|
-
utf8: ["hello", "hello world", "k repl", "abc"],
|
|
950
|
-
json: ['{"x":12,"n":"Woj"}', '{"name":"Alice"}', 'true', '[1, 2, 3]'],
|
|
951
|
-
ieee: ["0.0", "3.14159", "-2.718", "1e6"],
|
|
952
|
-
yn: ["yes", "no", "true", "false"],
|
|
953
|
-
hex: ["0x2A", "0xFF", "0x1000", "-0x10"],
|
|
954
|
-
currency: ["$10.00", "$42.50", "$99.99"]
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
export function openInputPopup(preselectedType = null, preselectedCodec = null) {
|
|
958
|
-
if (!inputPopupModal) return;
|
|
959
|
-
|
|
960
|
-
populateInputPopupDropdowns(preselectedType, preselectedCodec);
|
|
961
|
-
|
|
962
|
-
if (inputPopupText) {
|
|
963
|
-
inputPopupText.value = "";
|
|
964
|
-
}
|
|
965
|
-
updateLiveValidation();
|
|
966
|
-
|
|
967
|
-
inputPopupModal.classList.add("open");
|
|
968
|
-
setTimeout(() => {
|
|
969
|
-
if (inputPopupText) inputPopupText.focus();
|
|
970
|
-
}, 50);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
export function closeInputPopup() {
|
|
974
|
-
if (inputPopupModal) {
|
|
975
|
-
inputPopupModal.classList.remove("open");
|
|
976
|
-
}
|
|
977
|
-
if (inputEl) inputEl.focus();
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
export function cancelInputPopup() {
|
|
981
|
-
if (state.pendingInput) {
|
|
982
|
-
state.pendingInput = null;
|
|
983
|
-
updatePrompt();
|
|
984
|
-
appendSystemMessage("Input cancelled.");
|
|
985
|
-
}
|
|
986
|
-
closeInputPopup();
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function renderInputSamples(codecName) {
|
|
990
|
-
if (!inputSamplesList) return;
|
|
991
|
-
inputSamplesList.innerHTML = "";
|
|
992
|
-
const samples = CODEC_SAMPLES[codecName] || ["()"];
|
|
993
|
-
for (const s of samples) {
|
|
994
|
-
const btn = document.createElement("span");
|
|
995
|
-
btn.className = "sample-pill";
|
|
996
|
-
btn.textContent = s;
|
|
997
|
-
btn.title = `Click to insert sample "${s}"`;
|
|
998
|
-
btn.onclick = () => {
|
|
999
|
-
if (inputPopupText) {
|
|
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
|
-
}
|
|
1005
|
-
inputPopupText.focus();
|
|
1006
|
-
updateLiveValidation();
|
|
1007
|
-
}
|
|
589
|
+
for (const entry of entries) {
|
|
590
|
+
const card = document.createElement("div");
|
|
591
|
+
card.className = "codec-card active";
|
|
592
|
+
card.id = `card-codec-${entry.name.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
593
|
+
|
|
594
|
+
const titleEl = document.createElement("div");
|
|
595
|
+
titleEl.className = "codec-card-title";
|
|
596
|
+
titleEl.innerHTML = `
|
|
597
|
+
<span class="codec-icon">📦</span>
|
|
598
|
+
<span class="codec-name">${escapeHtml(entry.name)}</span>
|
|
599
|
+
<span class="codec-status-badge active">Active</span>
|
|
600
|
+
`;
|
|
601
|
+
card.appendChild(titleEl);
|
|
602
|
+
|
|
603
|
+
let targetDesc = "Self-describing";
|
|
604
|
+
if (entry.name === "int.mjs") {
|
|
605
|
+
targetDesc = "Pattern Family: $ int & lists";
|
|
606
|
+
} else if (entry.name === "utf8.mjs") {
|
|
607
|
+
targetDesc = "$ string";
|
|
608
|
+
} else if (entry.name === "ieee.mjs") {
|
|
609
|
+
targetDesc = "$ float64";
|
|
610
|
+
} else if (entry.name === "unit.mjs") {
|
|
611
|
+
targetDesc = "{}";
|
|
612
|
+
} else if (entry.name === "json.mjs") {
|
|
613
|
+
targetDesc = "Self-describing JSON";
|
|
614
|
+
} else if (Array.isArray(entry.patterns) && entry.patterns.length > 0) {
|
|
615
|
+
targetDesc = `Pattern Family (${entry.patterns.length})`;
|
|
616
|
+
} else if (entry.pattern) {
|
|
617
|
+
targetDesc = "Pattern Recipe";
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const typeDiv = document.createElement("div");
|
|
621
|
+
typeDiv.className = "codec-types";
|
|
622
|
+
typeDiv.innerHTML = `Target: <span class="badge-type">${escapeHtml(targetDesc)}</span>`;
|
|
623
|
+
card.appendChild(typeDiv);
|
|
624
|
+
|
|
625
|
+
const descDiv = document.createElement("div");
|
|
626
|
+
descDiv.className = "codec-desc";
|
|
627
|
+
const src = entry.source || entry.name;
|
|
628
|
+
descDiv.innerHTML = `Source: <code>${escapeHtml(src)}</code>`;
|
|
629
|
+
card.appendChild(descDiv);
|
|
630
|
+
|
|
631
|
+
const footer = document.createElement("div");
|
|
632
|
+
footer.className = "codec-card-footer";
|
|
633
|
+
|
|
634
|
+
const capsDiv = document.createElement("div");
|
|
635
|
+
capsDiv.className = "codec-caps";
|
|
636
|
+
const caps = [];
|
|
637
|
+
if (typeof entry.print === "function") caps.push('<span class="cap-badge print">Serializer</span>');
|
|
638
|
+
if (typeof entry.parse === "function") caps.push('<span class="cap-badge parse">Deserializer</span>');
|
|
639
|
+
capsDiv.innerHTML = caps.join(" ");
|
|
640
|
+
footer.appendChild(capsDiv);
|
|
641
|
+
|
|
642
|
+
const unloadBtn = document.createElement("button");
|
|
643
|
+
unloadBtn.className = "btn btn-sm btn-danger";
|
|
644
|
+
unloadBtn.textContent = "Unload";
|
|
645
|
+
unloadBtn.onclick = async () => {
|
|
646
|
+
await executeCommand(`:codec unload ${entry.name}`);
|
|
647
|
+
renderCodecsModal();
|
|
1008
648
|
};
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
649
|
+
footer.appendChild(unloadBtn);
|
|
1012
650
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
|
|
1016
|
-
const codecName = inputPopupCodec?.value || state.pendingInput?.codecName;
|
|
1017
|
-
if (state.pendingInput) {
|
|
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>:`;
|
|
1020
|
-
} else {
|
|
1021
|
-
inputPopupHint.innerHTML = `Choose target pattern & 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
|
-
}
|
|
651
|
+
card.appendChild(footer);
|
|
652
|
+
grid.appendChild(card);
|
|
1030
653
|
}
|
|
1031
654
|
}
|
|
1032
655
|
|
|
1033
|
-
function
|
|
1034
|
-
if (!inputPopupCodec) return;
|
|
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
|
-
}
|
|
1045
|
-
const prev = inputPopupCodec.value;
|
|
1046
|
-
|
|
1047
|
-
inputPopupCodec.innerHTML = "";
|
|
1048
|
-
const matchingCodecs = [];
|
|
656
|
+
function isCodecRegistered(name) {
|
|
1049
657
|
const store = state.codecs || {};
|
|
1050
|
-
|
|
1051
|
-
const candidates = [
|
|
1052
|
-
...(codeHash ? (store[codeHash] || []) : []),
|
|
1053
|
-
...(store["*"] || [])
|
|
1054
|
-
];
|
|
1055
|
-
|
|
1056
|
-
const seen = new Set();
|
|
1057
|
-
for (const c of candidates) {
|
|
1058
|
-
if (typeof c.parse === "function" && !seen.has(c.name)) {
|
|
1059
|
-
seen.add(c.name);
|
|
1060
|
-
matchingCodecs.push(c.name);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
const bareName = rawPattern.replace(/^(\?|\$)\s*/, "").trim();
|
|
1065
|
-
if (!seen.has("json")) {
|
|
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");
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
for (const name of matchingCodecs) {
|
|
1087
|
-
const opt = document.createElement("option");
|
|
1088
|
-
opt.value = name;
|
|
1089
|
-
opt.textContent = name;
|
|
1090
|
-
inputPopupCodec.appendChild(opt);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
if (prev && matchingCodecs.includes(prev)) {
|
|
1094
|
-
inputPopupCodec.value = prev;
|
|
1095
|
-
} else if (matchingCodecs.length > 0) {
|
|
1096
|
-
inputPopupCodec.value = matchingCodecs[0];
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
renderInputSamples(inputPopupCodec.value);
|
|
1100
|
-
updateInputPopupHint();
|
|
1101
|
-
updateLiveValidation();
|
|
658
|
+
return Boolean(store[name]);
|
|
1102
659
|
}
|
|
1103
660
|
|
|
1104
|
-
function populateInputPopupDropdowns(preselectedPattern = null, preselectedCodec = null) {
|
|
1105
|
-
if (!inputPopupType) return;
|
|
1106
|
-
|
|
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);
|
|
1121
|
-
}
|
|
1122
661
|
|
|
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
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
if (!prevPattern) {
|
|
1132
|
-
prevPattern = inputPopupType.value || "(...)";
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
inputPopupType.value = prevPattern;
|
|
1136
|
-
updateInputPopupCodecs();
|
|
1137
|
-
|
|
1138
|
-
const targetCodec = preselectedCodec || state.pendingInput?.codecName;
|
|
1139
|
-
if (targetCodec && inputPopupCodec) {
|
|
1140
|
-
inputPopupCodec.value = targetCodec;
|
|
1141
|
-
renderInputSamples(targetCodec);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
function updateLiveValidation() {
|
|
1146
|
-
if (!inputPopupStatus) return;
|
|
1147
|
-
const text = inputPopupText ? inputPopupText.value : "";
|
|
1148
|
-
if (!text) {
|
|
1149
|
-
inputPopupStatus.innerHTML = `<span style="color:var(--text-muted);font-size:11px;">Ready for input.</span>`;
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
|
|
1154
|
-
const codecName = inputPopupCodec?.value;
|
|
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>`;
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
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
|
-
|
|
1173
|
-
try {
|
|
1174
|
-
const codec = resolveCodec(state, codeHash, codecName || null, "parse");
|
|
1175
|
-
const resolvedPattern = pattern || (codeHash && codeHash !== "*" ? codeHashToPattern(codeHash, (h) => state.codes?.[h]) : null);
|
|
1176
|
-
const parsed = codec.parse(text, {
|
|
1177
|
-
codeHash,
|
|
1178
|
-
pattern: resolvedPattern,
|
|
1179
|
-
state,
|
|
1180
|
-
Value,
|
|
1181
|
-
isProduct,
|
|
1182
|
-
isVariant
|
|
1183
|
-
});
|
|
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));
|
|
1186
|
-
inputPopupStatus.innerHTML = `<span class="line-output" style="margin:0;padding:2px 6px;">✓ Valid: <code>${escapeHtml(String(repr))}</code></span>`;
|
|
1187
|
-
} catch (err) {
|
|
1188
|
-
inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">⚠ ${escapeHtml(err.message || String(err))}</span>`;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
export async function submitInputPopup() {
|
|
1193
|
-
const text = inputPopupText?.value ?? "";
|
|
1194
|
-
const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
|
|
1195
|
-
const codecName = inputPopupCodec?.value;
|
|
1196
|
-
|
|
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 !== "(...)") {
|
|
1206
|
-
try {
|
|
1207
|
-
const resolved = resolveInputPattern(state, rawPattern);
|
|
1208
|
-
pattern = resolved.pattern;
|
|
1209
|
-
codeHash = resolved.codeHash;
|
|
1210
|
-
rawExpr = rawPattern;
|
|
1211
|
-
} catch (err) {
|
|
1212
|
-
if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">Pattern Error: ${escapeHtml(err.message || String(err))}</span>`;
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
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
|
-
|
|
1231
|
-
closeInputPopup();
|
|
1232
|
-
await executeCommand(text);
|
|
1233
|
-
}
|
|
1234
662
|
|
|
1235
663
|
// Initialization on DOMContentLoaded
|
|
1236
664
|
export function initRepl() {
|
|
@@ -1248,17 +676,8 @@ export function initRepl() {
|
|
|
1248
676
|
dropOverlay = document.getElementById("drop-overlay");
|
|
1249
677
|
codecsModal = document.getElementById("codecs-modal");
|
|
1250
678
|
codecsBadgeEl = document.getElementById("codecs-count-badge");
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
inputPopupModal = document.getElementById("input-popup-modal");
|
|
1255
|
-
inputPopupType = document.getElementById("input-popup-type");
|
|
1256
|
-
inputPopupCodec = document.getElementById("input-popup-codec");
|
|
1257
|
-
inputPopupText = document.getElementById("input-popup-text");
|
|
1258
|
-
inputPopupStatus = document.getElementById("input-popup-status");
|
|
1259
|
-
inputPopupHint = document.getElementById("input-popup-hint");
|
|
1260
|
-
inputPopupCodecHint = document.getElementById("input-popup-codec-hint");
|
|
1261
|
-
inputSamplesList = document.getElementById("input-samples-list");
|
|
679
|
+
enabledCodecsGrid = document.getElementById("enabled-codecs-grid");
|
|
680
|
+
|
|
1262
681
|
|
|
1263
682
|
updatePrompt();
|
|
1264
683
|
|
|
@@ -1459,78 +878,7 @@ export function initRepl() {
|
|
|
1459
878
|
const closeCodecsBtn = document.getElementById("btn-close-codecs");
|
|
1460
879
|
if (closeCodecsBtn) closeCodecsBtn.onclick = closeCodecsModal;
|
|
1461
880
|
|
|
1462
|
-
// Toggle built-in codecs buttons
|
|
1463
|
-
["int", "utf8", "json", "ieee"].forEach(name => {
|
|
1464
|
-
const btn = document.getElementById(`btn-toggle-codec-${name}`);
|
|
1465
|
-
if (btn) {
|
|
1466
|
-
btn.onclick = () => {
|
|
1467
|
-
if (isCodecRegistered(name)) {
|
|
1468
|
-
executeCommand(`:codec unload ${name}`);
|
|
1469
|
-
} else {
|
|
1470
|
-
executeCommand(`:codec load ${name}`);
|
|
1471
|
-
}
|
|
1472
|
-
renderCodecsModal();
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
|
|
1477
|
-
// Codec Studio controls
|
|
1478
|
-
const presetSelect = document.getElementById("codec-preset-select");
|
|
1479
|
-
if (presetSelect) {
|
|
1480
|
-
presetSelect.addEventListener("change", () => {
|
|
1481
|
-
if (presetSelect.value) applyCodecPreset(presetSelect.value);
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
const btnRegisterCodec = document.getElementById("btn-register-codec");
|
|
1486
|
-
if (btnRegisterCodec) btnRegisterCodec.onclick = registerCustomCodecFromForm;
|
|
1487
|
-
|
|
1488
|
-
const btnTestParse = document.getElementById("btn-test-parse");
|
|
1489
|
-
if (btnTestParse) btnTestParse.onclick = testCustomParse;
|
|
1490
|
-
|
|
1491
|
-
const btnTestPrint = document.getElementById("btn-test-print");
|
|
1492
|
-
if (btnTestPrint) btnTestPrint.onclick = testCustomPrint;
|
|
1493
|
-
|
|
1494
|
-
const btnSaveCodecVfs = document.getElementById("btn-save-codec-vfs");
|
|
1495
|
-
if (btnSaveCodecVfs) btnSaveCodecVfs.onclick = saveCustomCodecToVfs;
|
|
1496
|
-
|
|
1497
|
-
// Codec Input Popup controls
|
|
1498
|
-
const btnInputNavbar = document.getElementById("btn-input-popup");
|
|
1499
|
-
if (btnInputNavbar) btnInputNavbar.onclick = () => openInputPopup();
|
|
1500
|
-
|
|
1501
|
-
const btnCloseInput = document.getElementById("btn-close-input-popup");
|
|
1502
|
-
if (btnCloseInput) btnCloseInput.onclick = cancelInputPopup;
|
|
1503
|
-
|
|
1504
|
-
const btnCancelInput = document.getElementById("btn-cancel-input");
|
|
1505
|
-
if (btnCancelInput) btnCancelInput.onclick = cancelInputPopup;
|
|
1506
881
|
|
|
1507
|
-
const btnSubmitInput = document.getElementById("btn-submit-input");
|
|
1508
|
-
if (btnSubmitInput) btnSubmitInput.onclick = submitInputPopup;
|
|
1509
|
-
|
|
1510
|
-
if (inputPopupType) {
|
|
1511
|
-
inputPopupType.addEventListener("change", updateInputPopupCodecs);
|
|
1512
|
-
inputPopupType.addEventListener("input", updateInputPopupCodecs);
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
if (inputPopupCodec) {
|
|
1516
|
-
inputPopupCodec.addEventListener("change", () => {
|
|
1517
|
-
renderInputSamples(inputPopupCodec.value);
|
|
1518
|
-
updateLiveValidation();
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
if (inputPopupText) {
|
|
1523
|
-
inputPopupText.addEventListener("input", updateLiveValidation);
|
|
1524
|
-
inputPopupText.addEventListener("keydown", (e) => {
|
|
1525
|
-
if (e.key === "Enter" && (e.ctrlKey || e.metaKey || !e.shiftKey)) {
|
|
1526
|
-
e.preventDefault();
|
|
1527
|
-
submitInputPopup();
|
|
1528
|
-
} else if (e.key === "Escape") {
|
|
1529
|
-
e.preventDefault();
|
|
1530
|
-
cancelInputPopup();
|
|
1531
|
-
}
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
882
|
|
|
1535
883
|
// Export .klib button
|
|
1536
884
|
const exportBtn = document.getElementById("btn-export");
|
|
@@ -1589,7 +937,6 @@ export function initRepl() {
|
|
|
1589
937
|
if (e.target === vfsModal) closeVfsModal();
|
|
1590
938
|
if (e.target === helpModal) helpModal.classList.remove("open");
|
|
1591
939
|
if (e.target === codecsModal) closeCodecsModal();
|
|
1592
|
-
if (e.target === inputPopupModal) cancelInputPopup();
|
|
1593
940
|
});
|
|
1594
941
|
|
|
1595
942
|
// Focus input when clicking anywhere in the input bar
|
|
@@ -1615,8 +962,15 @@ export function initRepl() {
|
|
|
1615
962
|
}
|
|
1616
963
|
});
|
|
1617
964
|
|
|
1618
|
-
//
|
|
1619
|
-
|
|
965
|
+
// Engine Badge toggle (wasm <-> js)
|
|
966
|
+
const engineBadge = document.getElementById("engine-badge");
|
|
967
|
+
if (engineBadge) {
|
|
968
|
+
engineBadge.onclick = () => {
|
|
969
|
+
const nextEngine = state.engine === "wasm" ? "js" : "wasm";
|
|
970
|
+
executeCommand(`:engine ${nextEngine}`);
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
updateEngineUI();
|
|
1620
974
|
|
|
1621
975
|
inputEl.focus();
|
|
1622
976
|
}
|
|
@@ -1634,14 +988,12 @@ if (typeof window !== "undefined") {
|
|
|
1634
988
|
executeCommand,
|
|
1635
989
|
initRepl,
|
|
1636
990
|
getState: () => state,
|
|
991
|
+
getEngine: () => state.engine,
|
|
992
|
+
setEngine: (engine) => executeCommand(`:engine ${engine}`),
|
|
1637
993
|
getVfsFile,
|
|
1638
994
|
setVfsFile,
|
|
1639
995
|
getAllVfsFiles,
|
|
1640
996
|
openCodecsModal,
|
|
1641
|
-
openInputPopup,
|
|
1642
|
-
closeInputPopup,
|
|
1643
|
-
submitInputPopup,
|
|
1644
|
-
cancelInputPopup,
|
|
1645
997
|
registerCodec,
|
|
1646
998
|
unregisterCodec
|
|
1647
999
|
};
|