@fraczak/k 7.1.0 → 8.0.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 (64) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/DOCS/BIG_PICTURE.md +54 -7
  3. package/DOCS/CODECS.md +47 -2
  4. package/DOCS/CODEC_ARCHITECTURE.md +14 -5
  5. package/DOCS/DICTIONARY.md +13 -4
  6. package/DOCS/FILE_FORMATS.md +3 -2
  7. package/DOCS/JSON_APPROACHES_COMPARISON.md +306 -0
  8. package/DOCS/NAMED_PATTERNS.md +317 -0
  9. package/DOCS/PATTERNS.md +59 -11
  10. package/DOCS/REPL.md +24 -5
  11. package/DOCS/WEB_REPL_REQUIREMENTS.md +4 -2
  12. package/Examples/arithmetics.k +22 -17
  13. package/Examples/bnat.k +21 -11
  14. package/Examples/buda.k +37 -20
  15. package/Examples/byte.k +3 -1
  16. package/Examples/code.k +38 -39
  17. package/Examples/djson-btrie.k +414 -0
  18. package/Examples/djson-ptrie.k +663 -0
  19. package/Examples/djson.k +323 -0
  20. package/Examples/esc.k +1 -1
  21. package/Examples/karatsuba-mult.k +16 -13
  22. package/Examples/list.k +8 -4
  23. package/Examples/nat.k +17 -11
  24. package/Examples/poly.k +11 -3
  25. package/Examples/vhdl_to_k.k +43 -31
  26. package/README.md +42 -15
  27. package/Value.mjs +28 -3
  28. package/backend-api.mjs +5 -1
  29. package/browser/repl-browser.mjs +6 -0
  30. package/browser/tree-view.mjs +82 -24
  31. package/browser/vfs-data.mjs +21 -12
  32. package/codecs/README.md +3 -0
  33. package/codecs/djson-btrie.mjs +560 -0
  34. package/codecs/djson-ptrie.mjs +587 -0
  35. package/codecs/djson.mjs +270 -0
  36. package/codecs/int.mjs +2 -1
  37. package/codecs/json.mjs +7 -2
  38. package/codecs/k-parse.mjs +7 -1
  39. package/codecs/k-print.mjs +4 -0
  40. package/codecs/runtime/codec-sdk.mjs +16 -2
  41. package/codecs/runtime/pattern-json.mjs +80 -1
  42. package/codecs/runtime/prefix-codec.mjs +629 -67
  43. package/codecs/runtime/show-value.mjs +93 -51
  44. package/codecs/runtime/unicode-string.mjs +29 -6
  45. package/codecs/show.mjs +4 -0
  46. package/codecs/utf16.mjs +2 -1
  47. package/core.k +79 -115
  48. package/index.mjs +3 -0
  49. package/named-patterns.mjs +267 -0
  50. package/object.mjs +31 -3
  51. package/package.json +5 -1
  52. package/parser.jison +68 -5
  53. package/parser.mjs +111 -60
  54. package/pretty.mjs +5 -0
  55. package/repl.mjs +245 -28
  56. package/scripts/build-repl-html.mjs +76 -5
  57. package/scripts/perf-djson.mjs +1142 -0
  58. package/scripts/run-tests.mjs +5 -0
  59. package/symbol-table.mjs +20 -2
  60. package/tests/bench-djson-tries.mjs +340 -0
  61. package/tests/test-codecs.mjs +211 -0
  62. package/tests/test-named-patterns.mjs +189 -0
  63. package/tests/test-repl-html.mjs +52 -4
  64. package/tests/test-repl.mjs +223 -0
@@ -0,0 +1,189 @@
1
+ // tests/test-named-patterns.mjs
2
+ // Comprehensive test suite for named patterns and type schemes.
3
+
4
+ import assert from "node:assert/strict";
5
+ import k from "../index.mjs";
6
+ import { Value } from "../Value.mjs";
7
+
8
+ console.log("==> Running Named Patterns Test Suite");
9
+
10
+ // Test 1: 0-arity pattern and composite product
11
+ console.log("--> Test 1: 0-arity pattern (bit[]) and product (byte[])");
12
+ {
13
+ const script = `
14
+ bit[] = < {}0, {}1 >;
15
+ byte[] = {
16
+ bit[] b0, bit[] b1, bit[] b2, bit[] b3,
17
+ bit[] b4, bit[] b5, bit[] b6, bit[] b7
18
+ };
19
+
20
+ is_byte = ? byte[];
21
+
22
+ sample = {
23
+ {}|0 b0, {}|1 b1, {}|0 b2, {}|1 b3,
24
+ {}|0 b4, {}|1 b5, {}|0 b6, {}|1 b7
25
+ };
26
+
27
+ sample is_byte
28
+ `;
29
+ const fn = k.compile(script);
30
+ const out = fn(Value.product({}));
31
+ assert.equal(out.type, "{}");
32
+ assert.equal(out.product.b0.tag, "0");
33
+ assert.equal(out.product.b1.tag, "1");
34
+ console.log(" Passed: 0-arity patterns and composition.");
35
+ }
36
+
37
+ // Test 2: Parameterized list pattern
38
+ console.log("--> Test 2: Parameterized pattern (list[X])");
39
+ {
40
+ const script = `
41
+ bit[] = < {}0, {}1 >;
42
+ list[X] = < {}nil, { X car, Y cdr } cons > = Y;
43
+
44
+ is_bit_list = ? list[bit[]];
45
+
46
+ empty = {}|nil is_bit_list;
47
+ one = { {}|1 car, {}|nil cdr }|cons is_bit_list;
48
+ two = {
49
+ {}|0 car,
50
+ { {}|1 car, {}|nil cdr }|cons cdr
51
+ }|cons is_bit_list;
52
+
53
+ { empty empty, one one, two two }
54
+ `;
55
+ const fn = k.compile(script);
56
+ const out = fn(Value.product({}));
57
+ assert.equal(out.product.empty.tag, "nil");
58
+ assert.equal(out.product.one.tag, "cons");
59
+ assert.equal(out.product.one.value.product.car.tag, "1");
60
+ assert.equal(out.product.two.tag, "cons");
61
+ assert.equal(out.product.two.value.product.car.tag, "0");
62
+ console.log(" Passed: Parameterized list pattern.");
63
+ }
64
+
65
+ // Test 3: Multi-parameter templates and composition
66
+ console.log("--> Test 3: Multi-parameter templates (pair, dict)");
67
+ {
68
+ const script = `
69
+ bit[] = < {}0, {}1 >;
70
+ pair[A, B] = { A key, B val };
71
+ list[X] = < {}nil, { X car, Y cdr } cons > = Y;
72
+ dict[K, V] = list[pair[K, V]];
73
+
74
+ is_dict = ? dict[bit[], bit[]];
75
+
76
+ entry = { {}|0 key, {}|1 val };
77
+ d = { entry car, {}|nil cdr }|cons is_dict;
78
+
79
+ d
80
+ `;
81
+ const fn = k.compile(script);
82
+ const out = fn(Value.product({}));
83
+ assert.equal(out.tag, "cons");
84
+ assert.equal(out.value.product.car.product.key.tag, "0");
85
+ assert.equal(out.value.product.car.product.val.tag, "1");
86
+ console.log(" Passed: Multi-parameter template composition.");
87
+ }
88
+
89
+ // Test 4: Existential internal variables (type unification across fields)
90
+ console.log("--> Test 4: Existential variables (same_pair[])");
91
+ {
92
+ const scriptOk = `
93
+ same_pair[] = { T first, T second };
94
+ check = ? same_pair[];
95
+ { {}|0 first, {}|1 second } check
96
+ `;
97
+ const fnOk = k.compile(scriptOk);
98
+ const outOk = fnOk(Value.product({}));
99
+ assert.equal(outOk.product.first.tag, "0");
100
+ assert.equal(outOk.product.second.tag, "1");
101
+
102
+ const scriptBad = `
103
+ same_pair[] = { T first, T second };
104
+ check = ? same_pair[];
105
+ { {} first, {}|0 second } check
106
+ `;
107
+ assert.throws(
108
+ () => k.compile(scriptBad),
109
+ /Cannot unify/
110
+ );
111
+ console.log(" Passed: Existential variables unified across fields.");
112
+ }
113
+
114
+ // Test 5: Recursive tree with internal knot and list
115
+ console.log("--> Test 5: Recursive tree with knot T (tree[X])");
116
+ {
117
+ const script = `
118
+ list[X] = < {}nil, { X car, Y cdr } cons > = Y;
119
+ tree[X] = < {}nil, { X val, list[T] children } node > = T;
120
+
121
+ bit[] = < {}0, {}1 >;
122
+ is_tree = ? tree[bit[]];
123
+
124
+ leaf = { {}|1 val, {}|nil children }|node is_tree;
125
+ root = {
126
+ {}|0 val,
127
+ { leaf car, {}|nil cdr }|cons children
128
+ }|node is_tree;
129
+
130
+ { leaf leaf, root root }
131
+ `;
132
+ const fn = k.compile(script);
133
+ const out = fn(Value.product({}));
134
+ assert.equal(out.product.leaf.tag, "node");
135
+ assert.equal(out.product.leaf.value.product.val.tag, "1");
136
+ assert.equal(out.product.root.tag, "node");
137
+ assert.equal(out.product.root.value.product.val.tag, "0");
138
+ console.log(" Passed: Recursive tree template with list port.");
139
+ }
140
+
141
+ // Test 6: Polymorphic relation with shared type variable
142
+ console.log("--> Test 6: Polymorphic relation ({ X car, list[X] cdr })");
143
+ {
144
+ const script = `
145
+ list[X] = < {}nil, { X car, Y cdr } cons > = Y;
146
+ head = ? { X car, list[X] cdr } .car;
147
+
148
+ bit[] = < {}0, {}1 >;
149
+ h1 = { {}|1 car, {}|nil cdr } head;
150
+
151
+ h1
152
+ `;
153
+ const fn = k.compile(script);
154
+ const out = fn(Value.product({}));
155
+ assert.equal(out.tag, "1");
156
+ console.log(" Passed: Polymorphic relation head with list[X].");
157
+ }
158
+
159
+ // Test 7: Error handling for templates
160
+ console.log("--> Test 7: Error handling (unknown, arity, cycles, dupes)");
161
+ {
162
+ assert.throws(
163
+ () => k.compile("check = ? missing[]; ()"),
164
+ /Unknown pattern template 'missing\[\]'/
165
+ );
166
+
167
+ assert.throws(
168
+ () => k.compile("list[X] = {}; check = ? list[]; ()"),
169
+ /Pattern 'list' expects 1 argument, got 0/
170
+ );
171
+
172
+ assert.throws(
173
+ () => k.compile("pair[A, B] = {}; check = ? pair[{}]; ()"),
174
+ /Pattern 'pair' expects 2 arguments, got 1/
175
+ );
176
+
177
+ assert.throws(
178
+ () => k.compile("a[] = b[]; b[] = a[]; check = ? a[]; ()"),
179
+ /Cyclic pattern template dependency detected/
180
+ );
181
+
182
+ assert.throws(
183
+ () => k.compile("dupe[X, X] = {}; ()"),
184
+ /Duplicate parameter 'X' in pattern 'dupe'/
185
+ );
186
+ console.log(" Passed: Error conditions correctly handled.");
187
+ }
188
+
189
+ console.log("==> All Named Patterns Tests Passed Successfully!");
@@ -140,6 +140,12 @@ if (chromiumBin) {
140
140
  const expectedFiles = [
141
141
  "arithmetics.k",
142
142
  "core.k",
143
+ "djson-btrie.k",
144
+ "djson-btrie.mjs",
145
+ "djson-ptrie.k",
146
+ "djson-ptrie.mjs",
147
+ "djson.k",
148
+ "djson.mjs",
143
149
  "ieee.k",
144
150
  "ieee.mjs",
145
151
  "int.mjs",
@@ -521,9 +527,9 @@ if (chromiumBin) {
521
527
  const v4Collapsed = v4 ? !v4.open : false;
522
528
 
523
529
  // Click Expand All
524
- const actionBtns = lastEntry.querySelectorAll(".k-btn-action");
525
- const expandBtn = actionBtns[0];
526
- const collapseBtn = actionBtns[1];
530
+ const toolbarActionBtns = lastEntry.querySelectorAll(".k-output-toolbar .k-btn-action");
531
+ const expandBtn = toolbarActionBtns[0];
532
+ const collapseBtn = toolbarActionBtns[1];
527
533
 
528
534
  expandBtn.click();
529
535
  const allOpenAfterExpand = v4.open;
@@ -535,13 +541,48 @@ if (chromiumBin) {
535
541
  );
536
542
  const v4CollapsedAgain = newV4 ? !newV4.open : false;
537
543
 
544
+ // Verify Copy buttons on Value and Pattern section headers
545
+ const patSection = lastEntry.querySelector(".k-tree-pat-section");
546
+ const copyValBtn = valSection?.querySelector("summary.k-tree-section-summary > .k-btn-copy-value");
547
+ const copyPatBtn = patSection?.querySelector("summary.k-tree-section-summary > .k-btn-copy-pattern");
548
+
549
+ const copiedTexts = [];
550
+ const origWriteText = navigator.clipboard?.writeText;
551
+ if (navigator.clipboard) {
552
+ navigator.clipboard.writeText = async (txt) => { copiedTexts.push(txt); };
553
+ } else {
554
+ Object.defineProperty(navigator, "clipboard", {
555
+ value: { writeText: async (txt) => { copiedTexts.push(txt); } },
556
+ configurable: true
557
+ });
558
+ }
559
+
560
+ const valOpenBefore = valSection?.open;
561
+ copyValBtn?.click();
562
+ const valOpenAfter = valSection?.open;
563
+
564
+ const patOpenBefore = patSection?.open;
565
+ copyPatBtn?.click();
566
+ const patOpenAfter = patSection?.open;
567
+
568
+ if (origWriteText && navigator.clipboard) {
569
+ navigator.clipboard.writeText = origWriteText;
570
+ }
571
+
538
572
  return {
539
573
  v1Open,
540
574
  v2Open,
541
575
  v3Open,
542
576
  v4Collapsed,
543
577
  allOpenAfterExpand,
544
- v4CollapsedAgain
578
+ v4CollapsedAgain,
579
+ toolbarBtnCount: toolbarActionBtns.length,
580
+ hasCopyValBtn: !!copyValBtn,
581
+ hasCopyPatBtn: !!copyPatBtn,
582
+ valDidNotToggle: valOpenBefore === valOpenAfter,
583
+ patDidNotToggle: patOpenBefore === patOpenAfter,
584
+ copiedValText: copiedTexts[0] || "",
585
+ copiedPatText: copiedTexts[1] || ""
545
586
  };
546
587
  })()`);
547
588
 
@@ -551,6 +592,13 @@ if (chromiumBin) {
551
592
  assert.strictEqual(variantDepthAndActionsCheck.v4Collapsed, true, "Variant level 4 must be collapsed by default");
552
593
  assert.strictEqual(variantDepthAndActionsCheck.allOpenAfterExpand, true, "Expand button should expand collapsed nodes");
553
594
  assert.strictEqual(variantDepthAndActionsCheck.v4CollapsedAgain, true, "Collapse button should reset deeper nodes to collapsed");
595
+ assert.strictEqual(variantDepthAndActionsCheck.toolbarBtnCount, 2, "Toolbar action group should only have Expand and Collapse buttons");
596
+ assert.strictEqual(variantDepthAndActionsCheck.hasCopyValBtn, true, "Value section summary should have Copy button");
597
+ assert.strictEqual(variantDepthAndActionsCheck.hasCopyPatBtn, true, "Pattern section summary should have Copy button");
598
+ assert.strictEqual(variantDepthAndActionsCheck.valDidNotToggle, true, "Clicking Value Copy button should not toggle Value details");
599
+ assert.strictEqual(variantDepthAndActionsCheck.patDidNotToggle, true, "Clicking Pattern Copy button should not toggle Pattern details");
600
+ assert.strictEqual(variantDepthAndActionsCheck.copiedValText, "{}|_|0|1|0|1", "Value Copy button should copy K value definition");
601
+ assert(variantDepthAndActionsCheck.copiedPatText.startsWith("?<"), `Pattern Copy button should copy K pattern filter definition, got: ${variantDepthAndActionsCheck.copiedPatText}`);
554
602
 
555
603
  // 4. Verify shortened welcome banner
556
604
  const bannerCheck = await evaluateAsync(`(() => {
@@ -226,6 +226,18 @@ output = await evaluateInput(':input json.mjs {"left":true,"right":false}', json
226
226
  assert.match(output[0], /json\.mjs: \{"left":true,"right":false\}/);
227
227
  assert.equal(promptForState(jsonState), "> ");
228
228
 
229
+ // Test djson.mjs in repl
230
+ const djsonState = createState();
231
+ output = await evaluateInput(":codec load ./codecs/djson.mjs", djsonState);
232
+ assert.match(output[0], /^loaded codec djson\.mjs/);
233
+ completions = completeInput(":input dj", djsonState)[0];
234
+ assert(completions.includes(":input djson.mjs"));
235
+ output = await evaluateInput(
236
+ ':input djson.mjs {"msg":"hello","values":[1,2]}',
237
+ djsonState
238
+ );
239
+ assert.match(output[0], /djson\.mjs: \{"msg":"hello","values":\[1,2\]\}/);
240
+
229
241
  const replPath = fileURLToPath(new URL("../repl.mjs", import.meta.url));
230
242
  try {
231
243
  fs.rmSync(symlinkPath, { force: true });
@@ -548,6 +560,217 @@ output = await evaluateInput("len", wasmListState);
548
560
  assert.match(output[0], /int\.mjs:\s*3/);
549
561
  assert.match(output[1], /\(wasm\)/);
550
562
 
563
+ // Verify :limit command and bounded printing
564
+ const limitState = createState({ engine: "wasm" });
565
+ output = await evaluateInput(":limit", limitState);
566
+ assert.equal(output[0], "Current output limit: none");
567
+
568
+ output = await evaluateInput(":limit 40", limitState);
569
+ assert.equal(output[0], "output limit set to 40");
570
+
571
+ output = await evaluateInput(":limit", limitState);
572
+ assert.equal(output[0], "Current output limit: 40");
573
+
574
+ output = await evaluateInput(":load Examples/arithmetics.k", limitState);
575
+ output = await evaluateInput("{ 10 int x, 5 int y } plus", limitState);
576
+ assert(output[0].includes("..."));
577
+ assert(output[0].length <= 80);
578
+
579
+ output = await evaluateInput(":limit none", limitState);
580
+ assert.equal(output[0], "output limit disabled");
581
+ output = await evaluateInput(":limit", limitState);
582
+ assert.equal(output[0], "Current output limit: none");
583
+
584
+ // Verify Examples/djson.k (json?, dot, has_key?, keys, values, remove, put)
585
+ const jsonKState = createState({ engine: "wasm" });
586
+ output = await evaluateInput(":load Examples/djson.k", jsonKState);
587
+ assert.equal(output[0], "loaded Examples/djson.k");
588
+ await evaluateInput(":codec load codecs/djson.mjs", jsonKState);
589
+ await evaluateInput(
590
+ ':input djson.mjs {"name":"Alice","age":30,"active":true}',
591
+ jsonKState
592
+ );
593
+ output = await evaluateInput(
594
+ "{/object cdr car .key key, /object object} dot",
595
+ jsonKState
596
+ );
597
+ assert.match(output[0], /djson\.mjs: 30/);
598
+
599
+ await evaluateInput(
600
+ ':input djson.mjs {"name":"Alice","age":30}',
601
+ jsonKState
602
+ );
603
+ output = await evaluateInput(
604
+ "{/object car .key key, /object object} remove object",
605
+ jsonKState
606
+ );
607
+ assert.match(output[0], /djson\.mjs: \{"name":"Alice"\}/);
608
+
609
+ // Verify Examples/djson-btrie.k (json?, dot, nth, put, remove)
610
+ const btrieState = createState({ engine: "wasm" });
611
+ output = await evaluateInput(":load Examples/djson-btrie.k", btrieState);
612
+ assert.equal(output[0], "loaded Examples/djson-btrie.k");
613
+ await evaluateInput(":codec load codecs/djson-btrie.mjs", btrieState);
614
+ await evaluateInput(
615
+ ':input djson-btrie.mjs ["age", {"name":"Alice","age":30}]',
616
+ btrieState
617
+ );
618
+ output = await evaluateInput(
619
+ "{{0 index, /list list} nth /string key, " +
620
+ "{1 index, /list list} nth /object object} dot",
621
+ btrieState
622
+ );
623
+ assert.match(output[0], /djson-btrie\.mjs: 30/);
624
+
625
+ await evaluateInput(
626
+ ':input djson-btrie.mjs ["age", {"name":"Alice","age":30}]',
627
+ btrieState
628
+ );
629
+ output = await evaluateInput(
630
+ "{{0 index, /list list} nth /string key, " +
631
+ "{1 index, /list list} nth /object object} remove object",
632
+ btrieState
633
+ );
634
+ assert.match(output[0], /djson-btrie\.mjs: \{"name":"Alice"\}/);
635
+
636
+ // Verify Examples/djson-ptrie.k (json?, dot, nth, list_size, obj_size)
637
+ const ptrieState = createState({ engine: "wasm" });
638
+ output = await evaluateInput(":load Examples/djson-ptrie.k", ptrieState);
639
+ assert.equal(output[0], "loaded Examples/djson-ptrie.k");
640
+ await evaluateInput(":codec load codecs/djson-ptrie.mjs", ptrieState);
641
+ await evaluateInput(
642
+ ':input djson-ptrie.mjs ["age", {"name":"Alice","age":30}]',
643
+ ptrieState
644
+ );
645
+ output = await evaluateInput(
646
+ "{{0 index, /list list} nth /string key, " +
647
+ "{1 index, /list list} nth /object object} dot",
648
+ ptrieState
649
+ );
650
+ assert.match(output[0], /djson-ptrie\.mjs: 30/);
651
+
652
+ // Verify named pattern persistence, listing, display, and tab completion
653
+ const patState = createState({ engine: "wasm" });
654
+
655
+ output = await evaluateInput(":patterns", patState);
656
+ assert.equal(output[0], "(none)");
657
+ output = await evaluateInput(":pats", patState);
658
+ assert.equal(output[0], "(none)");
659
+
660
+ output = await evaluateInput("bit[] = < {} 0, {} 1 >;", patState);
661
+ assert.match(output[0], /\/\* comp:/);
662
+
663
+ output = await evaluateInput(":patterns", patState);
664
+ assert.equal(output[0], "bit[]");
665
+ output = await evaluateInput(":pats", patState);
666
+ assert.equal(output[0], "bit[]");
667
+
668
+ output = await evaluateInput(":pattern bit", patState);
669
+ assert.equal(
670
+ output[0],
671
+ "bit[] = <{} 0, {} 1>; -- " +
672
+ "@MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
673
+ );
674
+ output = await evaluateInput(":pattern bit[]", patState);
675
+ assert.equal(
676
+ output[0],
677
+ "bit[] = <{} 0, {} 1>; -- " +
678
+ "@MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
679
+ );
680
+ output = await evaluateInput(":pat bit", patState);
681
+ assert.equal(
682
+ output[0],
683
+ "bit[] = <{} 0, {} 1>; -- " +
684
+ "@MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
685
+ );
686
+ output = await evaluateInput(
687
+ ":pattern @MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg",
688
+ patState
689
+ );
690
+ assert.equal(
691
+ output[0],
692
+ "bit[] = <{} 0, {} 1>; -- " +
693
+ "@MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
694
+ );
695
+
696
+ await assert.rejects(
697
+ () => evaluateInput(":pattern unknown", patState),
698
+ /Unknown pattern 'unknown'/
699
+ );
700
+ await assert.rejects(
701
+ () => evaluateInput(":pattern", patState),
702
+ /:pattern requires a pattern name/
703
+ );
704
+
705
+ completions = completeInput(":pat", patState)[0];
706
+ assert(completions.includes(":pattern"));
707
+ assert(completions.includes(":patterns"));
708
+ assert(completions.includes(":pat"));
709
+ assert(completions.includes(":pats"));
710
+
711
+ completions = completeInput(":pattern b", patState)[0];
712
+ assert(completions.includes(":pattern bit"));
713
+ completions = completeInput(":pat b", patState)[0];
714
+ assert(completions.includes(":pat bit"));
715
+ completions = completeInput(":pattern @Mjusy", patState)[0];
716
+ assert(
717
+ completions.includes(
718
+ ":pattern @MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
719
+ )
720
+ );
721
+
722
+ output = await evaluateInput("{} | 0 ? bit[]", patState);
723
+ assert.match(output[0], /\{\}\|0/);
724
+
725
+ output = await evaluateInput(
726
+ "pair[A, B] = { A first, B second };",
727
+ patState
728
+ );
729
+ output = await evaluateInput(":pattern pair", patState);
730
+ assert.equal(
731
+ output[0],
732
+ "pair[A, B] = {A first, B second}; -- " +
733
+ "@KqTBNkznjWWwync2Mm94XjCez63neJBJtgYcJJEE5kEm"
734
+ );
735
+
736
+ output = await evaluateInput(":load Examples/list.k", patState);
737
+ assert.equal(output[0], "loaded Examples/list.k");
738
+ output = await evaluateInput(":patterns", patState);
739
+ assert(output[0].includes("bit[]"));
740
+ assert(output[0].includes("list[X]"));
741
+ assert(output[0].includes("pair[A, B]"));
742
+
743
+ output = await evaluateInput(":pattern list", patState);
744
+ assert.equal(
745
+ output[0],
746
+ "list[X] = <{} nil, {X car, Y cdr} cons>=Y; -- " +
747
+ "@JagVexzgseRr4rVZRmVpsMbr2AKXDjzZnz6Qnf8a9SNe"
748
+ );
749
+
750
+ output = await evaluateInput("{} | nil ? list[bit[]]", patState);
751
+ assert.match(output[0], /\{\}\|nil/);
752
+
753
+ const testKlibPath = path.join(tmpDir, "patterns-test.klib");
754
+ output = await evaluateInput(`:klib ${testKlibPath}`, patState);
755
+ assert.equal(output[0], `saved ${testKlibPath}`);
756
+
757
+ const freshState = createState({ engine: "wasm" });
758
+ output = await evaluateInput(`:load ${testKlibPath}`, freshState);
759
+ assert.equal(output[0], `loaded ${testKlibPath}`);
760
+ output = await evaluateInput(":patterns", freshState);
761
+ assert(output[0].includes("bit[]"));
762
+ assert(output[0].includes("pair[A, B]"));
763
+ assert(output[0].includes("list[X]"));
764
+
765
+ output = await evaluateInput(":pattern bit", freshState);
766
+ assert.equal(
767
+ output[0],
768
+ "bit[] = <{} 0, {} 1>; -- " +
769
+ "@MjusyRWZnVj8TR4BhFCCcMRrQbdKp9gPtC29LT9nZvjg"
770
+ );
771
+ output = await evaluateInput("{} | nil ? list[bit[]]", freshState);
772
+ assert.match(output[0], /\{\}\|nil/);
773
+
551
774
  fs.rmSync(tmpDir, { recursive: true, force: true });
552
775
  fs.rmSync(symlinkPath, { force: true });
553
776
  console.log("OK");