@fraczak/k 6.7.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 +49 -60
- package/Examples/arithmetics.k +1 -0
- package/Examples/poly.k +46 -0
- package/TODO.md +112 -0
- package/browser/repl-browser.mjs +112 -733
- package/browser/shims/process.mjs +7 -0
- package/browser/tree-view.mjs +5 -2
- package/browser/vfs-data.mjs +14 -12
- 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 +78 -184
- package/repl.mjs +168 -226
- package/scripts/build-repl-html.mjs +273 -424
- 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 +190 -53
- package/tests/test-repl.mjs +150 -114
- 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
|
@@ -3,30 +3,36 @@ 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
|
-
|
|
18
|
-
|
|
14
|
+
formatDuration,
|
|
15
|
+
resolveInputTypeHash,
|
|
16
|
+
resolveInputPattern,
|
|
17
|
+
valueForPattern,
|
|
18
|
+
codecNames,
|
|
19
|
+
ensureEnveloped,
|
|
20
|
+
patternFromFilter
|
|
19
21
|
} from "../repl.mjs";
|
|
20
|
-
import { Value, isProduct, isVariant } from "../Value.mjs";
|
|
22
|
+
import { Value, isProduct, isVariant, withPattern } from "../Value.mjs";
|
|
21
23
|
import { encodeLibrary } from "../object.mjs";
|
|
22
24
|
import { setVfsFile, getVfsFile, getAllVfsFiles } from "./shims/fs.mjs";
|
|
23
25
|
import { createOutputEntryElement } from "./tree-view.mjs";
|
|
24
26
|
|
|
25
|
-
// Make Value and helpers globally available
|
|
27
|
+
// Make Value and helpers globally available in browser console
|
|
26
28
|
if (typeof window !== "undefined") {
|
|
27
|
-
window
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
Object.assign(window, {
|
|
30
|
+
Value,
|
|
31
|
+
isProduct,
|
|
32
|
+
isVariant,
|
|
33
|
+
withPattern,
|
|
34
|
+
patternFromFilter
|
|
35
|
+
});
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// Global REPL state
|
|
@@ -61,17 +67,8 @@ let helpModal;
|
|
|
61
67
|
let dropOverlay;
|
|
62
68
|
let codecsModal;
|
|
63
69
|
let codecsBadgeEl;
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Input Popup Modal Elements
|
|
68
|
-
let inputPopupModal;
|
|
69
|
-
let inputPopupType;
|
|
70
|
-
let inputPopupCodec;
|
|
71
|
-
let inputPopupText;
|
|
72
|
-
let inputPopupStatus;
|
|
73
|
-
let inputPopupHint;
|
|
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,97 +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: "bool",
|
|
573
|
-
universal: false,
|
|
574
|
-
parse: `// Parse 'yes', 'no', 'true', 'false' into $bool
|
|
575
|
-
const tag = text.trim().toLowerCase();
|
|
576
|
-
if (tag === "yes" || tag === "true" || tag === "1") {
|
|
577
|
-
return Value.variant("true", Value.product({}));
|
|
578
|
-
}
|
|
579
|
-
if (tag === "no" || tag === "false" || tag === "0") {
|
|
580
|
-
return Value.variant("false", Value.product({}));
|
|
581
|
-
}
|
|
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";`
|
|
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 or Buffer
|
|
656
|
-
return JSON.stringify(value);`
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
|
|
660
538
|
function openCodecsModal() {
|
|
661
539
|
if (!codecsModal) return;
|
|
662
540
|
renderCodecsModal();
|
|
@@ -669,501 +547,105 @@ function closeCodecsModal() {
|
|
|
669
547
|
|
|
670
548
|
function renderCodecsModal() {
|
|
671
549
|
updateCodecsBadge();
|
|
550
|
+
const grid = enabledCodecsGrid || document.getElementById("enabled-codecs-grid");
|
|
551
|
+
if (!grid) return;
|
|
552
|
+
grid.innerHTML = "";
|
|
672
553
|
|
|
673
|
-
// 1. Built-in codec buttons state
|
|
674
|
-
const standardCodecs = ["int", "utf8", "json", "ieee"];
|
|
675
|
-
for (const name of standardCodecs) {
|
|
676
|
-
const btn = document.getElementById(`btn-toggle-codec-${name}`);
|
|
677
|
-
const badge = document.getElementById(`codec-badge-${name}`);
|
|
678
|
-
const isLoaded = isCodecRegistered(name);
|
|
679
|
-
if (btn) {
|
|
680
|
-
btn.textContent = isLoaded ? "Unload" : "Load";
|
|
681
|
-
btn.className = isLoaded ? "btn btn-sm btn-secondary" : "btn btn-sm btn-primary";
|
|
682
|
-
}
|
|
683
|
-
if (badge) {
|
|
684
|
-
badge.textContent = isLoaded ? "Active" : "Inactive";
|
|
685
|
-
badge.className = isLoaded ? "codec-status-badge active" : "codec-status-badge";
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// 2. Populate target type dropdown
|
|
690
|
-
if (codecTypeSelect) {
|
|
691
|
-
const prev = codecTypeSelect.value;
|
|
692
|
-
codecTypeSelect.innerHTML = `<option value="">-- Choose Type Alias or Code Hash --</option>`;
|
|
693
|
-
const typeGroup = document.createElement("optgroup");
|
|
694
|
-
typeGroup.label = "Available Type Aliases in State";
|
|
695
|
-
const aliases = Object.entries(state.typeAliases || {}).sort(([a], [b]) => a.localeCompare(b));
|
|
696
|
-
for (const [alias, hash] of aliases) {
|
|
697
|
-
const opt = document.createElement("option");
|
|
698
|
-
opt.value = alias;
|
|
699
|
-
opt.textContent = `$${alias} (${hash.slice(0, 10)}...)`;
|
|
700
|
-
typeGroup.appendChild(opt);
|
|
701
|
-
}
|
|
702
|
-
codecTypeSelect.appendChild(typeGroup);
|
|
703
|
-
|
|
704
|
-
const universalOpt = document.createElement("option");
|
|
705
|
-
universalOpt.value = "*";
|
|
706
|
-
universalOpt.textContent = "* (Universal - all types)";
|
|
707
|
-
codecTypeSelect.appendChild(universalOpt);
|
|
708
|
-
|
|
709
|
-
if (prev) codecTypeSelect.value = prev;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// 3. Render active codecs table
|
|
713
|
-
if (activeCodecsTbody) {
|
|
714
|
-
activeCodecsTbody.innerHTML = "";
|
|
715
|
-
const store = state.codecs || {};
|
|
716
|
-
const entries = [];
|
|
717
|
-
for (const [codeHash, list] of Object.entries(store)) {
|
|
718
|
-
for (const c of list) {
|
|
719
|
-
entries.push({ codeHash, ...c });
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if (entries.length === 0) {
|
|
724
|
-
const tr = document.createElement("tr");
|
|
725
|
-
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>`;
|
|
726
|
-
activeCodecsTbody.appendChild(tr);
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// Invert type aliases for display
|
|
731
|
-
const hashToAlias = {};
|
|
732
|
-
for (const [name, hash] of Object.entries(state.typeAliases || {})) {
|
|
733
|
-
hashToAlias[hash] = name;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
for (const entry of entries) {
|
|
737
|
-
const tr = document.createElement("tr");
|
|
738
|
-
|
|
739
|
-
const nameTd = document.createElement("td");
|
|
740
|
-
nameTd.className = "codec-cell-name";
|
|
741
|
-
nameTd.textContent = entry.name;
|
|
742
|
-
tr.appendChild(nameTd);
|
|
743
|
-
|
|
744
|
-
const typeTd = document.createElement("td");
|
|
745
|
-
typeTd.className = "codec-cell-type";
|
|
746
|
-
if (entry.codeHash === "*") {
|
|
747
|
-
typeTd.innerHTML = `<span class="badge-type universal">Universal (*)</span>`;
|
|
748
|
-
} else {
|
|
749
|
-
const alias = hashToAlias[entry.codeHash];
|
|
750
|
-
typeTd.innerHTML = `<span class="badge-type">${alias ? `$${alias} ` : ""}<code title="${entry.codeHash}">${entry.codeHash.slice(0, 10)}...</code></span>`;
|
|
751
|
-
}
|
|
752
|
-
tr.appendChild(typeTd);
|
|
753
|
-
|
|
754
|
-
const capTd = document.createElement("td");
|
|
755
|
-
const caps = [];
|
|
756
|
-
if (typeof entry.print === "function") caps.push(`<span class="cap-badge print">Serializer</span>`);
|
|
757
|
-
if (typeof entry.parse === "function") caps.push(`<span class="cap-badge parse">Deserializer</span>`);
|
|
758
|
-
capTd.innerHTML = caps.join(" ");
|
|
759
|
-
tr.appendChild(capTd);
|
|
760
|
-
|
|
761
|
-
const srcTd = document.createElement("td");
|
|
762
|
-
srcTd.className = "codec-cell-src";
|
|
763
|
-
srcTd.textContent = entry.source ? (entry.source === "built-in" ? "Built-in" : entry.source.split("/").pop()) : "Custom";
|
|
764
|
-
tr.appendChild(srcTd);
|
|
765
|
-
|
|
766
|
-
const actTd = document.createElement("td");
|
|
767
|
-
const unloadBtn = document.createElement("button");
|
|
768
|
-
unloadBtn.className = "btn btn-sm btn-danger";
|
|
769
|
-
unloadBtn.textContent = "Unload";
|
|
770
|
-
unloadBtn.onclick = () => {
|
|
771
|
-
executeCommand(`:codec unload ${entry.name}`);
|
|
772
|
-
renderCodecsModal();
|
|
773
|
-
};
|
|
774
|
-
actTd.appendChild(unloadBtn);
|
|
775
|
-
tr.appendChild(actTd);
|
|
776
|
-
|
|
777
|
-
activeCodecsTbody.appendChild(tr);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function isCodecRegistered(name) {
|
|
783
554
|
const store = state.codecs || {};
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
codecTypeSelect.value = preset.type;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function registerCustomCodecFromForm() {
|
|
808
|
-
const nameInput = document.getElementById("codec-custom-name");
|
|
809
|
-
const parseArea = document.getElementById("codec-custom-parse");
|
|
810
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
811
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
812
|
-
|
|
813
|
-
const name = nameInput?.value.trim();
|
|
814
|
-
const rawType = codecTypeSelect?.value.trim() || "*";
|
|
815
|
-
const parseCode = parseArea?.value.trim();
|
|
816
|
-
const printCode = printArea?.value.trim();
|
|
817
|
-
|
|
818
|
-
if (!name) {
|
|
819
|
-
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);
|
|
820
573
|
return;
|
|
821
574
|
}
|
|
822
575
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
if (
|
|
843
|
-
|
|
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
|
-
if (statusBox) {
|
|
883
|
-
statusBox.innerHTML = `<span class="line-error">Parse Error: ${escapeHtml(err.message)}</span>`;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function testCustomPrint() {
|
|
889
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
890
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
891
|
-
|
|
892
|
-
try {
|
|
893
|
-
const printFn = new Function(
|
|
894
|
-
"value", "context",
|
|
895
|
-
`const { Value, isProduct, isVariant } = globalThis;\n${printArea.value}`
|
|
896
|
-
);
|
|
897
|
-
const result = printFn(state.value, { Value, isProduct, isVariant, state });
|
|
898
|
-
if (statusBox) {
|
|
899
|
-
statusBox.innerHTML = `<span class="line-output"><b>Print Output:</b> ${escapeHtml(String(result))}</span>`;
|
|
900
|
-
}
|
|
901
|
-
} catch (err) {
|
|
902
|
-
if (statusBox) {
|
|
903
|
-
statusBox.innerHTML = `<span class="line-error">Print Error: ${escapeHtml(err.message)}</span>`;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function saveCustomCodecToVfs() {
|
|
909
|
-
const nameInput = document.getElementById("codec-custom-name");
|
|
910
|
-
const parseArea = document.getElementById("codec-custom-parse");
|
|
911
|
-
const printArea = document.getElementById("codec-custom-print");
|
|
912
|
-
const statusBox = document.getElementById("codec-studio-status");
|
|
913
|
-
|
|
914
|
-
const name = nameInput?.value.trim() || "custom";
|
|
915
|
-
const rawType = codecTypeSelect?.value.trim() || "*";
|
|
916
|
-
const codeHash = rawType === "*" ? "*" : state.typeAliases[rawType] || rawType;
|
|
917
|
-
const fileName = `codecs/${name}-codec.mjs`;
|
|
918
|
-
|
|
919
|
-
const fileContent = `// Custom K Codec: ${name}
|
|
920
|
-
import { Value, isProduct, isVariant } from "../Value.mjs";
|
|
921
|
-
|
|
922
|
-
export const name = ${JSON.stringify(name)};
|
|
923
|
-
export const codes = [${JSON.stringify(codeHash)}];
|
|
924
|
-
export const universal = ${codeHash === "*"};
|
|
925
|
-
|
|
926
|
-
export function parse(text, context) {
|
|
927
|
-
${parseArea?.value || ""}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
export function print(value, context) {
|
|
931
|
-
${printArea?.value || ""}
|
|
932
|
-
}
|
|
933
|
-
`;
|
|
934
|
-
|
|
935
|
-
setVfsFile(fileName, fileContent);
|
|
936
|
-
if (statusBox) {
|
|
937
|
-
statusBox.innerHTML = `<span class="line-output">💾 Saved codec file to VFS as <b>${fileName}</b></span>`;
|
|
938
|
-
}
|
|
939
|
-
appendSystemMessage(`💾 Saved codec file to VFS: <b>${fileName}</b>. You can reload it anytime via <code>:codec load ${fileName}</code>.`);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// ==========================================
|
|
943
|
-
// CODEC INPUT POPUP SUBSYSTEM (:input)
|
|
944
|
-
// ==========================================
|
|
945
|
-
|
|
946
|
-
const CODEC_SAMPLES = {
|
|
947
|
-
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]'],
|
|
950
|
-
ieee: ["0.0", "3.14159", "-2.718", "1e6"],
|
|
951
|
-
yn: ["yes", "no", "true", "false"],
|
|
952
|
-
hex: ["0x2A", "0xFF", "0x1000", "-0x10"],
|
|
953
|
-
currency: ["$10.00", "$42.50", "$99.99"]
|
|
954
|
-
};
|
|
955
|
-
|
|
956
|
-
export function openInputPopup(preselectedType = null, preselectedCodec = null) {
|
|
957
|
-
if (!inputPopupModal) return;
|
|
958
|
-
|
|
959
|
-
populateInputPopupDropdowns(preselectedType, preselectedCodec);
|
|
960
|
-
|
|
961
|
-
if (inputPopupText) {
|
|
962
|
-
inputPopupText.value = "";
|
|
963
|
-
}
|
|
964
|
-
updateLiveValidation();
|
|
965
|
-
|
|
966
|
-
inputPopupModal.classList.add("open");
|
|
967
|
-
setTimeout(() => {
|
|
968
|
-
if (inputPopupText) inputPopupText.focus();
|
|
969
|
-
}, 50);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
export function closeInputPopup() {
|
|
973
|
-
if (inputPopupModal) {
|
|
974
|
-
inputPopupModal.classList.remove("open");
|
|
975
|
-
}
|
|
976
|
-
if (inputEl) inputEl.focus();
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
export function cancelInputPopup() {
|
|
980
|
-
if (state.pendingInput) {
|
|
981
|
-
state.pendingInput = null;
|
|
982
|
-
updatePrompt();
|
|
983
|
-
appendSystemMessage("Input cancelled.");
|
|
984
|
-
}
|
|
985
|
-
closeInputPopup();
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function renderInputSamples(codecName) {
|
|
989
|
-
if (!inputSamplesList) return;
|
|
990
|
-
inputSamplesList.innerHTML = "";
|
|
991
|
-
const samples = CODEC_SAMPLES[codecName] || ["()"];
|
|
992
|
-
for (const s of samples) {
|
|
993
|
-
const btn = document.createElement("span");
|
|
994
|
-
btn.className = "sample-pill";
|
|
995
|
-
btn.textContent = s;
|
|
996
|
-
btn.title = `Click to insert sample "${s}"`;
|
|
997
|
-
btn.onclick = () => {
|
|
998
|
-
if (inputPopupText) {
|
|
999
|
-
inputPopupText.value = s;
|
|
1000
|
-
inputPopupText.focus();
|
|
1001
|
-
updateLiveValidation();
|
|
1002
|
-
}
|
|
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();
|
|
1003
635
|
};
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
636
|
+
footer.appendChild(unloadBtn);
|
|
1007
637
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
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>:`;
|
|
1014
|
-
} else {
|
|
1015
|
-
inputPopupHint.innerHTML = `Choose target type & deserializer, then enter input string:`;
|
|
638
|
+
card.appendChild(footer);
|
|
639
|
+
grid.appendChild(card);
|
|
1016
640
|
}
|
|
1017
641
|
}
|
|
1018
642
|
|
|
1019
|
-
function
|
|
1020
|
-
if (!inputPopupCodec) return;
|
|
1021
|
-
const rawType = inputPopupType?.value || "*";
|
|
1022
|
-
const codeHash = rawType === "*" ? "*" : state.typeAliases?.[rawType] || rawType;
|
|
1023
|
-
const prev = inputPopupCodec.value;
|
|
1024
|
-
|
|
1025
|
-
inputPopupCodec.innerHTML = "";
|
|
1026
|
-
const matchingCodecs = [];
|
|
643
|
+
function isCodecRegistered(name) {
|
|
1027
644
|
const store = state.codecs || {};
|
|
1028
|
-
|
|
1029
|
-
const candidates = [
|
|
1030
|
-
...(store[codeHash] || []),
|
|
1031
|
-
...(codeHash !== "*" ? (store["*"] || []) : [])
|
|
1032
|
-
];
|
|
1033
|
-
|
|
1034
|
-
const seen = new Set();
|
|
1035
|
-
for (const c of candidates) {
|
|
1036
|
-
if (typeof c.parse === "function" && !seen.has(c.name)) {
|
|
1037
|
-
seen.add(c.name);
|
|
1038
|
-
matchingCodecs.push(c.name);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
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");
|
|
1046
|
-
matchingCodecs.push("json");
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
for (const name of matchingCodecs) {
|
|
1050
|
-
const opt = document.createElement("option");
|
|
1051
|
-
opt.value = name;
|
|
1052
|
-
opt.textContent = name;
|
|
1053
|
-
inputPopupCodec.appendChild(opt);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
if (prev && matchingCodecs.includes(prev)) {
|
|
1057
|
-
inputPopupCodec.value = prev;
|
|
1058
|
-
} else if (matchingCodecs.length > 0) {
|
|
1059
|
-
inputPopupCodec.value = matchingCodecs[0];
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
renderInputSamples(inputPopupCodec.value);
|
|
1063
|
-
updateInputPopupHint();
|
|
1064
|
-
updateLiveValidation();
|
|
645
|
+
return Boolean(store[name]);
|
|
1065
646
|
}
|
|
1066
647
|
|
|
1067
|
-
function populateInputPopupDropdowns(preselectedType = null, preselectedCodec = null) {
|
|
1068
|
-
if (!inputPopupType) return;
|
|
1069
|
-
|
|
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;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
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);
|
|
1087
|
-
}
|
|
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];
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
updateInputPopupCodecs();
|
|
1101
648
|
|
|
1102
|
-
const targetCodec = preselectedCodec || state.pendingInput?.codecName;
|
|
1103
|
-
if (targetCodec && inputPopupCodec) {
|
|
1104
|
-
inputPopupCodec.value = targetCodec;
|
|
1105
|
-
renderInputSamples(targetCodec);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
function updateLiveValidation() {
|
|
1110
|
-
if (!inputPopupStatus) return;
|
|
1111
|
-
const text = inputPopupText ? inputPopupText.value : "";
|
|
1112
|
-
if (!text) {
|
|
1113
|
-
inputPopupStatus.innerHTML = `<span style="color:var(--text-muted);font-size:11px;">Ready for input.</span>`;
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
const rawType = inputPopupType?.value;
|
|
1118
|
-
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>`;
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
const codeHash = rawType === "*" ? "*" : state.typeAliases?.[rawType] || rawType;
|
|
1125
|
-
try {
|
|
1126
|
-
const codec = resolveCodec(state, codeHash, codecName || null, "parse");
|
|
1127
|
-
const pattern = codeHashToPattern(codeHash, (h) => state.codes?.[h]);
|
|
1128
|
-
const parsed = codec.parse(text, {
|
|
1129
|
-
codeHash,
|
|
1130
|
-
pattern,
|
|
1131
|
-
state,
|
|
1132
|
-
Value,
|
|
1133
|
-
isProduct,
|
|
1134
|
-
isVariant
|
|
1135
|
-
});
|
|
1136
|
-
const repr = parsed instanceof Value ? parsed.toJSON() : (typeof parsed === "object" ? JSON.stringify(parsed) : String(parsed));
|
|
1137
|
-
inputPopupStatus.innerHTML = `<span class="line-output" style="margin:0;padding:2px 6px;">✓ Valid: <code>${escapeHtml(String(repr))}</code></span>`;
|
|
1138
|
-
} catch (err) {
|
|
1139
|
-
inputPopupStatus.innerHTML = `<span class="line-warning" style="margin:0;padding:2px 6px;">⚠ ${escapeHtml(err.message || String(err))}</span>`;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
export async function submitInputPopup() {
|
|
1144
|
-
const text = inputPopupText?.value ?? "";
|
|
1145
|
-
|
|
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;
|
|
1155
|
-
try {
|
|
1156
|
-
const codec = resolveCodec(state, codeHash, codecName || null, "parse");
|
|
1157
|
-
state.pendingInput = { codeHash, codecName: codec.name, promptName: codec.name };
|
|
1158
|
-
} catch (err) {
|
|
1159
|
-
if (inputPopupStatus) inputPopupStatus.innerHTML = `<span class="line-error">${escapeHtml(err.message)}</span>`;
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
closeInputPopup();
|
|
1165
|
-
await executeCommand(text);
|
|
1166
|
-
}
|
|
1167
649
|
|
|
1168
650
|
// Initialization on DOMContentLoaded
|
|
1169
651
|
export function initRepl() {
|
|
@@ -1181,32 +663,15 @@ export function initRepl() {
|
|
|
1181
663
|
dropOverlay = document.getElementById("drop-overlay");
|
|
1182
664
|
codecsModal = document.getElementById("codecs-modal");
|
|
1183
665
|
codecsBadgeEl = document.getElementById("codecs-count-badge");
|
|
1184
|
-
|
|
1185
|
-
codecTypeSelect = document.getElementById("codec-custom-type");
|
|
666
|
+
enabledCodecsGrid = document.getElementById("enabled-codecs-grid");
|
|
1186
667
|
|
|
1187
|
-
inputPopupModal = document.getElementById("input-popup-modal");
|
|
1188
|
-
inputPopupType = document.getElementById("input-popup-type");
|
|
1189
|
-
inputPopupCodec = document.getElementById("input-popup-codec");
|
|
1190
|
-
inputPopupText = document.getElementById("input-popup-text");
|
|
1191
|
-
inputPopupStatus = document.getElementById("input-popup-status");
|
|
1192
|
-
inputPopupHint = document.getElementById("input-popup-hint");
|
|
1193
|
-
inputSamplesList = document.getElementById("input-samples-list");
|
|
1194
668
|
|
|
1195
669
|
updatePrompt();
|
|
1196
670
|
|
|
1197
671
|
// Welcome message
|
|
1198
672
|
appendSystemMessage(`
|
|
1199
673
|
<div class="welcome-banner">
|
|
1200
|
-
<div class="welcome-title"
|
|
1201
|
-
<div class="welcome-desc">First-order partial functions over algebraic data types • WebAssembly execution engine • Custom Serializers & 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>
|
|
674
|
+
<div class="welcome-title">k interactive repl</div>
|
|
1210
675
|
</div>
|
|
1211
676
|
`, false, true);
|
|
1212
677
|
|
|
@@ -1365,14 +830,6 @@ export function initRepl() {
|
|
|
1365
830
|
const clearBtn = document.getElementById("btn-clear");
|
|
1366
831
|
if (clearBtn) clearBtn.onclick = clearOutput;
|
|
1367
832
|
|
|
1368
|
-
// Reset button
|
|
1369
|
-
const resetBtn = document.getElementById("btn-reset");
|
|
1370
|
-
if (resetBtn) {
|
|
1371
|
-
resetBtn.onclick = () => {
|
|
1372
|
-
executeCommand(":reset");
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
833
|
// Help button
|
|
1377
834
|
const helpBtn = document.getElementById("btn-help");
|
|
1378
835
|
if (helpBtn) {
|
|
@@ -1408,77 +865,7 @@ export function initRepl() {
|
|
|
1408
865
|
const closeCodecsBtn = document.getElementById("btn-close-codecs");
|
|
1409
866
|
if (closeCodecsBtn) closeCodecsBtn.onclick = closeCodecsModal;
|
|
1410
867
|
|
|
1411
|
-
// Toggle built-in codecs buttons
|
|
1412
|
-
["int", "utf8", "json", "ieee"].forEach(name => {
|
|
1413
|
-
const btn = document.getElementById(`btn-toggle-codec-${name}`);
|
|
1414
|
-
if (btn) {
|
|
1415
|
-
btn.onclick = () => {
|
|
1416
|
-
if (isCodecRegistered(name)) {
|
|
1417
|
-
executeCommand(`:codec unload ${name}`);
|
|
1418
|
-
} else {
|
|
1419
|
-
executeCommand(`:codec load ${name}`);
|
|
1420
|
-
}
|
|
1421
|
-
renderCodecsModal();
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
});
|
|
1425
|
-
|
|
1426
|
-
// Codec Studio controls
|
|
1427
|
-
const presetSelect = document.getElementById("codec-preset-select");
|
|
1428
|
-
if (presetSelect) {
|
|
1429
|
-
presetSelect.addEventListener("change", () => {
|
|
1430
|
-
if (presetSelect.value) applyCodecPreset(presetSelect.value);
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
const btnRegisterCodec = document.getElementById("btn-register-codec");
|
|
1435
|
-
if (btnRegisterCodec) btnRegisterCodec.onclick = registerCustomCodecFromForm;
|
|
1436
|
-
|
|
1437
|
-
const btnTestParse = document.getElementById("btn-test-parse");
|
|
1438
|
-
if (btnTestParse) btnTestParse.onclick = testCustomParse;
|
|
1439
|
-
|
|
1440
|
-
const btnTestPrint = document.getElementById("btn-test-print");
|
|
1441
|
-
if (btnTestPrint) btnTestPrint.onclick = testCustomPrint;
|
|
1442
|
-
|
|
1443
|
-
const btnSaveCodecVfs = document.getElementById("btn-save-codec-vfs");
|
|
1444
|
-
if (btnSaveCodecVfs) btnSaveCodecVfs.onclick = saveCustomCodecToVfs;
|
|
1445
|
-
|
|
1446
|
-
// Codec Input Popup controls
|
|
1447
|
-
const btnInputNavbar = document.getElementById("btn-input-popup");
|
|
1448
|
-
if (btnInputNavbar) btnInputNavbar.onclick = () => openInputPopup();
|
|
1449
|
-
|
|
1450
|
-
const btnCloseInput = document.getElementById("btn-close-input-popup");
|
|
1451
|
-
if (btnCloseInput) btnCloseInput.onclick = cancelInputPopup;
|
|
1452
|
-
|
|
1453
|
-
const btnCancelInput = document.getElementById("btn-cancel-input");
|
|
1454
|
-
if (btnCancelInput) btnCancelInput.onclick = cancelInputPopup;
|
|
1455
868
|
|
|
1456
|
-
const btnSubmitInput = document.getElementById("btn-submit-input");
|
|
1457
|
-
if (btnSubmitInput) btnSubmitInput.onclick = submitInputPopup;
|
|
1458
|
-
|
|
1459
|
-
if (inputPopupType) {
|
|
1460
|
-
inputPopupType.addEventListener("change", updateInputPopupCodecs);
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
if (inputPopupCodec) {
|
|
1464
|
-
inputPopupCodec.addEventListener("change", () => {
|
|
1465
|
-
renderInputSamples(inputPopupCodec.value);
|
|
1466
|
-
updateLiveValidation();
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
if (inputPopupText) {
|
|
1471
|
-
inputPopupText.addEventListener("input", updateLiveValidation);
|
|
1472
|
-
inputPopupText.addEventListener("keydown", (e) => {
|
|
1473
|
-
if (e.key === "Enter" && (e.ctrlKey || e.metaKey || !e.shiftKey)) {
|
|
1474
|
-
e.preventDefault();
|
|
1475
|
-
submitInputPopup();
|
|
1476
|
-
} else if (e.key === "Escape") {
|
|
1477
|
-
e.preventDefault();
|
|
1478
|
-
cancelInputPopup();
|
|
1479
|
-
}
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
869
|
|
|
1483
870
|
// Export .klib button
|
|
1484
871
|
const exportBtn = document.getElementById("btn-export");
|
|
@@ -1537,7 +924,6 @@ export function initRepl() {
|
|
|
1537
924
|
if (e.target === vfsModal) closeVfsModal();
|
|
1538
925
|
if (e.target === helpModal) helpModal.classList.remove("open");
|
|
1539
926
|
if (e.target === codecsModal) closeCodecsModal();
|
|
1540
|
-
if (e.target === inputPopupModal) cancelInputPopup();
|
|
1541
927
|
});
|
|
1542
928
|
|
|
1543
929
|
// Focus input when clicking anywhere in the input bar
|
|
@@ -1563,9 +949,6 @@ export function initRepl() {
|
|
|
1563
949
|
}
|
|
1564
950
|
});
|
|
1565
951
|
|
|
1566
|
-
// Apply initial yn preset to custom form
|
|
1567
|
-
applyCodecPreset("yn");
|
|
1568
|
-
|
|
1569
952
|
inputEl.focus();
|
|
1570
953
|
}
|
|
1571
954
|
|
|
@@ -1586,10 +969,6 @@ if (typeof window !== "undefined") {
|
|
|
1586
969
|
setVfsFile,
|
|
1587
970
|
getAllVfsFiles,
|
|
1588
971
|
openCodecsModal,
|
|
1589
|
-
openInputPopup,
|
|
1590
|
-
closeInputPopup,
|
|
1591
|
-
submitInputPopup,
|
|
1592
|
-
cancelInputPopup,
|
|
1593
972
|
registerCodec,
|
|
1594
973
|
unregisterCodec
|
|
1595
974
|
};
|