@fraczak/k 6.8.0 → 6.9.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.
- package/CHANGELOG.md +10 -0
- package/DOCS/CODECS.md +80 -181
- package/DOCS/CODEC_ARCHITECTURE.md +194 -0
- package/DOCS/REPL.md +41 -39
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/browser/repl-browser.mjs +109 -782
- 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 +63 -187
- package/scripts/build-repl-html.mjs +87 -379
- 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 +123 -64
- package/tests/test-repl.mjs +120 -134
- 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);
|
|
@@ -324,12 +305,6 @@ export async function executeCommand(input) {
|
|
|
324
305
|
if (codecsModal && codecsModal.classList.contains("open")) {
|
|
325
306
|
renderCodecsModal();
|
|
326
307
|
}
|
|
327
|
-
|
|
328
|
-
// If :input <type> [codec] was executed, state.pendingInput is now set!
|
|
329
|
-
// Open the Input Popup immediately to prompt the user for the input string!
|
|
330
|
-
if (errors.length === 0 && state.pendingInput && trimmed.startsWith(":input ")) {
|
|
331
|
-
openInputPopup();
|
|
332
|
-
}
|
|
333
308
|
}
|
|
334
309
|
|
|
335
310
|
function adjustInputHeight() {
|
|
@@ -353,14 +328,7 @@ function handleAutocomplete() {
|
|
|
353
328
|
}
|
|
354
329
|
|
|
355
330
|
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();
|
|
331
|
+
applyAutocomplete(matches[0]);
|
|
364
332
|
return;
|
|
365
333
|
}
|
|
366
334
|
|
|
@@ -402,11 +370,12 @@ function applyAutocomplete(match) {
|
|
|
402
370
|
const text = inputEl.value;
|
|
403
371
|
const cursorPos = inputEl.selectionStart;
|
|
404
372
|
const lineBeforeCursor = text.slice(0, cursorPos).split("\n").pop();
|
|
405
|
-
const
|
|
406
|
-
const
|
|
373
|
+
const lineStart = cursorPos - lineBeforeCursor.length;
|
|
374
|
+
const beforeLine = text.slice(0, lineStart);
|
|
407
375
|
const afterCursor = text.slice(cursorPos);
|
|
408
|
-
|
|
409
|
-
inputEl.
|
|
376
|
+
const insertSpace = (match.endsWith("/") || afterCursor.startsWith(" ")) ? "" : " ";
|
|
377
|
+
inputEl.value = beforeLine + match + insertSpace + afterCursor;
|
|
378
|
+
inputEl.selectionStart = inputEl.selectionEnd = beforeLine.length + match.length + insertSpace.length;
|
|
410
379
|
hideAutocomplete();
|
|
411
380
|
adjustInputHeight();
|
|
412
381
|
inputEl.focus();
|
|
@@ -566,98 +535,6 @@ function exportKlib() {
|
|
|
566
535
|
// CODECS SUBSYSTEM UI & CONTROLS
|
|
567
536
|
// ==========================================
|
|
568
537
|
|
|
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
538
|
function openCodecsModal() {
|
|
662
539
|
if (!codecsModal) return;
|
|
663
540
|
renderCodecsModal();
|
|
@@ -670,567 +547,105 @@ function closeCodecsModal() {
|
|
|
670
547
|
|
|
671
548
|
function renderCodecsModal() {
|
|
672
549
|
updateCodecsBadge();
|
|
550
|
+
const grid = enabledCodecsGrid || document.getElementById("enabled-codecs-grid");
|
|
551
|
+
if (!grid) return;
|
|
552
|
+
grid.innerHTML = "";
|
|
673
553
|
|
|
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
554
|
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>`;
|
|
555
|
+
const entries = Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
|
|
556
|
+
|
|
557
|
+
if (entries.length === 0) {
|
|
558
|
+
const empty = document.createElement("div");
|
|
559
|
+
empty.className = "codecs-empty-state";
|
|
560
|
+
empty.style.gridColumn = "1 / -1";
|
|
561
|
+
empty.style.padding = "32px 16px";
|
|
562
|
+
empty.style.textAlign = "center";
|
|
563
|
+
empty.style.color = "var(--text-muted)";
|
|
564
|
+
empty.style.background = "var(--bg-base)";
|
|
565
|
+
empty.style.border = "1px dashed var(--border-color)";
|
|
566
|
+
empty.style.borderRadius = "6px";
|
|
567
|
+
empty.innerHTML = `
|
|
568
|
+
<div style="font-size: 24px; margin-bottom: 8px;">📦</div>
|
|
569
|
+
<div style="font-size: 13px; font-weight: 600; color: var(--text-main); margin-bottom: 6px;">No codecs currently loaded</div>
|
|
570
|
+
<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>
|
|
571
|
+
`;
|
|
572
|
+
grid.appendChild(empty);
|
|
821
573
|
return;
|
|
822
574
|
}
|
|
823
575
|
|
|
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
|
-
}
|
|
576
|
+
for (const entry of entries) {
|
|
577
|
+
const card = document.createElement("div");
|
|
578
|
+
card.className = "codec-card active";
|
|
579
|
+
card.id = `card-codec-${entry.name.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
580
|
+
|
|
581
|
+
const titleEl = document.createElement("div");
|
|
582
|
+
titleEl.className = "codec-card-title";
|
|
583
|
+
titleEl.innerHTML = `
|
|
584
|
+
<span class="codec-icon">📦</span>
|
|
585
|
+
<span class="codec-name">${escapeHtml(entry.name)}</span>
|
|
586
|
+
<span class="codec-status-badge active">Active</span>
|
|
587
|
+
`;
|
|
588
|
+
card.appendChild(titleEl);
|
|
589
|
+
|
|
590
|
+
let targetDesc = "Self-describing";
|
|
591
|
+
if (entry.name === "int.mjs") {
|
|
592
|
+
targetDesc = "Pattern Family: $ int & lists";
|
|
593
|
+
} else if (entry.name === "utf8.mjs") {
|
|
594
|
+
targetDesc = "$ string";
|
|
595
|
+
} else if (entry.name === "ieee.mjs") {
|
|
596
|
+
targetDesc = "$ float64";
|
|
597
|
+
} else if (entry.name === "unit.mjs") {
|
|
598
|
+
targetDesc = "{}";
|
|
599
|
+
} else if (entry.name === "json.mjs") {
|
|
600
|
+
targetDesc = "Self-describing JSON";
|
|
601
|
+
} else if (Array.isArray(entry.patterns) && entry.patterns.length > 0) {
|
|
602
|
+
targetDesc = `Pattern Family (${entry.patterns.length})`;
|
|
603
|
+
} else if (entry.pattern) {
|
|
604
|
+
targetDesc = "Pattern Recipe";
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const typeDiv = document.createElement("div");
|
|
608
|
+
typeDiv.className = "codec-types";
|
|
609
|
+
typeDiv.innerHTML = `Target: <span class="badge-type">${escapeHtml(targetDesc)}</span>`;
|
|
610
|
+
card.appendChild(typeDiv);
|
|
611
|
+
|
|
612
|
+
const descDiv = document.createElement("div");
|
|
613
|
+
descDiv.className = "codec-desc";
|
|
614
|
+
const src = entry.source || entry.name;
|
|
615
|
+
descDiv.innerHTML = `Source: <code>${escapeHtml(src)}</code>`;
|
|
616
|
+
card.appendChild(descDiv);
|
|
617
|
+
|
|
618
|
+
const footer = document.createElement("div");
|
|
619
|
+
footer.className = "codec-card-footer";
|
|
620
|
+
|
|
621
|
+
const capsDiv = document.createElement("div");
|
|
622
|
+
capsDiv.className = "codec-caps";
|
|
623
|
+
const caps = [];
|
|
624
|
+
if (typeof entry.print === "function") caps.push('<span class="cap-badge print">Serializer</span>');
|
|
625
|
+
if (typeof entry.parse === "function") caps.push('<span class="cap-badge parse">Deserializer</span>');
|
|
626
|
+
capsDiv.innerHTML = caps.join(" ");
|
|
627
|
+
footer.appendChild(capsDiv);
|
|
628
|
+
|
|
629
|
+
const unloadBtn = document.createElement("button");
|
|
630
|
+
unloadBtn.className = "btn btn-sm btn-danger";
|
|
631
|
+
unloadBtn.textContent = "Unload";
|
|
632
|
+
unloadBtn.onclick = async () => {
|
|
633
|
+
await executeCommand(`:codec unload ${entry.name}`);
|
|
634
|
+
renderCodecsModal();
|
|
1008
635
|
};
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
636
|
+
footer.appendChild(unloadBtn);
|
|
1012
637
|
|
|
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
|
-
}
|
|
638
|
+
card.appendChild(footer);
|
|
639
|
+
grid.appendChild(card);
|
|
1030
640
|
}
|
|
1031
641
|
}
|
|
1032
642
|
|
|
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 = [];
|
|
643
|
+
function isCodecRegistered(name) {
|
|
1049
644
|
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();
|
|
1102
|
-
}
|
|
1103
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
645
|
+
return Boolean(store[name]);
|
|
1190
646
|
}
|
|
1191
647
|
|
|
1192
|
-
export async function submitInputPopup() {
|
|
1193
|
-
const text = inputPopupText?.value ?? "";
|
|
1194
|
-
const rawPattern = inputPopupType ? inputPopupType.value.trim() : "(...)";
|
|
1195
|
-
const codecName = inputPopupCodec?.value;
|
|
1196
648
|
|
|
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
649
|
|
|
1235
650
|
// Initialization on DOMContentLoaded
|
|
1236
651
|
export function initRepl() {
|
|
@@ -1248,17 +663,8 @@ export function initRepl() {
|
|
|
1248
663
|
dropOverlay = document.getElementById("drop-overlay");
|
|
1249
664
|
codecsModal = document.getElementById("codecs-modal");
|
|
1250
665
|
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");
|
|
666
|
+
enabledCodecsGrid = document.getElementById("enabled-codecs-grid");
|
|
667
|
+
|
|
1262
668
|
|
|
1263
669
|
updatePrompt();
|
|
1264
670
|
|
|
@@ -1459,78 +865,7 @@ export function initRepl() {
|
|
|
1459
865
|
const closeCodecsBtn = document.getElementById("btn-close-codecs");
|
|
1460
866
|
if (closeCodecsBtn) closeCodecsBtn.onclick = closeCodecsModal;
|
|
1461
867
|
|
|
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
868
|
|
|
1504
|
-
const btnCancelInput = document.getElementById("btn-cancel-input");
|
|
1505
|
-
if (btnCancelInput) btnCancelInput.onclick = cancelInputPopup;
|
|
1506
|
-
|
|
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
869
|
|
|
1535
870
|
// Export .klib button
|
|
1536
871
|
const exportBtn = document.getElementById("btn-export");
|
|
@@ -1589,7 +924,6 @@ export function initRepl() {
|
|
|
1589
924
|
if (e.target === vfsModal) closeVfsModal();
|
|
1590
925
|
if (e.target === helpModal) helpModal.classList.remove("open");
|
|
1591
926
|
if (e.target === codecsModal) closeCodecsModal();
|
|
1592
|
-
if (e.target === inputPopupModal) cancelInputPopup();
|
|
1593
927
|
});
|
|
1594
928
|
|
|
1595
929
|
// Focus input when clicking anywhere in the input bar
|
|
@@ -1615,9 +949,6 @@ export function initRepl() {
|
|
|
1615
949
|
}
|
|
1616
950
|
});
|
|
1617
951
|
|
|
1618
|
-
// Apply initial yn preset to custom form
|
|
1619
|
-
applyCodecPreset("yn");
|
|
1620
|
-
|
|
1621
952
|
inputEl.focus();
|
|
1622
953
|
}
|
|
1623
954
|
|
|
@@ -1638,10 +969,6 @@ if (typeof window !== "undefined") {
|
|
|
1638
969
|
setVfsFile,
|
|
1639
970
|
getAllVfsFiles,
|
|
1640
971
|
openCodecsModal,
|
|
1641
|
-
openInputPopup,
|
|
1642
|
-
closeInputPopup,
|
|
1643
|
-
submitInputPopup,
|
|
1644
|
-
cancelInputPopup,
|
|
1645
972
|
registerCodec,
|
|
1646
973
|
unregisterCodec
|
|
1647
974
|
};
|