@fraczak/k 6.3.3 → 6.5.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.
Files changed (157) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/DOCS/CODECS.md +258 -0
  3. package/DOCS/DICTIONARY.md +1172 -0
  4. package/DOCS/KIR_SPECIALIZATION_DESIGN.md +51 -46
  5. package/DOCS/KIR_V1.md +221 -0
  6. package/DOCS/KVM_EXECUTION_MODEL.md +56 -19
  7. package/DOCS/OBJECT_FILE_AND_PATTERN.md +2 -1
  8. package/DOCS/PROFILING_AND_TRACING.md +196 -0
  9. package/DOCS/REPL.md +61 -4
  10. package/DOCS/WEB_REPL_REQUIREMENTS.md +135 -0
  11. package/DOCS/dependency-graph.md +144 -0
  12. package/Examples/arithmetics.k +108 -82
  13. package/Examples/bnat.k +1 -1
  14. package/Examples/buda.k +44 -0
  15. package/Examples/ieee.k +300 -8671
  16. package/Examples/poly.k +67 -0
  17. package/README.md +68 -19
  18. package/Value.mjs +8 -2
  19. package/augmentation.mjs +9 -7
  20. package/backend-api.mjs +11 -2
  21. package/backends/arm64/README.md +104 -0
  22. package/backends/arm64/bin/k-arm64-compile.mjs +101 -0
  23. package/backends/arm64/bin/k-arm64-run.mjs +58 -0
  24. package/backends/arm64/bin/k-arm64.mjs +88 -0
  25. package/backends/arm64/scripts/run-tests.mjs +41 -0
  26. package/backends/arm64/src/arm64.mjs +229 -0
  27. package/backends/arm64/src/cli.mjs +155 -0
  28. package/backends/arm64/src/kvm2arm64.mjs +906 -0
  29. package/backends/arm64/src/runtime.c +728 -0
  30. package/backends/arm64/tests/integration.sh +112 -0
  31. package/backends/arm64/tests/test-arm64.mjs +240 -0
  32. package/backends/llvm/README.md +135 -0
  33. package/backends/llvm/bin/k-llvm-build.mjs +71 -0
  34. package/backends/llvm/bin/k-llvm-compile.mjs +68 -0
  35. package/backends/llvm/bin/k-llvm-jit.mjs +160 -0
  36. package/backends/llvm/bin/k-llvm-run.mjs +90 -0
  37. package/backends/llvm/runtime/krt.c +1508 -0
  38. package/backends/llvm/runtime/krt.h +109 -0
  39. package/backends/llvm/scripts/conformance.mjs +76 -0
  40. package/backends/llvm/scripts/run-tests.mjs +28 -0
  41. package/backends/llvm/src/executable.mjs +562 -0
  42. package/backends/llvm/src/kvm2llvm.mjs +998 -0
  43. package/backends/llvm/src/llvm.mjs +1092 -0
  44. package/backends/llvm/tests/composition-driver.c +22 -0
  45. package/backends/llvm/tests/identity-driver.c +27 -0
  46. package/backends/llvm/tests/integration.sh +153 -0
  47. package/backends/llvm/tests/perf-support.mjs +735 -0
  48. package/backends/llvm/tests/product-driver.c +23 -0
  49. package/backends/llvm/tests/projection-driver.c +19 -0
  50. package/backends/llvm/tests/relation-driver.c +29 -0
  51. package/backends/llvm/tests/test-llvm.mjs +240 -0
  52. package/backends/llvm/tests/union-driver.c +31 -0
  53. package/backends/llvm/tests/variant-driver.c +19 -0
  54. package/backends/llvm/tests/variant-projection-driver.c +21 -0
  55. package/backends/wasm/DOCS/USE-WEB-ASSEMBLY.md +233 -0
  56. package/backends/wasm/README.md +97 -0
  57. package/backends/wasm/TECHNICAL-DEBT.md +58 -0
  58. package/backends/wasm/bin/k-wasm-compile.mjs +50 -0
  59. package/backends/wasm/bin/k-wasm-run.mjs +140 -0
  60. package/backends/wasm/bin/k-wasm.mjs +49 -0
  61. package/backends/wasm/demo_ieee.mjs +319 -0
  62. package/backends/wasm/runtime.wat +475 -0
  63. package/backends/wasm/scripts/run-tests.mjs +41 -0
  64. package/backends/wasm/src/cli.mjs +133 -0
  65. package/backends/wasm/src/kvm2wasm.mjs +697 -0
  66. package/backends/wasm/src/wasm.mjs +1054 -0
  67. package/backends/wasm/tests/integration.sh +182 -0
  68. package/backends/wasm/tests/perf-support.mjs +336 -0
  69. package/backends/wasm/tests/test-wasm.mjs +625 -0
  70. package/browser/repl-browser.mjs +1584 -0
  71. package/browser/shims/assert.mjs +19 -0
  72. package/browser/shims/buffer.mjs +224 -0
  73. package/browser/shims/crypto.mjs +103 -0
  74. package/browser/shims/fs.mjs +159 -0
  75. package/browser/shims/os.mjs +17 -0
  76. package/browser/shims/path.mjs +54 -0
  77. package/browser/shims/process.mjs +44 -0
  78. package/browser/shims/readline.mjs +11 -0
  79. package/browser/shims/url.mjs +12 -0
  80. package/browser/vfs-data.mjs +19 -0
  81. package/codecs/README.md +3 -0
  82. package/codecs/runtime/show-value.mjs +12 -3
  83. package/conformance/helper-reuse/case.json +8 -0
  84. package/conformance/helper-reuse/expected.kv +1 -0
  85. package/conformance/helper-reuse/input.kv +1 -0
  86. package/conformance/helper-reuse/program.k +2 -0
  87. package/conformance/identity/case.json +8 -0
  88. package/conformance/identity/expected.kv +1 -0
  89. package/conformance/identity/input.kv +1 -0
  90. package/conformance/identity/program.k +1 -0
  91. package/conformance/product-projection/case.json +8 -0
  92. package/conformance/product-projection/expected.kv +1 -0
  93. package/conformance/product-projection/input.kv +1 -0
  94. package/conformance/product-projection/program.k +1 -0
  95. package/conformance/run.mjs +149 -0
  96. package/conformance/variant-construction/case.json +8 -0
  97. package/conformance/variant-construction/expected.kv +1 -0
  98. package/conformance/variant-construction/input.kv +1 -0
  99. package/conformance/variant-construction/program.k +1 -0
  100. package/harness/adapters/arm64.mjs +105 -0
  101. package/harness/adapters/kvm.mjs +144 -0
  102. package/harness/adapters/llvm.mjs +118 -0
  103. package/harness/adapters/native.mjs +77 -0
  104. package/harness/adapters/wasm.mjs +169 -0
  105. package/harness/backend.mjs +95 -0
  106. package/harness/cli.mjs +130 -0
  107. package/harness/format.mjs +95 -0
  108. package/harness/index.mjs +25 -0
  109. package/harness/persistent.mjs +121 -0
  110. package/harness/registry.mjs +44 -0
  111. package/harness/suite.mjs +257 -0
  112. package/harness/suites/poly.mjs +156 -0
  113. package/harness/trace.mjs +50 -0
  114. package/harness/util.mjs +92 -0
  115. package/intrinsics.mjs +41 -0
  116. package/kir.mjs +399 -0
  117. package/kvm.mjs +585 -81
  118. package/objects/README.md +37 -3
  119. package/objects/compile.mjs +15 -22
  120. package/objects/inspect.mjs +96 -0
  121. package/objects/validate.mjs +415 -0
  122. package/package.json +51 -19
  123. package/repl-codecs.mjs +105 -6
  124. package/repl.mjs +335 -49
  125. package/run.mjs +2 -7
  126. package/scripts/benchmark.mjs +406 -22
  127. package/scripts/build-repl-html.mjs +1784 -0
  128. package/scripts/perf-ieee.mjs +586 -0
  129. package/scripts/perf-int.mjs +531 -0
  130. package/scripts/perf-poly.mjs +945 -0
  131. package/scripts/render-benchmark.mjs +12 -8
  132. package/scripts/run-tests.mjs +6 -0
  133. package/tests/integration.sh +19 -2
  134. package/tests/test-arithmetics.mjs +54 -0
  135. package/tests/test-ieee-arithmetic.mjs +4 -0
  136. package/tests/test-kir.mjs +85 -0
  137. package/tests/test-kvm-polymorphic.mjs +278 -0
  138. package/tests/test-kvm.mjs +25 -6
  139. package/tests/test-poly.mjs +129 -0
  140. package/tests/test-repl-html.mjs +303 -0
  141. package/tests/test-repl.mjs +144 -1
  142. package/tests/test-validate-object.mjs +40 -0
  143. package/a.klib +0 -1
  144. package/a.kvm +0 -142676
  145. package/a.wasm +0 -0
  146. package/benchmark-results.html +0 -532
  147. package/benchmark-results.json +0 -480
  148. package/k-vscode/.vscode/launch.json +0 -14
  149. package/k-vscode/README.md +0 -75
  150. package/k-vscode/extension.js +0 -273
  151. package/k-vscode/language-configuration.json +0 -42
  152. package/k-vscode/package.json +0 -79
  153. package/k-vscode/snippets/k.json +0 -92
  154. package/k-vscode/syntaxes/k.tmLanguage.json +0 -599
  155. package/k-vscode/test/background-ranges.test.js +0 -50
  156. package/k-vscode/themes/k-contrast-color-theme.json +0 -104
  157. package/scripts/git-hooks/pre-commit +0 -10
@@ -0,0 +1,129 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { createState, evaluateInput } from "../backend-api.mjs";
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const arithmeticsPath = path.join(root, "Examples/arithmetics.k");
10
+ const polyPath = path.join(root, "Examples/poly.k");
11
+
12
+ console.log("==> Running Polymorphic List Tests (Examples/poly.k)");
13
+
14
+ const state = createState();
15
+ await evaluateInput(`:load ${arithmeticsPath}`, state);
16
+ await evaluateInput(`:load ${polyPath}`, state);
17
+
18
+ async function evalK(snippet) {
19
+ const res = await evaluateInput(snippet, state);
20
+ assert.ok(res && res.length > 0 && !res[0].includes("undefined"), `Evaluation failed for: ${snippet}`);
21
+ return res[0];
22
+ }
23
+
24
+ // Test 1: Concat
25
+ {
26
+ const res = await evalK(`
27
+ l1 = {{}|a car, {{}|b car, nil cdr} cons cdr} cons;
28
+ l2 = {{}|c car, {{}|d car, nil cdr} cons cdr} cons;
29
+ {l1 xs, l2 ys} concat
30
+ `);
31
+ assert.match(res, /\|a car.*\|b car.*\|c car.*\|d car/);
32
+ console.log("Test 1 (concat): Passed");
33
+ }
34
+
35
+ // Test 2: Reverse
36
+ {
37
+ const res = await evalK(`
38
+ l = {{}|first car, {{}|second car, {{}|third car, nil cdr} cons cdr} cons cdr} cons;
39
+ l reverse
40
+ `);
41
+ assert.match(res, /\|third car.*\|second car.*\|first car/);
42
+ console.log("Test 2 (reverse): Passed");
43
+ }
44
+
45
+ // Test 3: Length
46
+ {
47
+ const res = await evalK(`
48
+ l = {{}|a car, {{}|b car, {{}|c car, nil cdr} cons cdr} cons cdr} cons;
49
+ l length
50
+ `);
51
+ // 3 in LSB-first binary with '+' sign: |_|1|1|+
52
+ assert.match(res, /1.*1.*\+/);
53
+ console.log("Test 3 (length): Passed");
54
+ }
55
+
56
+ // Test 4: Get N-th
57
+ {
58
+ const res0 = await evalK(`
59
+ l = {{}|item0 car, {{}|item1 car, {{}|item2 car, nil cdr} cons cdr} cons cdr} cons;
60
+ {0 int n, l xs} get_nth
61
+ `);
62
+ assert.match(res0, /\|item0/);
63
+
64
+ const res1 = await evalK(`{1 int n, l xs} get_nth`);
65
+ assert.match(res1, /\|item1/);
66
+
67
+ const res2 = await evalK(`{2 int n, l xs} get_nth`);
68
+ assert.match(res2, /\|item2/);
69
+ console.log("Test 4 (get_nth): Passed");
70
+ }
71
+
72
+ // Test 5: Split By
73
+ {
74
+ const res = await evalK(`
75
+ l = {{}|a car, {{}|b car, {{}|c car, {{}|d car, nil cdr} cons cdr} cons cdr} cons cdr} cons;
76
+ {2 int n, l xs} split_by
77
+ `);
78
+ assert.match(res, /first/);
79
+ assert.match(res, /second/);
80
+ console.log("Test 5 (split_by): Passed");
81
+ }
82
+
83
+ // Test 6: Take and Drop
84
+ {
85
+ const resTake = await evalK(`
86
+ l = {{}|a car, {{}|b car, {{}|c car, nil cdr} cons cdr} cons cdr} cons;
87
+ {2 int n, l xs} take
88
+ `);
89
+ assert.match(resTake, /\|a car.*\|b car/);
90
+
91
+ const resDrop = await evalK(`
92
+ {2 int n, l xs} drop
93
+ `);
94
+ assert.match(resDrop, /\|c car/);
95
+ console.log("Test 6 (take & drop): Passed");
96
+ }
97
+
98
+ // Test 7: Repeat
99
+ {
100
+ const res = await evalK(`
101
+ {3 int n, {}|val x} repeat
102
+ `);
103
+ assert.match(res, /\|val car.*\|val car.*\|val car/);
104
+ console.log("Test 7 (repeat): Passed");
105
+ }
106
+
107
+ // Test 8: Append
108
+ {
109
+ const res = await evalK(`
110
+ l = {{}|a car, {{}|b car, nil cdr} cons cdr} cons;
111
+ {{}|c x, l xs} append
112
+ `);
113
+ assert.match(res, /\|a car.*\|b car.*\|c car/);
114
+ console.log("Test 8 (append): Passed");
115
+ }
116
+
117
+ // Test 9: Zip
118
+ {
119
+ const res = await evalK(`
120
+ l1 = {{}|k1 car, {{}|k2 car, nil cdr} cons cdr} cons;
121
+ l2 = {1 int car, {2 int car, nil cdr} cons cdr} cons;
122
+ {l1 xs, l2 ys} zip
123
+ `);
124
+ assert.match(res, /\|k1 x/);
125
+ assert.match(res, /\|k2 x/);
126
+ console.log("Test 9 (zip): Passed");
127
+ }
128
+
129
+ console.log("==> All Polymorphic List Tests Passed Successfully!");
@@ -0,0 +1,303 @@
1
+ import assert from "node:assert";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { spawn, spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const root = path.resolve(__dirname, "..");
9
+ const replHtmlPath = path.join(root, "repl.html");
10
+
11
+ console.log("=== Testing Web REPL (repl.html) ===");
12
+
13
+ // 1. Build repl.html
14
+ console.log("1. Running scripts/build-repl-html.mjs...");
15
+ const buildResult = spawnSync(process.execPath, [path.join(root, "scripts/build-repl-html.mjs")], {
16
+ cwd: root,
17
+ stdio: "pipe",
18
+ encoding: "utf8"
19
+ });
20
+
21
+ if (buildResult.status !== 0) {
22
+ console.error(buildResult.stderr || buildResult.stdout);
23
+ process.exit(buildResult.status ?? 1);
24
+ }
25
+
26
+ // 2. Validate self-contained HTML
27
+ console.log("2. Validating generated repl.html...");
28
+ assert(fs.existsSync(replHtmlPath), "repl.html must exist");
29
+ const html = fs.readFileSync(replHtmlPath, "utf8");
30
+
31
+ assert(html.length > 500000, `repl.html size (${html.length} bytes) is suspiciously small`);
32
+ assert(html.includes("<!DOCTYPE html>"), "repl.html must be valid HTML5");
33
+ assert(html.includes("<style>"), "repl.html must contain inlined CSS styles");
34
+ assert(html.includes('<script type="module">'), "repl.html must contain inlined module script");
35
+ assert(!html.includes('<script src="http'), "repl.html must NOT contain external remote scripts");
36
+ assert(html.includes("wasm-in-process"), "repl.html must mention wasm-in-process engine");
37
+ assert(html.includes("arithmetics.k"), "repl.html must embed standard examples");
38
+ assert(html.includes("btn-codecs"), "repl.html must contain Codecs button");
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");
44
+ console.log(" repl.html is valid, self-contained, and has size:", (html.length / 1024).toFixed(1), "KB");
45
+
46
+ // 3. If Chromium is available, run end-to-end browser test
47
+ const chromiumBin = ["chromium-browser", "chromium", "google-chrome"].find(bin => {
48
+ const res = spawnSync("which", [bin], { stdio: "ignore" });
49
+ return res.status === 0;
50
+ });
51
+
52
+ if (chromiumBin) {
53
+ console.log(`3. Running browser verification with ${chromiumBin}...`);
54
+ const tmpDir = `/tmp/chrome-test-${Date.now()}`;
55
+ fs.mkdirSync(tmpDir, { recursive: true });
56
+
57
+ const cdpPort = 9333 + Math.floor(Math.random() * 500);
58
+ const chrome = spawn(chromiumBin, [
59
+ "--headless=new",
60
+ "--disable-gpu",
61
+ `--remote-debugging-port=${cdpPort}`,
62
+ "--no-sandbox",
63
+ "--disable-extensions",
64
+ `--user-data-dir=${tmpDir}`,
65
+ `file://${replHtmlPath}`
66
+ ]);
67
+
68
+ try {
69
+ let wsUrl = null;
70
+ for (let i = 0; i < 30; i++) {
71
+ try {
72
+ const res = await fetch(`http://127.0.0.1:${cdpPort}/json/list`);
73
+ const list = await res.json();
74
+ const page = list.find(item => item.url && item.url.includes("repl.html"));
75
+ if (page && page.webSocketDebuggerUrl) {
76
+ wsUrl = page.webSocketDebuggerUrl;
77
+ break;
78
+ }
79
+ } catch {}
80
+ await new Promise(r => setTimeout(r, 200));
81
+ }
82
+
83
+ assert(wsUrl, `Could not connect to Chromium CDP on port ${cdpPort}`);
84
+
85
+ const ws = new WebSocket(wsUrl);
86
+ await new Promise(r => ws.onopen = r);
87
+
88
+ let idCounter = 1;
89
+ const pending = new Map();
90
+ ws.onmessage = (event) => {
91
+ const msg = JSON.parse(event.data);
92
+ if (msg.id && pending.has(msg.id)) {
93
+ const { resolve, reject } = pending.get(msg.id);
94
+ pending.delete(msg.id);
95
+ if (msg.error) reject(new Error(msg.error.message));
96
+ else resolve(msg.result);
97
+ }
98
+ };
99
+
100
+ function send(method, params = {}) {
101
+ const id = idCounter++;
102
+ return new Promise((resolve, reject) => {
103
+ pending.set(id, { resolve, reject });
104
+ ws.send(JSON.stringify({ id, method, params }));
105
+ });
106
+ }
107
+
108
+ await send("Page.enable");
109
+ await send("Runtime.enable");
110
+ await new Promise(r => setTimeout(r, 1200));
111
+
112
+ async function evaluateAsync(expression) {
113
+ const res = await send("Runtime.evaluate", {
114
+ expression,
115
+ awaitPromise: true,
116
+ returnByValue: true
117
+ });
118
+ if (res.exceptionDetails) {
119
+ throw new Error(res.exceptionDetails.exception?.description || "Evaluation error");
120
+ }
121
+ return res.result?.value;
122
+ }
123
+
124
+ // Wait for window.kRepl to be ready
125
+ await evaluateAsync(`new Promise((resolve, reject) => {
126
+ let tries = 0;
127
+ const check = () => {
128
+ if (window.kRepl && typeof window.kRepl.executeCommand === "function") resolve(true);
129
+ else if (++tries > 100) reject(new Error("Timeout waiting for window.kRepl"));
130
+ else setTimeout(check, 50);
131
+ };
132
+ check();
133
+ })`);
134
+
135
+ // Check UI initialized
136
+ const title = await evaluateAsync(`document.querySelector(".brand-name")?.textContent`);
137
+ assert.strictEqual(title, "k repl", "Brand name should be 'k repl'");
138
+
139
+ // Verify VFS curated files
140
+ const vfsList = await evaluateAsync(`window.kRepl.getAllVfsFiles().sort()`);
141
+ const expectedFiles = [
142
+ "arithmetics.k",
143
+ "core.k",
144
+ "ieee.k",
145
+ "ieee.mjs",
146
+ "int.mjs",
147
+ "json.mjs",
148
+ "unit.mjs",
149
+ "utf8.mjs"
150
+ ].sort();
151
+ assert.deepStrictEqual(vfsList, expectedFiles, `getAllVfsFiles() must match curated files list, got: ${JSON.stringify(vfsList)}`);
152
+
153
+ // Test :load arithmetics.k
154
+ const loadOut = await evaluateAsync(`(async () => {
155
+ await window.kRepl.executeCommand(":load arithmetics.k");
156
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
157
+ return lines[lines.length - 1];
158
+ })()`);
159
+ assert(loadOut.includes("loaded arithmetics.k"), `Expected loaded arithmetics.k, got: ${loadOut}`);
160
+
161
+ // Test evaluating 10
162
+ const valOut = await evaluateAsync(`(async () => {
163
+ await window.kRepl.executeCommand("10");
164
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
165
+ return lines[lines.length - 1];
166
+ })()`);
167
+ assert(valOut.includes("{}|_|0|1|0|1"), `Expected 10 in output, got: ${valOut}`);
168
+
169
+ // Test {10 int x, 5 int y} plus
170
+ const plusOut = await evaluateAsync(`(async () => {
171
+ await window.kRepl.executeCommand("{10 int x, 5 int y} plus");
172
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
173
+ return lines[lines.length - 1];
174
+ })()`);
175
+ assert(plusOut.includes("{}|_|1|1|1|1|+"), `Expected 15 in output, got: ${plusOut}`);
176
+
177
+ // Test :codecs initially empty
178
+ const codecsOut = await evaluateAsync(`(async () => {
179
+ await window.kRepl.executeCommand(":codecs");
180
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
181
+ return lines[lines.length - 1];
182
+ })()`);
183
+ assert.strictEqual(codecsOut, "(none)");
184
+
185
+ // Test :codec load int
186
+ const loadIntOut = await evaluateAsync(`(async () => {
187
+ await window.kRepl.executeCommand(":codec load int");
188
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
189
+ return lines[lines.length - 1];
190
+ })()`);
191
+ assert(loadIntOut.includes("loaded codec int"), `Expected loaded codec int, got: ${loadIntOut}`);
192
+
193
+ // Check codecs badge updated to 1
194
+ const badgeCount = await evaluateAsync(`document.getElementById("codecs-count-badge")?.textContent`);
195
+ assert.strictEqual(badgeCount, "1", "Codecs badge count should be 1 after loading int codec");
196
+
197
+ // Test evaluating 10 int with int codec active -> output should format using int serializer
198
+ const formattedVal = await evaluateAsync(`(async () => {
199
+ await window.kRepl.executeCommand("10 int");
200
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
201
+ return lines[lines.length - 1];
202
+ })()`);
203
+ assert(formattedVal.includes("int: 10"), `Expected 'int: 10' in output, got: ${formattedVal}`);
204
+
205
+ // Test defining inline custom codec via :codec define
206
+ await evaluateAsync(`window.kRepl.executeCommand(":type bool = <{} true, {} false>")`);
207
+ const defineCodecOut = await evaluateAsync(`(async () => {
208
+ await window.kRepl.executeCommand(":codec define yn bool ({ parse: (t) => Value.variant(t.trim() === 'yes' ? 'true' : 'false', Value.product({})), print: (v) => v.tag === 'true' ? 'YES' : 'NO' })");
209
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
210
+ return lines[lines.length - 1];
211
+ })()`);
212
+ assert(defineCodecOut.includes("defined codec yn"), `Expected defined codec yn, got: ${defineCodecOut}`);
213
+
214
+ // Test modal interaction via openCodecsModal
215
+ const modalIsOpen = await evaluateAsync(`(() => {
216
+ window.kRepl.openCodecsModal();
217
+ return document.getElementById("codecs-modal")?.classList.contains("open");
218
+ })()`);
219
+ assert.strictEqual(modalIsOpen, true, "Codecs modal should open");
220
+
221
+ // Close codecs modal
222
+ await evaluateAsync(`(() => {
223
+ document.getElementById("codecs-modal")?.classList.remove("open");
224
+ })()`);
225
+
226
+ // Test :input opens input popup modal
227
+ await evaluateAsync(`window.kRepl.executeCommand(":input int")`);
228
+ const inputModalOpen = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
229
+ assert.strictEqual(inputModalOpen, true, "Executing :input int should open input popup modal");
230
+
231
+ // Enter value '55' in popup and submit
232
+ const submitResult = await evaluateAsync(`(async () => {
233
+ const textEl = document.getElementById("input-popup-text");
234
+ textEl.value = "55";
235
+ await window.kRepl.submitInputPopup();
236
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
237
+ return lines[lines.length - 1];
238
+ })()`);
239
+ assert(submitResult.includes("int: 55"), `Expected int: 55 after submitting input popup, got: ${submitResult}`);
240
+
241
+ // Verify modal is closed after submission
242
+ const modalClosedAfterSubmit = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
243
+ assert.strictEqual(modalClosedAfterSubmit, true, "Input modal should be closed after submit");
244
+
245
+ // Test :input directive without args opens input popup
246
+ await evaluateAsync(`window.kRepl.executeCommand(":input")`);
247
+ const inputWithoutArgsOpensModal = await evaluateAsync(`document.getElementById("input-popup-modal")?.classList.contains("open")`);
248
+ assert.strictEqual(inputWithoutArgsOpensModal, true, "Executing :input without args should open input popup");
249
+
250
+ // Test cancelInputPopup
251
+ await evaluateAsync(`window.kRepl.cancelInputPopup()`);
252
+ const modalClosedAfterCancel = await evaluateAsync(`!document.getElementById("input-popup-modal")?.classList.contains("open")`);
253
+ assert.strictEqual(modalClosedAfterCancel, true, "Input modal should close on cancel");
254
+
255
+ // Test JSON codec with float64 in browser: :input {string a, float64 n} then {"a":"Woj","n":123}
256
+ await evaluateAsync(`(async () => {
257
+ await window.kRepl.executeCommand(":load core.k");
258
+ await window.kRepl.executeCommand(":load ieee.k");
259
+ await window.kRepl.executeCommand(":codec load json");
260
+ })()`);
261
+ const inputPromptOut = await evaluateAsync(`(async () => {
262
+ await window.kRepl.executeCommand(":input {string a, float64 n}");
263
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
264
+ return lines[lines.length - 1];
265
+ })()`);
266
+ assert(inputPromptOut.includes("enter value text"), `Expected input prompt, got: ${inputPromptOut}`);
267
+
268
+ // Enter JSON value with float64
269
+ const jsonResult = await evaluateAsync(`(async () => {
270
+ await window.kRepl.executeCommand('{"a":"Woj","n":123}');
271
+ const lines = Array.from(document.querySelectorAll(".entry-line")).map(el => el.textContent);
272
+ return lines[lines.length - 1];
273
+ })()`);
274
+ assert(jsonResult.includes('json: {"a":"Woj","n":123}'), `Expected json: {"a":"Woj","n":123}, got: ${jsonResult}`);
275
+
276
+ // Verify text selection holds in output element
277
+ const selectionCheck = await evaluateAsync(`(() => {
278
+ const output = document.getElementById("terminal-output");
279
+ const range = document.createRange();
280
+ range.selectNodeContents(output);
281
+ const sel = window.getSelection();
282
+ sel.removeAllRanges();
283
+ sel.addRange(range);
284
+ // Simulate click on terminal container/output
285
+ output.dispatchEvent(new MouseEvent("click", { bubbles: true }));
286
+ return window.getSelection().toString().length > 0;
287
+ })()`);
288
+ assert.strictEqual(selectionCheck, true, "Selection in terminal output should hold after click");
289
+
290
+ ws.close();
291
+ console.log(" Browser execution verified successfully!");
292
+ } finally {
293
+ chrome.kill();
294
+ await new Promise(r => setTimeout(r, 500));
295
+ try {
296
+ fs.rmSync(tmpDir, { recursive: true, force: true });
297
+ } catch {}
298
+ }
299
+ } else {
300
+ console.log("3. Chromium not found, skipping headless browser CDP step.");
301
+ }
302
+
303
+ console.log("OK - Web REPL test passed.");
@@ -199,7 +199,7 @@ const jsonState = createState();
199
199
  output = await evaluateInput(":type bool = <{} true, {} false>", jsonState);
200
200
  assert.match(output[0], /^\$ bool = @/);
201
201
  output = await evaluateInput(":codec load ./codecs/json.mjs", jsonState);
202
- assert.equal(output[0], "loaded codec json for all types");
202
+ assert.match(output[0], /^loaded codec json for all types/);
203
203
  output = await evaluateInput(":codec list", jsonState);
204
204
  assert.match(output[0], /^json all /);
205
205
  completions = completeInput(":input bool j", jsonState)[0];
@@ -277,6 +277,149 @@ await assert.rejects(
277
277
  /Type Error|Unknown ref: 'type'|Parse error|Parse Error/
278
278
  );
279
279
 
280
+ // Verify WebAssembly in-process backend supports TCO / stack safety on deep recursion
281
+ const tcoState = createState();
282
+ await evaluateInput(":type nat = <{} zero, nat succ>", tcoState);
283
+ await evaluateInput(":rel countdown = $nat < /zero {}|zero, /succ countdown >", tcoState);
284
+
285
+ let deepNat = Value.variant("zero", Value.product({}, [["closed-product", []]]), [["open-union", [["zero", 1]]], ["closed-product", []]]);
286
+ for (let i = 0; i < 5000; i++) {
287
+ deepNat = Value.variant("succ", deepNat, [["open-union", [["succ", 1]]], ...deepNat.pattern]);
288
+ }
289
+ tcoState.value = deepNat;
290
+
291
+ output = await evaluateInput("countdown", tcoState);
292
+ assert.match(output[0], /^\{\}\|zero/);
293
+ assert.equal(tcoState.value.toJSON(), "zero");
294
+
295
+ // Verify loading library and evaluating constant/recursive relation from library
296
+ const arithState = createState();
297
+ output = await evaluateInput(":load Examples/arithmetics.k", arithState);
298
+ assert.equal(output[0], "loaded Examples/arithmetics.k");
299
+ output = await evaluateInput("10", arithState);
300
+ assert.match(output[0], /^\{\}\|_\|0\|1\|0\|1/);
301
+ output = await evaluateInput("{10 int x, 5 int y} plus", arithState);
302
+ assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
303
+
304
+ // Verify built-in codecs, :codecs, :codec define, and :codec unload
305
+ const codecTestState = createState();
306
+ output = await evaluateInput(":codecs", codecTestState);
307
+ assert.equal(output[0], "(none)");
308
+
309
+ output = await evaluateInput(":codec load int", codecTestState);
310
+ assert.match(output[0], /^loaded codec int for @/);
311
+
312
+ output = await evaluateInput(":codecs", codecTestState);
313
+ assert.match(output[0], /^int @/);
314
+
315
+ completions = completeInput(":codec un", codecTestState)[0];
316
+ assert(completions.includes(":codec unload"));
317
+ completions = completeInput(":codec unload ", codecTestState)[0];
318
+ assert(completions.includes(":codec unload int"));
319
+ completions = completeInput(":codec load i", codecTestState)[0];
320
+ assert(completions.includes(":codec load int"));
321
+
322
+ output = await evaluateInput(":codec unload int", codecTestState);
323
+ assert.equal(output[0], "unloaded codec int");
324
+ output = await evaluateInput(":codecs", codecTestState);
325
+ assert.equal(output[0], "(none)");
326
+
327
+ output = await evaluateInput(":type bool = <{} true, {} false>", codecTestState);
328
+ output = await evaluateInput(
329
+ `:codec define yn bool ({ parse: (text) => Value.variant(text.trim() === "yes" ? "true" : "false", Value.product({})), print: (v) => v.tag === "true" ? "YES" : "NO" })`,
330
+ codecTestState
331
+ );
332
+ assert.match(output[0], /^defined codec yn for @/);
333
+
334
+ output = await evaluateInput(":input bool yn", codecTestState);
335
+ output = await evaluateInput("yes", codecTestState);
336
+ assert.match(output[0], /yn: YES/);
337
+
338
+ // Projection on product with variant field (avoids filter variable collisions)
339
+ const projState = createState();
340
+ output = await evaluateInput("{{}|a x, {} y}", projState);
341
+ assert.match(output[0], /^\{\{\}\|a x, \{\} y\}/);
342
+ output = await evaluateInput(".x", projState);
343
+ assert.match(output[0], /^\{\}\|a/);
344
+
345
+ const projState2 = createState();
346
+ output = await evaluateInput("{{}|a a, {} y}", projState2);
347
+ assert.match(output[0], /^\{\{\}\|a a, \{\} y\}/);
348
+ output = await evaluateInput(".a", projState2);
349
+ assert.match(output[0], /^\{\}\|a/);
350
+
351
+ // Auto-loading dependencies and bidirectional type aliasing tests
352
+ const autoLoadState = createState();
353
+ output = await evaluateInput(":codec load int", autoLoadState);
354
+ assert.match(output[0], /auto-loaded (?:Examples\/)?arithmetics\.k/);
355
+ assert.ok(autoLoadState.typeAliases.int, "int type alias should be registered");
356
+ output = await evaluateInput(":input int", autoLoadState);
357
+ output = await evaluateInput("123", autoLoadState);
358
+ assert.match(output[0], /int: 123/);
359
+
360
+ const utf8AutoState = createState();
361
+ output = await evaluateInput(":codec load utf8", utf8AutoState);
362
+ assert.match(output[0], /auto-loaded core\.k/);
363
+ assert.ok(utf8AutoState.typeAliases.utf8, "utf8 type alias should be registered");
364
+ assert.ok(utf8AutoState.typeAliases.string, "string type alias should be registered");
365
+ assert.equal(utf8AutoState.typeAliases.utf8, utf8AutoState.typeAliases.string);
366
+ output = await evaluateInput(":input utf8", utf8AutoState);
367
+ output = await evaluateInput("test utf8", utf8AutoState);
368
+ assert.match(output[0], /utf8: test utf8/);
369
+
370
+ const ieeeAutoState = createState();
371
+ output = await evaluateInput(":codec load ieee", ieeeAutoState);
372
+ assert.match(output[0], /auto-loaded (?:Examples\/)?ieee\.k/);
373
+ assert.ok(ieeeAutoState.typeAliases.ieee, "ieee type alias should be registered");
374
+ assert.ok(ieeeAutoState.typeAliases.float64, "float64 type alias should be registered");
375
+ assert.equal(ieeeAutoState.typeAliases.ieee, ieeeAutoState.typeAliases.float64);
376
+
377
+ const jsonInputState = createState();
378
+ await evaluateInput(":load core.k", jsonInputState);
379
+ await evaluateInput(":load ieee.k", jsonInputState);
380
+ await evaluateInput(":codec load json", jsonInputState);
381
+ output = await evaluateInput(":input {string a, float64 n}", jsonInputState);
382
+ assert.match(output[0], /enter value text/);
383
+ output = await evaluateInput('{"a":"Woj","n":123}', jsonInputState);
384
+ assert.match(output[0], /json: \{"a":"Woj","n":123\}/);
385
+
386
+ const unitState = createState();
387
+ output = await evaluateInput(":codec load unit", unitState);
388
+ assert.match(output[0], /loaded codec unit/);
389
+ output = await evaluateInput(":input unit", unitState);
390
+ assert.match(output[0], /enter value text/);
391
+ output = await evaluateInput("{}", unitState);
392
+ assert.match(output[0], /unit: \{\}/);
393
+
394
+ const polyAutoState = createState();
395
+ output = await evaluateInput(":load Examples/poly.k", polyAutoState);
396
+ assert.equal(output[0], "loaded Examples/poly.k");
397
+ assert.ok(polyAutoState.relAliases.reverse, "poly relations should be loaded");
398
+ assert.ok(polyAutoState.typeAliases.int, "arithmetics int should be auto-loaded");
399
+
400
+ // Verify :timing and :time commands
401
+ const timingState = createState();
402
+ output = await evaluateInput(":time ()", timingState);
403
+ assert.equal(output[0], "{} ?{}");
404
+ assert.match(output[1], /\/\* comp: .*, exec: .* \*\//);
405
+ assert.ok(timingState.lastTiming);
406
+ assert.ok(typeof timingState.lastTiming.compileMs === "number");
407
+ assert.ok(typeof timingState.lastTiming.executeMs === "number");
408
+
409
+ output = await evaluateInput(":timing on", timingState);
410
+ assert.equal(output[0], "timing reporting enabled");
411
+ assert.equal(timingState.showTiming, true);
412
+ output = await evaluateInput("()", timingState);
413
+ assert.equal(output[0], "{} ?{}");
414
+ assert.match(output[1], /\/\* comp: .*, exec: .* \*\//);
415
+
416
+ output = await evaluateInput(":timing off", timingState);
417
+ assert.equal(output[0], "timing reporting disabled");
418
+ assert.equal(timingState.showTiming, false);
419
+ output = await evaluateInput("()", timingState);
420
+ assert.equal(output.length, 1);
421
+ assert.equal(output[0], "{} ?{}");
422
+
280
423
  fs.rmSync(tmpDir, { recursive: true, force: true });
281
424
  fs.rmSync(symlinkPath, { force: true });
282
425
  console.log("OK");
@@ -0,0 +1,40 @@
1
+ import assert from "node:assert";
2
+ import { compileLibraryBuffer, compileObjectBuffer, decodeObject } from "../object.mjs";
3
+ import { objectToKIRP, retypeObjectRelation } from "../kir.mjs";
4
+ import { validateKIRP, validateObjectPayload } from "../objects/validate.mjs";
5
+
6
+ const object = decodeObject(compileObjectBuffer("id = (); id", { source: "validate.k" }));
7
+ assert.equal(validateObjectPayload(object), object);
8
+ assert.equal(validateKIRP(objectToKIRP(object)).format, "k-ir");
9
+ const retyped = validateKIRP(retypeObjectRelation(object, "__main__", [["open-product", []]]));
10
+ assert.equal(retyped.layer, "KIR-P");
11
+ assert.equal(retyped.kind, "executable");
12
+ assert(!("instanceKey" in retyped));
13
+
14
+ const library = decodeObject(compileLibraryBuffer("succ = |succ;\n", { source: "validate-lib.k" }));
15
+ assert.equal(validateObjectPayload(library), library);
16
+ assert.equal(validateKIRP(objectToKIRP(library)).kind, "library");
17
+
18
+ const badObject = {
19
+ ...object,
20
+ rels: {
21
+ ...object.rels,
22
+ __main__: {
23
+ ...object.rels.__main__,
24
+ typeDerivation: { status: "maybe" }
25
+ }
26
+ }
27
+ };
28
+ assert.throws(
29
+ () => validateObjectPayload(badObject),
30
+ /typeDerivation\.status must be one of/
31
+ );
32
+
33
+ const badKir = objectToKIRP(object);
34
+ badKir.rels.__main__.inputPattern = 999;
35
+ assert.throws(
36
+ () => validateKIRP(badKir),
37
+ /inputPattern is out of range/
38
+ );
39
+
40
+ console.log("OK");