@fraczak/k 6.8.0 → 6.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CLAUDE.md +1 -1
  3. package/DOCS/BIG_PICTURE.md +3 -5
  4. package/DOCS/CODECS.md +80 -181
  5. package/DOCS/CODEC_ARCHITECTURE.md +194 -0
  6. package/DOCS/REPL.md +69 -51
  7. package/Examples/arithmetics.k +1 -0
  8. package/Examples/poly.k +46 -0
  9. package/backends/llvm/runtime/krt.c +4 -0
  10. package/backends/llvm/src/kvm2llvm.mjs +2 -2
  11. package/backends/llvm/tests/test-llvm.mjs +19 -0
  12. package/backends/wasm/src/kvm2wasm.mjs +4 -0
  13. package/backends/wasm/tests/test-wasm.mjs +4 -0
  14. package/browser/repl-browser.mjs +131 -779
  15. package/browser/shims/process.mjs +7 -0
  16. package/browser/tree-view.mjs +5 -2
  17. package/browser/vfs-data.mjs +14 -14
  18. package/codecs/README.md +7 -0
  19. package/codecs/ieee.mjs +50 -75
  20. package/codecs/int.mjs +83 -62
  21. package/codecs/json-codec.mjs +5 -77
  22. package/codecs/json.mjs +12 -56
  23. package/codecs/k-parse.mjs +3 -19
  24. package/codecs/runtime/codec-sdk.mjs +94 -0
  25. package/codecs/runtime/pattern-k.mjs +45 -0
  26. package/codecs/string-codec.mjs +48 -74
  27. package/codecs/unit.mjs +13 -61
  28. package/codecs/utf16.mjs +13 -56
  29. package/codecs/utf8.mjs +11 -55
  30. package/package.json +1 -1
  31. package/patterns/from-k.mjs +2 -22
  32. package/repl-codecs.mjs +69 -196
  33. package/repl.mjs +177 -281
  34. package/scripts/build-repl-html.mjs +114 -385
  35. package/scripts/run-tests.mjs +1 -0
  36. package/tests/test-codecs.mjs +186 -0
  37. package/tests/test-poly.mjs +44 -0
  38. package/tests/test-repl-html.mjs +163 -68
  39. package/tests/test-repl.mjs +210 -156
  40. package/tests/test-structural-values.mjs +17 -0
  41. package/tests/test-tree-view.mjs +3 -0
  42. package/codecs/example-pipeline.mjs +0 -163
  43. package/codecs/runtime/ieee-pattern.mjs +0 -30
  44. package/codecs/runtime/test-codec.mjs +0 -89
  45. package/codecs/runtime/typeFromValue.mjs +0 -34
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from "node:assert/strict";
4
+ import { spawnSync } from "node:child_process";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import * as SDK from "../codecs/runtime/codec-sdk.mjs";
9
+ import * as IntCodec from "../codecs/int.mjs";
10
+ import * as JsonCodec from "../codecs/json.mjs";
11
+ import * as Utf8Codec from "../codecs/utf8.mjs";
12
+ import * as Utf16Codec from "../codecs/utf16.mjs";
13
+ import * as IeeeCodec from "../codecs/ieee.mjs";
14
+ import * as UnitCodec from "../codecs/unit.mjs";
15
+
16
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
+
18
+ function runCLI(codecRelPath, args, input = null) {
19
+ const inputBuffer = typeof input === "string" ? Buffer.from(input, "utf8") : input;
20
+ const res = spawnSync(process.execPath, [path.join(root, codecRelPath), ...args], {
21
+ input: inputBuffer,
22
+ encoding: null
23
+ });
24
+ return {
25
+ status: res.status,
26
+ stdout: res.stdout,
27
+ stderr: res.stderr,
28
+ stdoutText: res.stdout ? res.stdout.toString("utf8") : "",
29
+ stderrText: res.stderr ? res.stderr.toString("utf8") : ""
30
+ };
31
+ }
32
+
33
+ console.log("=== Testing Codec Architecture & CLI Contract ===");
34
+
35
+ // 1. SDK Exports
36
+ console.log("1. Verifying SDK exports...");
37
+ assert.equal(typeof SDK.Value, "function");
38
+ assert.equal(typeof SDK.isProduct, "function");
39
+ assert.equal(typeof SDK.isVariant, "function");
40
+ assert.equal(typeof SDK.withPattern, "function");
41
+ assert.equal(typeof SDK.encodeToWire, "function");
42
+ assert.equal(typeof SDK.decodeWire, "function");
43
+ assert.equal(typeof SDK.runCodecCLI, "function");
44
+ assert.equal(typeof SDK.isMainEntrypoint, "function");
45
+ assert.equal(typeof SDK.patternFromFilter, "function");
46
+
47
+ // 2. In-memory Enveloped Value Contract
48
+ console.log("2. Verifying in-memory parse -> enveloped value (P, v)...");
49
+ for (const [name, codec, sampleInput] of [
50
+ ["int.mjs", IntCodec, "42"],
51
+ ["json.mjs", JsonCodec, '{"x": 1}'],
52
+ ["utf8.mjs", Utf8Codec, "hello"],
53
+ ["utf16.mjs", Utf16Codec, "hello"],
54
+ ["ieee.mjs", IeeeCodec, "3.14"],
55
+ ["unit.mjs", UnitCodec, undefined]
56
+ ]) {
57
+ assert.equal(typeof codec.parse, "function", `${name} must export parse()`);
58
+ assert.equal(typeof codec.print, "function", `${name} must export print()`);
59
+ const parsed = sampleInput !== undefined ? codec.parse(sampleInput) : codec.parse();
60
+ assert(parsed && typeof parsed === "object", `${name} parse() must return an object`);
61
+ assert(parsed.pattern, `${name} parse() must produce an enveloped value with .pattern`);
62
+ const printed = codec.print(parsed);
63
+ assert(printed !== undefined, `${name} print() must return a formatted result`);
64
+ }
65
+
66
+ // 3. CLI --help Flag
67
+ console.log("3. Verifying CLI --help flag...");
68
+ for (const relPath of [
69
+ "codecs/int.mjs",
70
+ "codecs/json.mjs",
71
+ "codecs/utf8.mjs",
72
+ "codecs/utf16.mjs",
73
+ "codecs/ieee.mjs",
74
+ "codecs/unit.mjs"
75
+ ]) {
76
+ const res = runCLI(relPath, ["--help"]);
77
+ assert.equal(res.status, 0, `${relPath} --help should exit 0`);
78
+ assert(res.stdoutText.includes("Usage:"), `${relPath} --help should output usage`);
79
+ assert(res.stdoutText.includes("--parse"), `${relPath} --help should mention --parse`);
80
+ assert(res.stdoutText.includes("--print"), `${relPath} --help should mention --print`);
81
+ }
82
+
83
+ // 4. CLI --parse and --print Pipelines
84
+ console.log("4. Verifying CLI --parse and --print pipelines...");
85
+
86
+ // int.mjs: single integer
87
+ {
88
+ const parseRes = runCLI("codecs/int.mjs", ["--parse"], "-12345\n");
89
+ assert.equal(parseRes.status, 0);
90
+ const printRes = runCLI("codecs/int.mjs", ["--print"], parseRes.stdout);
91
+ assert.equal(printRes.status, 0);
92
+ assert.equal(printRes.stdout.toString("utf8").trim(), "-12345");
93
+ }
94
+
95
+ // int.mjs: integer list
96
+ {
97
+ const parseRes = runCLI("codecs/int.mjs", ["--parse"], "[0, 1, -2, 3]\n");
98
+ assert.equal(parseRes.status, 0);
99
+ const printRes = runCLI("codecs/int.mjs", ["--print"], parseRes.stdout);
100
+ assert.equal(printRes.status, 0);
101
+ assert.equal(printRes.stdout.toString("utf8").trim(), "[0,1,-2,3]");
102
+ }
103
+
104
+ // json.mjs: complex JSON
105
+ {
106
+ const jsonInput = JSON.stringify({ name: "k", active: true, count: 42, tags: ["fast", "typed"] });
107
+ const parseRes = runCLI("codecs/json.mjs", ["--parse"], jsonInput);
108
+ assert.equal(parseRes.status, 0);
109
+ const printRes = runCLI("codecs/json.mjs", ["--print"], parseRes.stdout);
110
+ assert.equal(printRes.status, 0);
111
+ assert.deepEqual(JSON.parse(printRes.stdout.toString("utf8")), JSON.parse(jsonInput));
112
+ }
113
+
114
+ // utf8.mjs: unicode text
115
+ {
116
+ const utf8Input = "Hello 🌍, ąęćłó! \nSecond line.";
117
+ const parseRes = runCLI("codecs/utf8.mjs", ["--parse"], utf8Input);
118
+ assert.equal(parseRes.status, 0);
119
+ const printRes = runCLI("codecs/utf8.mjs", ["--print"], parseRes.stdout);
120
+ assert.equal(printRes.status, 0);
121
+ assert.equal(printRes.stdout.toString("utf8"), utf8Input);
122
+ }
123
+
124
+ // ieee.mjs: floating point
125
+ {
126
+ const parseRes = runCLI("codecs/ieee.mjs", ["--parse"], "2.71828\n");
127
+ assert.equal(parseRes.status, 0);
128
+ const printRes = runCLI("codecs/ieee.mjs", ["--print"], parseRes.stdout);
129
+ assert.equal(printRes.status, 0);
130
+ assert.equal(printRes.stdout.toString("utf8").trim(), "2.71828");
131
+ }
132
+
133
+ // unit.mjs: {}
134
+ {
135
+ const parseRes = runCLI("codecs/unit.mjs", ["--parse"], "");
136
+ assert.equal(parseRes.status, 0);
137
+ const printRes = runCLI("codecs/unit.mjs", ["--print"], parseRes.stdout);
138
+ assert.equal(printRes.status, 0);
139
+ assert.equal(printRes.stdout.toString("utf8").trim(), "{}");
140
+ }
141
+
142
+ // 5. Error Handling
143
+ console.log("5. Verifying CLI error handling...");
144
+ {
145
+ // Unknown flag
146
+ const res = runCLI("codecs/int.mjs", ["--unknown"]);
147
+ assert.equal(res.status, 1);
148
+ assert(res.stderrText.includes("Usage:"));
149
+ }
150
+
151
+ {
152
+ // Invalid parse syntax
153
+ const res = runCLI("codecs/int.mjs", ["--parse"], "not_a_number");
154
+ assert.equal(res.status, 1);
155
+ assert(res.stderrText.includes("Invalid integer syntax"));
156
+ }
157
+
158
+ {
159
+ // Invalid wire input to --print
160
+ const res = runCLI("codecs/int.mjs", ["--print"], Buffer.from([0x00, 0x01]));
161
+ assert.equal(res.status, 1);
162
+ }
163
+
164
+ // 6. Pattern Derivation via k Type System
165
+ console.log("6. Verifying pattern derivation via k type system (patternFromFilter)...");
166
+ {
167
+ const intPat = SDK.patternFromFilter('?< <bits 0, bits 1, {} _>=bits "+", bits "-">');
168
+ assert.deepEqual(IntCodec.INT_PATTERN, intPat);
169
+
170
+ const unitPat = SDK.patternFromFilter("?{}");
171
+ assert.deepEqual(UnitCodec.UNIT_PATTERN, unitPat);
172
+
173
+ const listPat = SDK.patternFromFilter(
174
+ '$ bits = < bits 0, bits 1, {} _ >; $ int = < bits "+", bits "-" >; ?< {} nil, { $int car, list cdr } cons > = list'
175
+ );
176
+ assert.deepEqual(IntCodec.INT_LIST_PATTERN, listPat);
177
+
178
+ // Verify caching returns identical structure
179
+ const cached = SDK.patternFromFilter("?{}");
180
+ assert.deepEqual(cached, unitPat);
181
+
182
+ // Verify error when script is not a filter or type expression
183
+ assert.throws(() => SDK.patternFromFilter(".field"), /Main expression must be a filter or a type name/);
184
+ }
185
+
186
+ console.log("OK - All codec tests passed.");
@@ -126,4 +126,48 @@ async function evalK(snippet) {
126
126
  console.log("Test 9 (zip): Passed");
127
127
  }
128
128
 
129
+ // Test 10: Split, Merge, and Merge Sort
130
+ {
131
+ const resNil = await evalK(`nil sort`);
132
+ assert.match(resNil, /\|nil/);
133
+
134
+ const resSingle = await evalK(`
135
+ l = {1 int car, nil cdr} cons;
136
+ l sort
137
+ `);
138
+ assert.match(resSingle, /1/);
139
+
140
+ const resSplit = await evalK(`
141
+ l4 = {1 int car, {2 int car, {3 int car, {4 int car, nil cdr} cons cdr} cons cdr} cons cdr} cons;
142
+ l4 split
143
+ `);
144
+ assert.match(resSplit, /first.*second/);
145
+
146
+ const resSorted = await evalK(`
147
+ gt_int? = { { .x /"+" x, .y /"+" y } GT if, () then } .then;
148
+ _merge = <
149
+ {.xs /nil if, .ys then} .then,
150
+ {.ys /nil if, .xs then} .then,
151
+ {
152
+ {.xs car x, .ys car y} gt_int? if,
153
+ {.ys car car, {.xs xs, .ys cdr ys} _merge cdr} cons then
154
+ } .then,
155
+ {.xs car car, {.xs cdr xs, .ys ys} _merge cdr} cons
156
+ >;
157
+ _sort = list? <
158
+ /nil nil,
159
+ /cons <
160
+ {.cdr /nil if, .car singleton then} .then,
161
+ |cons split {.first _sort xs, .second _sort ys} _merge
162
+ >
163
+ > list?;
164
+ l = {5 int car, {2 int car, {4 int car, {1 int car, {3 int car, nil cdr} cons cdr} cons cdr} cons cdr} cons cdr} cons;
165
+ l _sort
166
+ `);
167
+ assert.match(resSorted, /\|1.*\|0\|1.*\|1\|1.*\|0\|0\|1.*\|1\|0\|1/);
168
+ console.log("Test 10 (split, merge, sort): Passed");
169
+ }
170
+
129
171
  console.log("==> All Polymorphic List Tests Passed Successfully!");
172
+
173
+
@@ -37,10 +37,9 @@ assert(html.includes("wasm-in-process"), "repl.html must mention wasm-in-process
37
37
  assert(html.includes("arithmetics.k"), "repl.html must embed standard examples");
38
38
  assert(html.includes("btn-codecs"), "repl.html must contain Codecs button");
39
39
  assert(html.includes("codecs-modal"), "repl.html must contain Codecs modal");
40
- assert(html.includes("active-codecs-tbody"), "repl.html must contain active codecs table");
41
- assert(html.includes("codec-preset-select"), "repl.html must contain codec preset templates");
42
- assert(html.includes("btn-input-popup"), "repl.html must contain input button");
43
- assert(html.includes("input-popup-modal"), "repl.html must contain input popup modal");
40
+ assert(html.includes("enabled-codecs-grid"), "repl.html must contain enabled codecs grid");
41
+ assert(!html.includes("btn-input-popup"), "repl.html must NOT contain input button");
42
+ assert(!html.includes("input-popup-modal"), "repl.html must NOT contain input popup modal");
44
43
  console.log(" repl.html is valid, self-contained, and has size:", (html.length / 1024).toFixed(1), "KB");
45
44
 
46
45
  // 3. If Chromium is available, run end-to-end browser test
@@ -183,90 +182,127 @@ if (chromiumBin) {
183
182
  })()`);
184
183
  assert.strictEqual(codecsOut, "(none)");
185
184
 
186
- // Test :codec load int
185
+ // Test :codec load int.mjs
187
186
  const loadIntOut = await evaluateAsync(`(async () => {
188
- await window.kRepl.executeCommand(":codec load int");
187
+ await window.kRepl.executeCommand(":codec load int.mjs");
189
188
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
190
189
  return lines[lines.length - 1];
191
190
  })()`);
192
- assert(loadIntOut.includes("loaded codec int"), `Expected loaded codec int, got: ${loadIntOut}`);
191
+ assert(loadIntOut.includes("loaded codec int.mjs"), `Expected loaded codec int.mjs, got: ${loadIntOut}`);
193
192
 
194
193
  // Check codecs badge updated to 1
195
194
  const badgeCount = await evaluateAsync(`document.getElementById("codecs-count-badge")?.textContent`);
196
- assert.strictEqual(badgeCount, "1", "Codecs badge count should be 1 after loading int codec");
195
+ assert.strictEqual(badgeCount, "1", "Codecs badge count should be 1 after loading int.mjs codec");
197
196
 
198
- // Test evaluating 10 int with int codec active -> output should format using int serializer
197
+ // Test evaluating 10 int with int.mjs codec active -> output should format using int.mjs serializer
199
198
  const formattedVal = await evaluateAsync(`(async () => {
200
199
  await window.kRepl.executeCommand("10 int");
201
200
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
202
201
  return lines[lines.length - 1];
203
202
  })()`);
204
- assert(formattedVal.includes("int: 10"), `Expected 'int: 10' in output, got: ${formattedVal}`);
203
+ assert(formattedVal.includes("int.mjs: 10"), `Expected 'int.mjs: 10' in output, got: ${formattedVal}`);
205
204
 
206
- // Test defining custom codec via Custom Codec Studio UI
207
- await evaluateAsync(`window.kRepl.executeCommand("$ yes_no = < {} yes, {} no >;");`);
208
- const defineCodecOut = await evaluateAsync(`(async () => {
205
+ // Test modal interaction via openCodecsModal (only enabled codecs shown)
206
+ const modalCheck = await evaluateAsync(`(() => {
209
207
  window.kRepl.openCodecsModal();
210
- const typeSelect = document.getElementById("codec-custom-type");
211
- if (typeSelect) typeSelect.value = "yes_no";
212
- document.getElementById("btn-register-codec").click();
213
- return document.getElementById("codec-studio-status")?.textContent || "";
208
+ const modal = document.getElementById("codecs-modal");
209
+ const cardInt = document.getElementById("card-codec-int_mjs");
210
+ const grid = document.getElementById("enabled-codecs-grid");
211
+ const cards = grid ? Array.from(grid.querySelectorAll(".codec-card")) : [];
212
+
213
+ return {
214
+ open: modal?.classList.contains("open"),
215
+ hasIntCard: Boolean(cardInt),
216
+ cardCount: cards.length
217
+ };
214
218
  })()`);
215
- assert(defineCodecOut.includes("Successfully registered codec yn"), `Expected defined codec yn, got: ${defineCodecOut}`);
219
+ assert.strictEqual(modalCheck.open, true, "Codecs modal should open");
220
+ assert.strictEqual(modalCheck.hasIntCard, true, "Card for enabled int.mjs codec should exist");
221
+ assert.strictEqual(modalCheck.cardCount, 1, "Only 1 enabled codec card should exist");
216
222
 
217
- // Test modal interaction via openCodecsModal
218
- const modalIsOpen = await evaluateAsync(`(() => {
223
+ // Test loading unit.mjs via REPL command
224
+ const loadUnitOut = await evaluateAsync(`(async () => {
225
+ await window.kRepl.executeCommand(":codec load unit.mjs");
219
226
  window.kRepl.openCodecsModal();
220
- return document.getElementById("codecs-modal")?.classList.contains("open");
227
+ const cardUnit = document.getElementById("card-codec-unit_mjs");
228
+ const badge = document.getElementById("codecs-count-badge");
229
+ return {
230
+ hasUnitCard: Boolean(cardUnit),
231
+ badgeCount: badge?.textContent
232
+ };
221
233
  })()`);
222
- assert.strictEqual(modalIsOpen, true, "Codecs modal should open");
234
+ assert.strictEqual(loadUnitOut.hasUnitCard, true, "card-codec-unit_mjs should exist after loading unit.mjs");
235
+ assert.strictEqual(loadUnitOut.badgeCount, "2", "Badge count should be 2 after loading unit.mjs");
236
+
237
+ // Test unloading unit.mjs via card Unload button
238
+ const unloadUnitCheck = await evaluateAsync(`(async () => {
239
+ const unloadBtn = document.querySelector("#card-codec-unit_mjs .btn-danger");
240
+ unloadBtn.click();
241
+ await new Promise(r => setTimeout(r, 60));
242
+ const cardUnit = document.getElementById("card-codec-unit_mjs");
243
+ const badge = document.getElementById("codecs-count-badge");
244
+ return {
245
+ hasUnitCard: Boolean(cardUnit),
246
+ badgeCount: badge?.textContent
247
+ };
248
+ })()`);
249
+ assert.strictEqual(unloadUnitCheck.hasUnitCard, false, "card-codec-unit_mjs should be removed after Unload");
250
+ assert.strictEqual(unloadUnitCheck.badgeCount, "1", "Badge count should be 1 after unloading unit.mjs");
223
251
 
224
252
  // Close codecs modal
225
253
  await evaluateAsync(`(() => {
226
254
  document.getElementById("codecs-modal")?.classList.remove("open");
227
255
  })()`);
228
256
 
229
- // Test :input opens input popup modal
230
- await evaluateAsync(`window.kRepl.executeCommand(":input int")`);
231
- const inputModalOpen = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
232
- assert.strictEqual(inputModalOpen, true, "Executing :input int should open input popup modal");
257
+ // Test :input directive without args outputs usage in terminal
258
+ await evaluateAsync(`window.kRepl.executeCommand(":input")`);
259
+ const inputUsageCheck = await evaluateAsync(`(() => {
260
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
261
+ return lines.slice(-2).join(" ");
262
+ })()`);
263
+ assert(inputUsageCheck.includes("Usage: :input <codec.mjs> [text]"), `Expected usage text, got: ${inputUsageCheck}`);
264
+
265
+ // Test :input int.mjs sets prompt to int.mjs>
266
+ await evaluateAsync(`window.kRepl.executeCommand(":input int.mjs")`);
267
+ const promptAfterInput = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
268
+ assert.strictEqual(promptAfterInput, "int.mjs> ", `Expected prompt label 'int.mjs> ', got: '${promptAfterInput}'`);
233
269
 
234
- // Enter value '55' in popup and submit
270
+ // Enter value '55' at the prompt
235
271
  const submitResult = await evaluateAsync(`(async () => {
236
- const textEl = document.getElementById("input-popup-text");
237
- textEl.value = "55";
238
- await window.kRepl.submitInputPopup();
272
+ await window.kRepl.executeCommand("55");
239
273
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
240
274
  return lines[lines.length - 1];
241
275
  })()`);
242
- assert(submitResult.includes("int: 55"), `Expected int: 55 after submitting input popup, got: ${submitResult}`);
243
-
244
- // Verify modal is closed after submission
245
- const modalClosedAfterSubmit = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
246
- assert.strictEqual(modalClosedAfterSubmit, true, "Input modal should be closed after submit");
247
-
248
- // Test :input directive without args opens input popup
249
- await evaluateAsync(`window.kRepl.executeCommand(":input")`);
250
- const inputWithoutArgsOpensModal = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
251
- assert.strictEqual(inputWithoutArgsOpensModal, true, "Executing :input without args should open input popup");
252
-
253
- // Test cancelInputPopup
254
- await evaluateAsync(`window.kRepl.cancelInputPopup()`);
255
- const modalClosedAfterCancel = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
256
- assert.strictEqual(modalClosedAfterCancel, true, "Input modal should close on cancel");
276
+ assert(submitResult.includes("int.mjs: 55"), `Expected int.mjs: 55 after entering value, got: ${submitResult}`);
277
+
278
+ // Verify prompt is restored to '> '
279
+ const promptAfterValue = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
280
+ assert.strictEqual(promptAfterValue, "> ", `Expected prompt label '> ', got: '${promptAfterValue}'`);
281
+
282
+ // Test Tab autocompletion for :input: typing ":input " and pressing Tab does NOT repeat :input
283
+ const tabCompletionCheck = await evaluateAsync(`(() => {
284
+ const inputEl = document.getElementById("repl-input");
285
+ inputEl.value = ":input ";
286
+ inputEl.selectionStart = inputEl.selectionEnd = 7;
287
+ const event = new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true });
288
+ inputEl.dispatchEvent(event);
289
+ return {
290
+ value: inputEl.value,
291
+ hasDuplicates: inputEl.value.includes(":input :input")
292
+ };
293
+ })()`);
294
+ assert.strictEqual(tabCompletionCheck.hasDuplicates, false, `:input Tab completion duplicated command: '${tabCompletionCheck.value}'`);
295
+ assert(tabCompletionCheck.value.startsWith(":input int.mjs"), `Expected completion to start with ':input int.mjs', got: '${tabCompletionCheck.value}'`);
257
296
 
258
- // Test JSON codec with float64 in browser: :input {string a, float64 n} then {"a":"Woj","n":123}
297
+ // Test JSON codec with float64 in browser: :input json.mjs then {"a":"Woj","n":123}
259
298
  await evaluateAsync(`(async () => {
260
299
  await window.kRepl.executeCommand(":load core.k");
261
300
  await window.kRepl.executeCommand(":load ieee.k");
262
- await window.kRepl.executeCommand(":codec load json");
301
+ await window.kRepl.executeCommand(":codec load json.mjs");
302
+ await window.kRepl.executeCommand(":input json.mjs");
263
303
  })()`);
264
- const inputPromptOut = await evaluateAsync(`(async () => {
265
- await window.kRepl.executeCommand(":input {string a, float64 n}");
266
- const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
267
- return lines[lines.length - 1];
268
- })()`);
269
- assert(inputPromptOut.includes("enter value text"), `Expected input prompt, got: ${inputPromptOut}`);
304
+ const jsonPromptCheck = await evaluateAsync(`document.getElementById("prompt-label")?.textContent`);
305
+ assert.strictEqual(jsonPromptCheck, "json.mjs> ", `Expected prompt 'json.mjs> ', got: '${jsonPromptCheck}'`);
270
306
 
271
307
  // Enter JSON value with float64
272
308
  const jsonResult = await evaluateAsync(`(async () => {
@@ -274,33 +310,54 @@ if (chromiumBin) {
274
310
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
275
311
  return lines[lines.length - 1];
276
312
  })()`);
277
- assert(jsonResult.includes('json: {"a":"Woj","n":123}'), `Expected json: {"a":"Woj","n":123}, got: ${jsonResult}`);
313
+ assert(jsonResult.includes('json.mjs: {"a":"Woj","n":123}'), `Expected json.mjs: {"a":"Woj","n":123}, got: ${jsonResult}`);
278
314
 
279
315
  // Verify Reset button is removed from navbar
280
316
  const hasResetBtn = await evaluateAsync(`Boolean(document.getElementById("btn-reset"))`);
281
317
  assert.strictEqual(hasResetBtn, false, "Reset button should be removed from navbar");
282
318
 
283
- // Test explicit pattern expression :input ? {string a, float64 n} json
319
+ // Test one-line codec input: :input json.mjs {"a":"Test","n":456}
284
320
  await evaluateAsync(`(async () => {
285
- await window.kRepl.executeCommand(":input ? {string a, float64 n} json");
286
- await window.kRepl.executeCommand('{"a":"Test","n":456}');
321
+ await window.kRepl.executeCommand(':input json.mjs {"a":"Test","n":456}');
287
322
  })()`);
288
323
  const patternResult = await evaluateAsync(`(() => {
289
324
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
290
325
  return lines[lines.length - 1];
291
326
  })()`);
292
- assert(patternResult.includes('json: {"a":"Test","n":456}'), `Expected json: {"a":"Test","n":456}, got: ${patternResult}`);
327
+ assert(patternResult.includes('json.mjs: {"a":"Test","n":456}'), `Expected json.mjs: {"a":"Test","n":456}, got: ${patternResult}`);
328
+
329
+ // Test integer list input: :input int.mjs [0,1,2]
330
+ await evaluateAsync(`(async () => {
331
+ await window.kRepl.executeCommand(":input int.mjs [0,1,2]");
332
+ })()`);
333
+ const listResult = await evaluateAsync(`(() => {
334
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
335
+ return lines[lines.length - 1];
336
+ })()`);
337
+ assert(listResult.includes('int.mjs: [0,1,2]'), `Expected int.mjs: [0,1,2], got: ${listResult}`);
293
338
 
294
- // Test standalone codec input :input json without pre-declared type
339
+ // Test utf8.mjs input: :input utf8.mjs Hello world
295
340
  await evaluateAsync(`(async () => {
296
- await window.kRepl.executeCommand(":input json");
297
- await window.kRepl.executeCommand('{"foo":"bar"}');
341
+ await window.kRepl.executeCommand(":codec load utf8.mjs");
342
+ await window.kRepl.executeCommand(":input utf8.mjs Hello world");
298
343
  })()`);
299
- const standaloneResult = await evaluateAsync(`(() => {
344
+ const utf8Result = await evaluateAsync(`(() => {
300
345
  const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
301
346
  return lines[lines.length - 1];
302
347
  })()`);
303
- assert(standaloneResult.includes('json: {"foo":"bar"}'), `Expected json: {"foo":"bar"}, got: ${standaloneResult}`);
348
+ assert(utf8Result.includes('utf8.mjs: Hello world'), `Expected utf8.mjs: Hello world, got: ${utf8Result}`);
349
+
350
+ // Test ieee.mjs input: :input ieee.mjs 3.14159
351
+ await evaluateAsync(`(async () => {
352
+ await window.kRepl.executeCommand(":codec load ieee.mjs");
353
+ await window.kRepl.executeCommand(":input ieee.mjs 3.14159");
354
+ })()`);
355
+ const ieeeResult = await evaluateAsync(`(() => {
356
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
357
+ return lines[lines.length - 1];
358
+ })()`);
359
+ assert(ieeeResult.includes('ieee.mjs: 3.14159'), `Expected ieee.mjs: 3.14159, got: ${ieeeResult}`);
360
+
304
361
 
305
362
  // Verify text selection holds in output element
306
363
  const selectionCheck = await evaluateAsync(`(() => {
@@ -336,6 +393,7 @@ if (chromiumBin) {
336
393
  // Check Value section
337
394
  const valSection = container.querySelector(".k-tree-value-section");
338
395
  if (!valSection) return { error: "No value section" };
396
+ if (valSection.open) return { error: "Value section should be collapsed by default (open === false)" };
339
397
 
340
398
  // Check product details nodes hierarchy
341
399
  // Level 1: root product (depth 1)
@@ -355,9 +413,10 @@ if (chromiumBin) {
355
413
  if (!level4) return { error: "Level 4 product node missing" };
356
414
  if (level4.open) return { error: "Level 4 product should be collapsed (open === false)" };
357
415
 
358
- // Check pattern section exists and has cycle or vardef
416
+ // Check pattern section exists and is collapsed by default
359
417
  const patSection = container.querySelector(".k-tree-pat-section");
360
418
  if (!patSection) return { error: "No pattern section" };
419
+ if (patSection.open) return { error: "Pattern section should be collapsed by default (open === false)" };
361
420
 
362
421
  // Test Mode Toggle: click Raw
363
422
  rawBtn.click();
@@ -480,11 +539,17 @@ if (chromiumBin) {
480
539
  btnHelp.click();
481
540
  const helpModalOpen = document.getElementById("help-modal")?.classList.contains("open");
482
541
  const modalText = document.getElementById("help-modal")?.textContent || "";
483
- const hasRel = modalText.includes(":rel ");
542
+ const hasRel = modalText.includes(":rel <name>");
484
543
  const hasVal = modalText.includes(":val");
485
544
  const hasTiming = modalText.includes(":timing");
545
+ const hasTime = modalText.includes(":time");
546
+ const hasT = modalText.includes(":t <name>");
486
547
  const hasCodecDefine = modalText.includes(":codec define");
487
548
  const hasType = modalText.includes(":type <name>");
549
+ const hasTypes = modalText.includes(":types");
550
+ const hasCodes = modalText.includes(":codes");
551
+ const hasD = modalText.includes(":d <name>");
552
+ const hasEngine = modalText.includes(":engine");
488
553
  return {
489
554
  found: true,
490
555
  isVisible,
@@ -493,18 +558,48 @@ if (chromiumBin) {
493
558
  hasRel,
494
559
  hasVal,
495
560
  hasTiming,
561
+ hasTime,
562
+ hasT,
496
563
  hasCodecDefine,
497
- hasType
564
+ hasType,
565
+ hasTypes,
566
+ hasCodes,
567
+ hasD,
568
+ hasEngine
498
569
  };
499
570
  })()`);
500
571
  assert.strictEqual(narrowNavbarCheck.found, true, "#btn-help should exist");
501
572
  assert.strictEqual(narrowNavbarCheck.isVisible, true, "#btn-help should be visible within narrow viewport");
502
573
  assert.strictEqual(narrowNavbarCheck.helpModalOpen, true, "Clicking #btn-help in narrow viewport should open help modal");
503
- assert.strictEqual(narrowNavbarCheck.hasRel, false, "Help modal should not contain :rel");
574
+ assert.strictEqual(narrowNavbarCheck.hasRel, true, "Help modal should contain :rel <name>");
575
+ assert.strictEqual(narrowNavbarCheck.hasType, true, "Help modal should contain :type <name>");
576
+ assert.strictEqual(narrowNavbarCheck.hasTypes, true, "Help modal should contain :types");
577
+ assert.strictEqual(narrowNavbarCheck.hasEngine, true, "Help modal should contain :engine");
578
+ assert.strictEqual(narrowNavbarCheck.hasCodes, false, "Help modal should not contain :codes");
579
+ assert.strictEqual(narrowNavbarCheck.hasTime, false, "Help modal should not contain :time");
580
+ assert.strictEqual(narrowNavbarCheck.hasT, false, "Help modal should not contain :t <name>");
581
+ assert.strictEqual(narrowNavbarCheck.hasD, false, "Help modal should not contain :d <name>");
504
582
  assert.strictEqual(narrowNavbarCheck.hasVal, false, "Help modal should not contain :val");
505
583
  assert.strictEqual(narrowNavbarCheck.hasTiming, false, "Help modal should not contain :timing");
506
584
  assert.strictEqual(narrowNavbarCheck.hasCodecDefine, false, "Help modal should not contain :codec define");
507
- assert.strictEqual(narrowNavbarCheck.hasType, true, "Help modal should contain :type <name>");
585
+
586
+ // 6. Test dual engine switching in browser (wasm <-> js)
587
+ const engineCheck = await evaluateAsync(`(async () => {
588
+ const initialEngine = window.kRepl.getEngine();
589
+ await window.kRepl.executeCommand(":engine js");
590
+ const jsEngine = window.kRepl.getEngine();
591
+ const badgeJsText = document.getElementById("engine-badge")?.textContent;
592
+ await window.kRepl.executeCommand("() | ok");
593
+ await window.kRepl.executeCommand(":engine wasm");
594
+ const wasmEngine = window.kRepl.getEngine();
595
+ const badgeWasmText = document.getElementById("engine-badge")?.textContent;
596
+ return { initialEngine, jsEngine, wasmEngine, badgeJsText, badgeWasmText };
597
+ })()`);
598
+ assert.strictEqual(engineCheck.initialEngine, "wasm");
599
+ assert.strictEqual(engineCheck.jsEngine, "js");
600
+ assert.strictEqual(engineCheck.badgeJsText, "js-in-process");
601
+ assert.strictEqual(engineCheck.wasmEngine, "wasm");
602
+ assert.strictEqual(engineCheck.badgeWasmText, "wasm-in-process");
508
603
 
509
604
  ws.close();
510
605
  console.log(" Browser execution verified successfully!");