@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
@@ -29,16 +29,16 @@ output = await evaluateInput(":type nat", state);
29
29
  assert.match(output[0], /^\$ nat = /);
30
30
 
31
31
  output = await evaluateInput("succ = | succ;", state);
32
- output = await evaluateInput(":d succ", state);
32
+ output = await evaluateInput(":rel succ", state);
33
33
  assert.match(output[0], /^succ = /);
34
34
 
35
- output = await evaluateInput(":codes", state);
35
+ output = await evaluateInput(":types", state);
36
36
  assert.match(output[0], /^nat = @/m);
37
37
 
38
38
  output = await evaluateInput(":rels", state);
39
39
  assert.match(output[0], /^succ = @/m);
40
40
 
41
- output = await evaluateInput(":run succ", state);
41
+ output = await evaluateInput("succ", state);
42
42
  assert.equal(output[0], "{}|succ ?<{} succ, ...>");
43
43
 
44
44
  output = await evaluateInput("/zero", state);
@@ -48,24 +48,38 @@ assert.equal(state.value.toJSON(), "succ");
48
48
  output = await evaluateInput("succ", state);
49
49
  assert.match(output[0], /^\{\}\|succ\|succ \?</);
50
50
 
51
- output = await evaluateInput(":t succ", state);
52
- assert.match(output[0], /^succ : /);
53
-
54
- output = await evaluateInput(":d succ", state);
51
+ output = await evaluateInput(":rel succ", state);
55
52
  assert.match(output[0], /^succ = /);
56
53
 
57
54
  let completions = completeInput(":he", state)[0];
58
55
  assert.deepEqual(completions, [":help"]);
59
56
 
60
57
  const canonicalPartial = state.typeAliases.nat.slice(0, 8);
61
- completions = completeInput(`:run ${canonicalPartial}`, state)[0];
58
+ completions = completeInput(`:rel ${canonicalPartial}`, state)[0];
62
59
  assert(completions.some((line) => line.endsWith(state.typeAliases.nat)));
63
60
 
64
61
  completions = completeInput(`(${canonicalPartial}`, state)[0];
65
62
  assert(completions.includes(`(${state.typeAliases.nat}`));
66
63
 
67
- completions = completeInput(":run su", state)[0];
68
- assert(completions.includes(":run succ"));
64
+ completions = completeInput(":rel su", state)[0];
65
+ assert(completions.includes(":rel succ"));
66
+ completions = completeInput(":ty", state)[0];
67
+ assert(completions.includes(":types"));
68
+ assert(completions.includes(":type"));
69
+
70
+ await assert.rejects(
71
+ () => evaluateInput(":run succ", state),
72
+ /Unknown command ':run'/
73
+ );
74
+ await assert.rejects(
75
+ () => evaluateInput(":time ()", state),
76
+ /Unknown command ':time'/
77
+ );
78
+ await assert.rejects(
79
+ () => evaluateInput(":t succ", state),
80
+ /Unknown command ':t'/
81
+ );
82
+
69
83
  completions = completeInput(":co", state)[0];
70
84
  assert(completions.includes(":codec"));
71
85
 
@@ -134,11 +148,11 @@ fs.writeFileSync(sourcePath, "$ nat = <{} zero, nat succ>;\nsucc = $nat |succ $n
134
148
  const loadedSource = createState();
135
149
  output = await evaluateInput(`:load ${sourcePath}`, loadedSource);
136
150
  assert.equal(output[0], `loaded ${sourcePath}`);
137
- output = await evaluateInput(":codes", loadedSource);
151
+ output = await evaluateInput(":types", loadedSource);
138
152
  assert.match(output[0], /^nat = @/m);
139
153
  output = await evaluateInput(":rels", loadedSource);
140
154
  assert.match(output[0], /^succ = @/m);
141
- output = await evaluateInput(":d twice", loadedSource);
155
+ output = await evaluateInput(":rel twice", loadedSource);
142
156
  assert.match(output[0], /^twice = \$nat succ succ \$nat; -- @/);
143
157
  output = await evaluateInput(":C nat", loadedSource);
144
158
  assert.match(output[0], /^\$ nat = < nat succ, @[^ ]+ zero >; -- @/);
@@ -160,6 +174,9 @@ export function parse(text) {
160
174
  }
161
175
 
162
176
  export function print(value) {
177
+ if (!value || (value.tag !== "true" && value.tag !== "false")) {
178
+ throw new Error("expected true or false");
179
+ }
163
180
  return value.tag;
164
181
  }
165
182
  `);
@@ -172,58 +189,52 @@ assert(completions.includes(":codec list"));
172
189
  completions = completeInput(`:codec load ${tmpDir}/bool`, codecState)[0];
173
190
  assert(completions.includes(`:codec load ${codecPath}`));
174
191
  output = await evaluateInput(`:codec load ${codecPath}`, codecState);
175
- assert.equal(output[0], `loaded codec yn for ${boolHash}`);
192
+ assert.equal(output[0], `loaded codec bool-codec.mjs`);
176
193
  output = await evaluateInput(":codec list", codecState);
177
- assert.match(output[0], new RegExp(`^yn ${boolHash.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
178
- completions = completeInput(":input bo", codecState)[0];
179
- assert(completions.includes(":input bool"));
180
- completions = completeInput(":input bool y", codecState)[0];
181
- assert(completions.includes(":input bool yn"));
182
- output = await evaluateInput(":input bool yn", codecState);
183
- assert.equal(output[0], `input ${boolHash} using yn: enter value text`);
194
+ assert.match(output[0], /^bool-codec\.mjs/);
195
+ completions = completeInput(":input", codecState)[0];
196
+ assert(completions.includes(":input"));
197
+ completions = completeInput(":input ", codecState)[0];
198
+ assert(completions.includes(":input bool-codec.mjs"));
199
+ completions = completeInput(":input b", codecState)[0];
200
+ assert(completions.includes(":input bool-codec.mjs"));
201
+ output = await evaluateInput(":input bool-codec.mjs", codecState);
202
+ assert.equal(promptForState(codecState), "bool-codec.mjs> ");
184
203
  output = await evaluateInput("true", codecState);
185
204
  assert.match(output[0], /\{\}\|true \?</);
186
- assert.match(output[0], /yn: true/);
205
+ assert.match(output[0], /bool-codec\.mjs: true/);
187
206
  output = await evaluateInput("{} | false", codecState);
188
- assert.doesNotMatch(output[0], /yn: false/);
207
+ assert.match(output[0], /bool-codec\.mjs: false/);
208
+ output = await evaluateInput("{}", codecState);
209
+ assert.doesNotMatch(output[0], /bool-codec\.mjs:/);
189
210
 
190
211
  const utf8State = createState();
191
212
  output = await evaluateInput(":load core.k", utf8State);
192
213
  assert.equal(output[0], "loaded core.k");
193
214
  output = await evaluateInput(":codec load ./codecs/utf8.mjs", utf8State);
194
- assert.equal(output[0], `loaded codec utf8 for ${utf8State.typeAliases.string}`);
195
- output = await evaluateInput(":input string utf8", utf8State);
196
- assert.equal(output[0], `input ${utf8State.typeAliases.string} using utf8: enter value text`);
197
- output = await evaluateInput("hello", utf8State);
198
- assert.match(output[0], /utf8: hello/);
215
+ assert.equal(output[0], `loaded codec utf8.mjs`);
216
+ output = await evaluateInput(":input utf8.mjs hello", utf8State);
217
+ assert.match(output[0], /utf8\.mjs: hello/);
199
218
 
200
219
  const jsonState = createState();
201
- output = await evaluateInput("$ bool = < {} true, {} false >;", jsonState);
202
220
  output = await evaluateInput(":codec load ./codecs/json.mjs", jsonState);
203
- assert.match(output[0], /^loaded codec json for all types/);
221
+ assert.match(output[0], /^loaded codec json\.mjs/);
204
222
  output = await evaluateInput(":codec list", jsonState);
205
- assert.match(output[0], /^json all /);
206
- completions = completeInput(":input bool j", jsonState)[0];
207
- assert(completions.includes(":input bool json"));
208
- output = await evaluateInput(":input bool", jsonState);
209
- assert.equal(output[0], `input ${jsonState.typeAliases.bool}: enter value text`);
210
- assert.equal(promptForState(jsonState), "json> ");
223
+ assert.match(output[0], /^json\.mjs/);
224
+ completions = completeInput(":input j", jsonState)[0];
225
+ assert(completions.includes(":input json.mjs"));
226
+ output = await evaluateInput(":input json.mjs", jsonState);
227
+ assert.equal(promptForState(jsonState), "json.mjs> ");
211
228
  output = await evaluateInput("true", jsonState);
212
229
  assert.match(output[0], /\{\}\|true \?</);
213
- assert.match(output[0], /json: true/);
230
+ assert.match(output[0], /json\.mjs: true/);
214
231
  assert.equal(promptForState(jsonState), "> ");
215
- output = await evaluateInput(":input <{}true,{}false>", jsonState);
216
- assert.equal(output[0], `input ${jsonState.typeAliases.bool}: enter value text`);
217
- assert.equal(promptForState(jsonState), "json> ");
218
- output = await evaluateInput("false", jsonState);
232
+ output = await evaluateInput(":input json.mjs false", jsonState);
219
233
  assert.match(output[0], /\{\}\|false \?</);
220
- assert.match(output[0], /json: false/);
234
+ assert.match(output[0], /json\.mjs: false/);
221
235
  assert.equal(promptForState(jsonState), "> ");
222
- output = await evaluateInput(":input {bool left, bool right} json", jsonState);
223
- assert.match(output[0], /^input @[^ ]+ using json: enter value text$/);
224
- assert.equal(promptForState(jsonState), "json> ");
225
- output = await evaluateInput('{"left":true,"right":false}', jsonState);
226
- assert.match(output[0], /json: \{"left":true,"right":false\}/);
236
+ output = await evaluateInput(':input json.mjs {"left":true,"right":false}', jsonState);
237
+ assert.match(output[0], /json\.mjs: \{"left":true,"right":false\}/);
227
238
  assert.equal(promptForState(jsonState), "> ");
228
239
 
229
240
  const replPath = fileURLToPath(new URL("../repl.mjs", import.meta.url));
@@ -238,7 +249,7 @@ const reloaded = createState();
238
249
  output = await evaluateInput(`:load ${libPath}`, reloaded);
239
250
  assert.equal(output[0], `loaded ${libPath}`);
240
251
 
241
- output = await evaluateInput(":codes", reloaded);
252
+ output = await evaluateInput(":types", reloaded);
242
253
  assert.match(output[0], /^nat = @/m);
243
254
 
244
255
  output = await evaluateInput(":rels", reloaded);
@@ -250,7 +261,7 @@ assert.equal(output[0], "{}|succ ?<{} succ, ...>");
250
261
  const noAliasReloaded = createState();
251
262
  output = await evaluateInput(`:load --no-alias ${libPath}`, noAliasReloaded);
252
263
  assert.equal(output[0], `loaded ${libPath}`);
253
- output = await evaluateInput(":codes", noAliasReloaded);
264
+ output = await evaluateInput(":types", noAliasReloaded);
254
265
  assert.equal(output[0], "(none)");
255
266
  output = await evaluateInput(":rels", noAliasReloaded);
256
267
  assert.equal(output[0], "(none)");
@@ -258,7 +269,7 @@ assert.equal(output[0], "(none)");
258
269
  const shorthand = createState();
259
270
  output = await evaluateInput("$ nat = <{} zero, nat succ>\n; succ = |succ\n;", shorthand);
260
271
  assert.match(output[0], /\/\* comp: .* \*\//);
261
- output = await evaluateInput(":codes", shorthand);
272
+ output = await evaluateInput(":types", shorthand);
262
273
  assert.match(output[0], /^nat = @/m);
263
274
  output = await evaluateInput(":rels", shorthand);
264
275
  assert.match(output[0], /^succ = @/m);
@@ -268,7 +279,7 @@ assert.match(output[0], / false/);
268
279
  assert.match(output[0], / true/);
269
280
  output = await evaluateInput("$ maybe = <{} none, {} some>;", shorthand);
270
281
  assert.match(output[0], /\/\* comp: .* \*\//);
271
- output = await evaluateInput(":codes", shorthand);
282
+ output = await evaluateInput(":types", shorthand);
272
283
  assert.match(output[0], /^maybe = @/m);
273
284
  output = await evaluateInput("{} | true not", shorthand);
274
285
  assert.match(output[0], /^\{\}\|false \?</);
@@ -302,41 +313,45 @@ assert.match(output[0], /^\{\}\|_\|0\|1\|0\|1/);
302
313
  output = await evaluateInput("{10 int x, 5 int y} plus", arithState);
303
314
  assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
304
315
 
305
- // Verify built-in codecs, :codecs, and :codec unload
316
+ // Verify codecs loading, listing, and unloading
306
317
  const codecTestState = createState();
307
318
  output = await evaluateInput(":codecs", codecTestState);
308
319
  assert.equal(output[0], "(none)");
309
320
 
310
- output = await evaluateInput(":codec load int", codecTestState);
311
- assert.match(output[0], /^loaded codec int for @/);
321
+ output = await evaluateInput(":codec load ./codecs/int.mjs", codecTestState);
322
+ assert.match(output[0], /^loaded codec int\.mjs/);
312
323
 
313
324
  output = await evaluateInput(":codecs", codecTestState);
314
- assert.match(output[0], /^int @/);
325
+ assert.match(output[0], /^int\.mjs/);
315
326
 
316
327
  completions = completeInput(":codec un", codecTestState)[0];
317
328
  assert(completions.includes(":codec unload"));
318
329
  completions = completeInput(":codec unload ", codecTestState)[0];
319
- assert(completions.includes(":codec unload int"));
320
- completions = completeInput(":codec load i", codecTestState)[0];
321
- assert(completions.includes(":codec load int"));
330
+ assert(completions.includes(":codec unload int.mjs"));
331
+ completions = completeInput(":codec load ./codecs/i", codecTestState)[0];
332
+ assert(completions.includes(":codec load ./codecs/int.mjs"));
322
333
 
323
- output = await evaluateInput(":codec unload int", codecTestState);
324
- assert.equal(output[0], "unloaded codec int");
334
+ output = await evaluateInput(":codec unload int.mjs", codecTestState);
335
+ assert.equal(output[0], "unloaded codec int.mjs");
325
336
  output = await evaluateInput(":codecs", codecTestState);
326
337
  assert.equal(output[0], "(none)");
327
338
 
328
339
  output = await evaluateInput("$ bool = < {} true, {} false >;", codecTestState);
329
340
  registerCodec(codecTestState, {
330
- name: "yn",
331
- codes: [codecTestState.typeAliases.bool],
332
- universal: false,
341
+ name: "yn.mjs",
333
342
  parse: (text) => Value.variant(text.trim() === "yes" ? "true" : "false", Value.product({})),
334
- print: (v) => v.tag === "true" ? "YES" : "NO"
335
- }, "<test>");
336
-
337
- output = await evaluateInput(":input bool yn", codecTestState);
343
+ print: (v) => {
344
+ if (v.tag !== "true" && v.tag !== "false") throw new Error("not a bool");
345
+ return v.tag === "true" ? "YES" : "NO";
346
+ }
347
+ }, "yn.mjs");
348
+
349
+ output = await evaluateInput(":input yn.mjs", codecTestState);
350
+ assert.equal(promptForState(codecTestState), "yn.mjs> ");
338
351
  output = await evaluateInput("yes", codecTestState);
339
- assert.match(output[0], /yn: YES/);
352
+ assert.match(output[0], /yn\.mjs: YES/);
353
+ output = await evaluateInput(":input yn.mjs no", codecTestState);
354
+ assert.match(output[0], /yn\.mjs: NO/);
340
355
 
341
356
  // Projection on product with variant field (avoids filter variable collisions)
342
357
  const projState = createState();
@@ -351,103 +366,88 @@ assert.match(output[0], /^\{\{\}\|a a, \{\} y\}/);
351
366
  output = await evaluateInput(".a", projState2);
352
367
  assert.match(output[0], /^\{\}\|a/);
353
368
 
354
- // Auto-loading dependencies and bidirectional type aliasing tests
355
- const autoLoadState = createState();
356
- output = await evaluateInput(":codec load int", autoLoadState);
357
- assert.match(output[0], /auto-loaded (?:Examples\/)?arithmetics\.k/);
358
- assert.ok(autoLoadState.typeAliases.int, "int type alias should be registered");
359
- output = await evaluateInput(":input int", autoLoadState);
360
- output = await evaluateInput("123", autoLoadState);
361
- assert.match(output[0], /int: 123/);
369
+ // Explicit loading of dependencies and codec testing (no auto-loading)
370
+ const intState = createState();
371
+ await evaluateInput(":load Examples/arithmetics.k", intState);
372
+ output = await evaluateInput(":codec load ./codecs/int.mjs", intState);
373
+ assert.match(output[0], /^loaded codec int\.mjs/);
374
+ output = await evaluateInput(":input int.mjs", intState);
375
+ output = await evaluateInput("123", intState);
376
+ assert.match(output[0], /int\.mjs: 123/);
377
+ output = await evaluateInput(":input int.mjs", intState);
378
+ output = await evaluateInput("[0,1,2]", intState);
379
+ assert.match(output[0], /int\.mjs: \[0,1,2\]/);
362
380
 
363
381
  const utf8AutoState = createState();
364
- output = await evaluateInput(":codec load utf8", utf8AutoState);
365
- assert.match(output[0], /auto-loaded core\.k/);
366
- assert.ok(utf8AutoState.typeAliases.utf8, "utf8 type alias should be registered");
367
- assert.ok(utf8AutoState.typeAliases.string, "string type alias should be registered");
368
- assert.equal(utf8AutoState.typeAliases.utf8, utf8AutoState.typeAliases.string);
369
- output = await evaluateInput(":input utf8", utf8AutoState);
382
+ await evaluateInput(":load core.k", utf8AutoState);
383
+ output = await evaluateInput(":codec load ./codecs/utf8.mjs", utf8AutoState);
384
+ assert.match(output[0], /^loaded codec utf8\.mjs/);
385
+ output = await evaluateInput(":input utf8.mjs", utf8AutoState);
370
386
  output = await evaluateInput("test utf8", utf8AutoState);
371
- assert.match(output[0], /utf8: test utf8/);
387
+ assert.match(output[0], /utf8\.mjs: test utf8/);
372
388
 
373
389
  const ieeeAutoState = createState();
374
- output = await evaluateInput(":codec load ieee", ieeeAutoState);
375
- assert.match(output[0], /auto-loaded (?:Examples\/)?ieee\.k/);
390
+ await evaluateInput(":load Examples/ieee.k", ieeeAutoState);
391
+ output = await evaluateInput(":codec load ./codecs/ieee.mjs", ieeeAutoState);
392
+ assert.match(output[0], /^loaded codec ieee\.mjs/);
376
393
  assert.ok(ieeeAutoState.typeAliases.ieee, "ieee type alias should be registered");
377
- assert.ok(ieeeAutoState.typeAliases.float64, "float64 type alias should be registered");
378
- assert.equal(ieeeAutoState.typeAliases.ieee, ieeeAutoState.typeAliases.float64);
379
394
 
380
395
  const jsonInputState = createState();
381
396
  await evaluateInput(":load core.k", jsonInputState);
382
- await evaluateInput(":load ieee.k", jsonInputState);
383
- await evaluateInput(":codec load json", jsonInputState);
384
- output = await evaluateInput(":input {string a, float64 n}", jsonInputState);
385
- assert.match(output[0], /enter value text/);
386
- output = await evaluateInput('{"a":"Woj","n":123}', jsonInputState);
387
- assert.match(output[0], /json: \{"a":"Woj","n":123\}/);
388
-
389
- // Verify pattern expressions and standalone codec input
390
- const patternInputState = createState();
391
- await evaluateInput(":codec load json", patternInputState);
392
-
393
- // 1. Explicit pattern expression: ? {string a, float64 n} json
394
- output = await evaluateInput(":input ? {string a, float64 n} json", patternInputState);
395
- assert.match(output[0], /input \? \{string a, float64 n\} using json: enter value text/);
396
- output = await evaluateInput('{"a":"Woj","n":123}', patternInputState);
397
- assert.match(output[0], /json: \{"a":"Woj","n":123\}/);
398
-
399
- // 2. Wildcard pattern expression: ? (...) json
400
- output = await evaluateInput(":input ? (...) json", patternInputState);
401
- assert.match(output[0], /enter value text/);
402
- output = await evaluateInput('{"anything": [1, 2, 3]}', patternInputState);
403
- assert.match(output[0], /json: \{"anything":\[1,2,3\]\}/);
404
-
405
- // 3. Standalone codec input: :input json (no predefined type or pattern required)
406
- output = await evaluateInput(":input json", patternInputState);
407
- assert.match(output[0], /input json: enter value text/);
408
- output = await evaluateInput('{"hello":"world"}', patternInputState);
409
- assert.match(output[0], /json: \{"hello":"world"\}/);
410
-
411
- // 4. Pattern mismatch detection
412
- output = await evaluateInput(":input ? {string a, float64 n} json", patternInputState);
413
- await assert.rejects(
414
- () => evaluateInput('{"wrong_key": 999}', patternInputState),
415
- /Closed product pattern does not match value fields/
416
- );
417
-
418
- // 5. Bare :input defaults to :input (...) with auto-loaded json codec
397
+ await evaluateInput(":load Examples/ieee.k", jsonInputState);
398
+ await evaluateInput(":codec load ./codecs/json.mjs", jsonInputState);
399
+ output = await evaluateInput(':input json.mjs {"a":"Woj","n":123}', jsonInputState);
400
+ assert.match(output[0], /json\.mjs: \{"a":"Woj","n":123\}/);
401
+
402
+ // Verify standalone codec input, one-line and interactive modes, and multi-codec printing
403
+ const multiCodecState = createState();
404
+ await evaluateInput(":codec load ./codecs/json.mjs", multiCodecState);
405
+ await evaluateInput(":codec load ./codecs/utf8.mjs", multiCodecState);
406
+ await evaluateInput(":codec load ./codecs/int.mjs", multiCodecState);
407
+
408
+ // 1. One-line JSON input: :input json.mjs {"hello":"world"}
409
+ output = await evaluateInput(':input json.mjs {"hello":"world"}', multiCodecState);
410
+ assert.match(output[0], /json\.mjs: \{"hello":"world"\}/);
411
+
412
+ // 2. Interactive JSON input: :input json.mjs then {"anything": [1, 2, 3]}
413
+ output = await evaluateInput(":input json.mjs", multiCodecState);
414
+ assert.equal(promptForState(multiCodecState), "json.mjs> ");
415
+ output = await evaluateInput('{"anything": [1, 2, 3]}', multiCodecState);
416
+ assert.match(output[0], /json\.mjs: \{"anything":\[1,2,3\]\}/);
417
+ assert.equal(promptForState(multiCodecState), "> ");
418
+
419
+ // 3. Integer scalar and list input
420
+ output = await evaluateInput(":input int.mjs 42", multiCodecState);
421
+ assert.match(output[0], /int\.mjs: 42/);
422
+
423
+ output = await evaluateInput(":input int.mjs [0,1,2]", multiCodecState);
424
+ assert.match(output[0], /int\.mjs: \[0,1,2\]/);
425
+
426
+ // 4. Bare :input displays usage and loaded codecs
419
427
  const bareInputState = createState();
420
- assert.equal(promptForState(bareInputState), "> ");
421
428
  output = await evaluateInput(":input", bareInputState);
422
- assert.deepEqual(output, ["input (...): enter value text"]);
423
- assert.equal(promptForState(bareInputState), "json> ");
424
- output = await evaluateInput('{"test": 123}', bareInputState);
425
- assert.match(output[0], /json: \{"test":123\}/);
426
- assert.equal(promptForState(bareInputState), "> ");
427
-
428
- // 6. Pattern as type prefixed by dollar
429
- const dollarPatternState = createState();
430
- await evaluateInput(":load arithmetics.k", dollarPatternState);
431
- output = await evaluateInput(":input $ int", dollarPatternState);
432
- assert.deepEqual(output, ["input $ int: enter value text"]);
433
- assert.equal(promptForState(dollarPatternState), "int> ");
434
- output = await evaluateInput("42", dollarPatternState);
435
- assert.match(output[0], /int: 42/);
436
-
437
- await evaluateInput("$ bool = <{} true, {} false>;", dollarPatternState);
438
- output = await evaluateInput(":input $ bool json", dollarPatternState);
439
- assert.deepEqual(output, ["input $ bool using json: enter value text"]);
440
- assert.equal(promptForState(dollarPatternState), "json> ");
441
- output = await evaluateInput("true", dollarPatternState);
442
- assert.match(output[0], /json: true/);
429
+ assert.match(output[0], /^Usage: :input <codec\.mjs> \[text\]/);
430
+ assert.match(output[1], /Loaded codecs:/);
431
+
432
+ // 5. Codec with leading dollar sign is accepted
433
+ output = await evaluateInput(":input $ int.mjs 42", multiCodecState);
434
+ assert.match(output[0], /int\.mjs: 42/);
435
+ output = await evaluateInput(":input $int.mjs 100", multiCodecState);
436
+ assert.match(output[0], /int\.mjs: 100/);
437
+
438
+ // 6. Unknown codec throws descriptive error
439
+ await assert.rejects(
440
+ () => evaluateInput(":input nonexistent 123", multiCodecState),
441
+ /Codec 'nonexistent' is not loaded/
442
+ );
443
443
 
444
444
  const unitState = createState();
445
- output = await evaluateInput(":codec load unit", unitState);
446
- assert.match(output[0], /loaded codec unit/);
447
- output = await evaluateInput(":input unit", unitState);
448
- assert.match(output[0], /enter value text/);
445
+ output = await evaluateInput(":codec load ./codecs/unit.mjs", unitState);
446
+ assert.match(output[0], /^loaded codec unit\.mjs/);
447
+ output = await evaluateInput(":input unit.mjs", unitState);
448
+ assert.equal(promptForState(unitState), "unit.mjs> ");
449
449
  output = await evaluateInput("{}", unitState);
450
- assert.match(output[0], /unit: \{\}/);
450
+ assert.match(output[0], /unit\.mjs: \{\}/);
451
451
 
452
452
  const polyAutoState = createState();
453
453
  output = await evaluateInput(":load Examples/poly.k", polyAutoState);
@@ -455,21 +455,75 @@ assert.equal(output[0], "loaded Examples/poly.k");
455
455
  assert.ok(polyAutoState.relAliases.reverse, "poly relations should be loaded");
456
456
  assert.ok(polyAutoState.typeAliases.int, "arithmetics int should be auto-loaded");
457
457
 
458
- // Verify timing reporting is always on and :time command
458
+ // Verify timing reporting is always on and reflects active engine
459
459
  const timingState = createState();
460
460
  assert.equal(timingState.showTiming, true);
461
+ assert.equal(timingState.engine, "wasm");
461
462
 
462
463
  output = await evaluateInput("()", timingState);
463
464
  assert.equal(output[0], "{} ?{}");
464
- assert.match(output[1], /\/\* comp: .*, exec: .* \*\//);
465
-
466
- output = await evaluateInput(":time ()", timingState);
467
- assert.equal(output[0], "{} ?{}");
468
- assert.match(output[1], /\/\* comp: .*, exec: .* \*\//);
465
+ assert.match(output[1], /\/\* comp: .*, exec: .* \(wasm\) \*\//);
469
466
  assert.ok(timingState.lastTiming);
467
+ assert.equal(timingState.lastTiming.engine, "wasm");
470
468
  assert.ok(typeof timingState.lastTiming.compileMs === "number");
471
469
  assert.ok(typeof timingState.lastTiming.executeMs === "number");
472
470
 
471
+ // Verify :engine command and switching between wasm and js engines
472
+ output = await evaluateInput(":engine", timingState);
473
+ assert.equal(output[0], "Current engine: wasm");
474
+
475
+ output = await evaluateInput(":engine js", timingState);
476
+ assert.equal(output[0], "engine set to js");
477
+ assert.equal(timingState.engine, "js");
478
+
479
+ output = await evaluateInput("()", timingState);
480
+ assert.equal(output[0], "{} ?{}");
481
+ assert.match(output[1], /\/\* comp: .*, exec: .* \(js\) \*\//);
482
+ assert.equal(timingState.lastTiming.engine, "js");
483
+
484
+ // Verify shortcuts :wasm and :js
485
+ output = await evaluateInput(":wasm", timingState);
486
+ assert.equal(output[0], "engine set to wasm");
487
+ assert.equal(timingState.engine, "wasm");
488
+
489
+ output = await evaluateInput(":js", timingState);
490
+ assert.equal(output[0], "engine set to js");
491
+ assert.equal(timingState.engine, "js");
492
+
493
+ // Verify :reset preserves the active engine
494
+ output = await evaluateInput(":reset", timingState);
495
+ assert.equal(output[0], "reset");
496
+ assert.equal(timingState.engine, "js");
497
+
498
+ // Verify invalid engine error
499
+ await assert.rejects(
500
+ () => evaluateInput(":engine invalid", timingState),
501
+ /Invalid engine 'invalid'\. Valid engines: wasm, js/
502
+ );
503
+
504
+ // Verify autocompletion for :engine
505
+ completions = completeInput(":engine ", timingState)[0];
506
+ assert(completions.includes(":engine wasm"));
507
+ assert(completions.includes(":engine js"));
508
+
509
+ // Verify evaluating expressions in JS engine
510
+ const jsState = createState({ engine: "js" });
511
+ assert.equal(jsState.engine, "js");
512
+
513
+ output = await evaluateInput("$ nat = < {} zero, nat succ >;\nsucc = | succ;\n{} | zero succ succ", jsState);
514
+ assert.match(output[0], /^\{\}\|zero\|succ\|succ/);
515
+ assert.match(output[1], /\(js\)/);
516
+
517
+ output = await evaluateInput("$ bool = < {} true, {} false >;\nnot = $bool </true | false, {} | true >;\n{} | true not", jsState);
518
+ assert.match(output[0], /^\{\}\|false/);
519
+ assert.match(output[1], /\(js\)/);
520
+
521
+ output = await evaluateInput(":load Examples/arithmetics.k", jsState);
522
+ assert.equal(output[0], "loaded Examples/arithmetics.k");
523
+ output = await evaluateInput("{ 10 int x, 5 int y } plus", jsState);
524
+ assert.match(output[0], /^\{\}\|_\|1\|1\|1\|1\|\+/);
525
+ assert.match(output[1], /\(js\)/);
526
+
473
527
  fs.rmSync(tmpDir, { recursive: true, force: true });
474
528
  fs.rmSync(symlinkPath, { force: true });
475
529
  console.log("OK");
@@ -25,3 +25,20 @@ assert.equal(valueToK(foreignTwo), "{}|_|0|1|+");
25
25
 
26
26
  const decoded = decodeWire(encodeToWire(foreignTwo, INT_PATTERN)).value;
27
27
  assert.equal(printInt(decoded), "2");
28
+
29
+ const { INT_LIST_PATTERN, parse: parseIntCodec } = await import("../codecs/int.mjs");
30
+ const { valueWithEnvelopeToK } = await import("../codecs/runtime/show-value.mjs");
31
+
32
+ const parsedList = parseIntCodec("[0,1,2]");
33
+ const listWire = encodeToWire(parsedList, INT_LIST_PATTERN);
34
+ const decodedList = decodeWire(listWire).value;
35
+ assert.equal(
36
+ valueWithEnvelopeToK(decodedList),
37
+ "{{}|_|0|+ car, {{}|_|1|+ car, {{}|_|0|1|+ car, {}|nil cdr}|cons cdr}|cons cdr}|cons ?<{<<X1 0, X1 1, {}=X2 _>=X1 +, X1 -> car, X0 cdr} cons, X2 nil>=X0"
38
+ );
39
+ assert.equal(printInt(decodedList), "[0,1,2]");
40
+
41
+ const emptyList = parseIntCodec("[]");
42
+ const emptyWire = encodeToWire(emptyList, INT_LIST_PATTERN);
43
+ const decodedEmpty = decodeWire(emptyWire).value;
44
+ assert.equal(printInt(decodedEmpty), "[]");
@@ -172,6 +172,7 @@ assert.strictEqual(patTree.open, true, "Root pattern node must be open");
172
172
  assert(patTree.textContent.includes("recursive"), "Recursive cycle must be detected and marked 'recursive'");
173
173
 
174
174
  // 5. Test createOutputEntryElement
175
+ variantVal.pattern = recursivePattern;
175
176
  const entryEl = createOutputEntryElement({
176
177
  value: variantVal,
177
178
  rawText: "{}|_|1|0|1|+ ?<<X0 0, X0 1, {} _>=X0 +, X0 ->\nint: 11",
@@ -180,6 +181,8 @@ const entryEl = createOutputEntryElement({
180
181
 
181
182
  assert(entryEl.querySelector(".k-tree-view"), "Tree view container must exist");
182
183
  assert(entryEl.querySelector(".k-raw-view"), "Raw view container must exist");
184
+ assert.strictEqual(entryEl.querySelector(".k-tree-value-section")?.open, false, "Value section must be collapsed by default");
185
+ assert.strictEqual(entryEl.querySelector(".k-tree-pat-section")?.open, false, "Pattern section must be collapsed by default");
183
186
  assert(entryEl.textContent.includes("int: 11"), "Codec output must be visible in container");
184
187
 
185
188
  console.log("OK - All tree view unit tests passed.");