@fraczak/k 6.2.0 → 6.2.2

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.
@@ -0,0 +1,7 @@
1
+ list? = ?< {} nil, {X car, Y cdr} cons > = Y;
2
+ nil = {} |nil list?;
3
+ singleton = {() car, nil cdr} | cons list?;
4
+ cons = {.car car, .cdr cdr} | cons list?;
5
+ nil? = list? /nil nil;
6
+ car = list? /cons .car;
7
+ cdr = list? /cons .cdr;
package/Examples/nat.k ADDED
@@ -0,0 +1,12 @@
1
+ $ nat = < {} 0, nat +1 >;
2
+ 0 = {} | 0 ;
3
+ 0? = $ nat { / 0 if, () then} .then;
4
+ succ = | +1 $ nat ;
5
+ pred = $nat / +1 ;
6
+ add = ?{ $nat x, ... } = X <{.x pred x, .y succ y} ?X add, .y> ;
7
+ mult = <
8
+ {{.x pred x, .y y} mult x, .y y} add,
9
+ 0
10
+ > ;
11
+
12
+ succ {()x,()y} add
@@ -0,0 +1,50 @@
1
+ -- VHDL to K Translation Patterns
2
+
3
+ -- VHDL: signal a : std_logic;
4
+ -- K: $ bit = <{} 0, {} 1>;
5
+
6
+ -- VHDL: signal bus : std_logic_vector(7 downto 0);
7
+ -- K: $ byte = {bit 0, bit 1, bit 2, bit 3, bit 4, bit 5, bit 6, bit 7};
8
+
9
+ -- VHDL: entity adder is port(a,b: in std_logic; sum: out std_logic); end;
10
+ -- K: adder = ${bit a, bit b} {.a a_out, .b b_out, xor_gate sum_out};
11
+
12
+ -- VHDL: process(clk) begin if rising_edge(clk) then q <= d; end if; end;
13
+ -- K: dff = $bit .; -- Identity (register holds value)
14
+
15
+ -- VHDL: y <= a and b;
16
+ -- K: and_gate = ${bit a, bit b} <{.a.1 1, .b.1 1, 1}, 0>;
17
+
18
+ -- VHDL: y <= a or b;
19
+ -- K: or_gate = ${bit a, bit b} <{.a.0 0, .b.0 0, 0}, 1>;
20
+
21
+ -- VHDL: y <= not a;
22
+ -- K: not_gate = $bit <{.0 0, 1}, {.1 1, 0}>;
23
+
24
+ -- VHDL: with sel select y <= a when "00", b when "01", c when others;
25
+ -- K: mux = ${bit sel0, bit sel1, bit a, bit b, bit c} <
26
+ -- {.sel0.0 0, .sel1.0 0, .a},
27
+ -- {.sel0.1 1, .sel1.0 0, .b},
28
+ -- .c>;
29
+
30
+ -- VHDL: architecture of 4-bit adder
31
+ four_bit_adder = ${
32
+ {bit 0, bit 1, bit 2, bit 3} a,
33
+ {bit 0, bit 1, bit 2, bit 3} b
34
+ } {
35
+ {.a.0 a0, .b.0 b0} full_adder fa0,
36
+ {.a.1 a1, .b.1 b1, .fa0.carry c_in} full_adder fa1,
37
+ {.a.2 a2, .b.2 b2, .fa1.carry c_in} full_adder fa2,
38
+ {.a.3 a3, .b.3 b3, .fa2.carry c_in} full_adder fa3,
39
+ {.fa0.sum 0, .fa1.sum 1, .fa2.sum 2, .fa3.sum 3} sum,
40
+ .fa3.carry overflow
41
+ };
42
+
43
+ -- VHDL: Full adder component
44
+ full_adder = ${bit a, bit b, bit c_in} {
45
+ {.a a_val, .b b_val} xor_gate ab_xor,
46
+ {.ab_xor xor_val, .c_in c_val} xor_gate sum,
47
+ {.a a_val, .b b_val} and_gate ab_and,
48
+ {.ab_xor xor_val, .c_in c_val} and_gate xor_cin_and,
49
+ {.ab_and and1, .xor_cin_and and2} or_gate carry
50
+ };
package/README.md CHANGED
@@ -195,6 +195,14 @@ Installed binary names are `k-` plus the source basename without `.mjs`, except
195
195
  for `k.mjs` itself. Source names that already include `k-`, such as
196
196
  `codecs/k-parse.mjs`, keep that name.
197
197
 
198
+ ## WebAssembly Backend
199
+
200
+ The experimental WebAssembly backend lives in the separate
201
+ [`fraczak/k-wasm`](https://github.com/fraczak/k-wasm) repository. Keeping the
202
+ backend separate avoids adding its WABT dependency to the core runtime. External
203
+ backends integrate with the compiler and binary codecs through
204
+ [`backend-api.mjs`](backend-api.mjs).
205
+
198
206
  ## Node.js API
199
207
 
200
208
  ```js
@@ -0,0 +1,12 @@
1
+ export { annotate } from "./index.mjs";
2
+ export { lowerToKVM, executeKVM } from "./kvm.mjs";
3
+ export { decodeWire, encodeToWire } from "./codecs/runtime/prefix-codec.mjs";
4
+ export { exportPatternGraph, NODE_KIND } from "./codecs/runtime/codec.mjs";
5
+ export { patternToPropertyList, propertyListToPattern } from "./codecs/runtime/pattern-json.mjs";
6
+ export { Product, Variant, fromObject } from "./Value.mjs";
7
+ export { decodeObject, loadLibrary } from "./object.mjs";
8
+ export { createState, evaluateInput } from "./repl.mjs";
9
+ export { parse as parseFloat64, print as printFloat64 } from "./codecs/ieee.mjs";
10
+ export { valueForCode } from "./repl-codecs.mjs";
11
+ export { default as codes } from "./codes.mjs";
12
+ export { default as run, run_converged } from "./run.mjs";
@@ -480,67 +480,89 @@ function refinePatternForValue(pattern, value) {
480
480
  }
481
481
 
482
482
  function collect(patternNodeId, currentValue) {
483
- const patternNode = base.nodes[patternNodeId];
484
- if (!patternNode) {
485
- throw new Error(`Unknown pattern node ${patternNodeId}`);
486
- }
487
- pushOccurrence(patternNodeId, currentValue);
483
+ const stack = [{ patternNodeId, currentValue }];
488
484
 
489
- switch (patternNode.kind) {
490
- case NODE_KIND.ANY:
491
- return;
485
+ while (stack.length > 0) {
486
+ const frame = stack.pop();
487
+ if (frame.extra) {
488
+ pushExtra(frame.patternNodeId, frame.label, frame.currentValue);
489
+ continue;
490
+ }
492
491
 
493
- case NODE_KIND.CLOSED_PRODUCT:
494
- case NODE_KIND.OPEN_PRODUCT: {
495
- if (!(currentValue instanceof Product)) {
496
- throw new Error(`Expected Product for pattern node ${patternNodeId}`);
497
- }
498
- const actualLabels = Object.keys(currentValue.product).sort();
499
- const explicit = patternNode.edges.map((edge) => edge.label);
500
- if (patternNode.kind === NODE_KIND.CLOSED_PRODUCT) {
501
- if (actualLabels.length !== explicit.length || actualLabels.some((label, idx) => label !== explicit[idx])) {
502
- throw new Error(`Closed product pattern does not match value fields [${actualLabels.join(", ")}]`);
492
+ const patternNode = base.nodes[frame.patternNodeId];
493
+ if (!patternNode) {
494
+ throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
495
+ }
496
+ pushOccurrence(frame.patternNodeId, frame.currentValue);
497
+
498
+ switch (patternNode.kind) {
499
+ case NODE_KIND.ANY:
500
+ break;
501
+
502
+ case NODE_KIND.CLOSED_PRODUCT:
503
+ case NODE_KIND.OPEN_PRODUCT: {
504
+ if (!(frame.currentValue instanceof Product)) {
505
+ throw new Error(`Expected Product for pattern node ${frame.patternNodeId}`);
503
506
  }
504
- } else {
505
- for (const label of explicit) {
506
- if (!(label in currentValue.product)) {
507
- throw new Error(`Open product pattern is missing required field '${label}'`);
507
+ const actualLabels = Object.keys(frame.currentValue.product).sort();
508
+ const explicit = patternNode.edges.map((edge) => edge.label);
509
+ if (patternNode.kind === NODE_KIND.CLOSED_PRODUCT) {
510
+ if (actualLabels.length !== explicit.length || actualLabels.some((label, idx) => label !== explicit[idx])) {
511
+ throw new Error(`Closed product pattern does not match value fields [${actualLabels.join(", ")}]`);
512
+ }
513
+ } else {
514
+ for (const label of explicit) {
515
+ if (!(label in frame.currentValue.product)) {
516
+ throw new Error(`Open product pattern is missing required field '${label}'`);
517
+ }
508
518
  }
509
519
  }
510
- }
511
520
 
512
- const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
513
- for (const label of actualLabels) {
514
- if (explicitTargets.has(label)) {
515
- collect(explicitTargets.get(label), currentValue.product[label]);
516
- } else if (patternNode.kind === NODE_KIND.OPEN_PRODUCT) {
517
- pushExtra(patternNodeId, label, currentValue.product[label]);
518
- } else {
519
- throw new Error(`Unexpected extra field '${label}' for closed product pattern`);
521
+ const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
522
+ for (let i = actualLabels.length - 1; i >= 0; i--) {
523
+ const label = actualLabels[i];
524
+ if (explicitTargets.has(label)) {
525
+ stack.push({
526
+ patternNodeId: explicitTargets.get(label),
527
+ currentValue: frame.currentValue.product[label]
528
+ });
529
+ } else if (patternNode.kind === NODE_KIND.OPEN_PRODUCT) {
530
+ stack.push({
531
+ extra: true,
532
+ patternNodeId: frame.patternNodeId,
533
+ label,
534
+ currentValue: frame.currentValue.product[label]
535
+ });
536
+ } else {
537
+ throw new Error(`Unexpected extra field '${label}' for closed product pattern`);
538
+ }
520
539
  }
540
+ break;
521
541
  }
522
- return;
523
- }
524
542
 
525
- case NODE_KIND.CLOSED_UNION:
526
- case NODE_KIND.OPEN_UNION: {
527
- if (!(currentValue instanceof Variant)) {
528
- throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
529
- }
530
- const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
531
- if (explicitTargets.has(currentValue.tag)) {
532
- collect(explicitTargets.get(currentValue.tag), currentValue.value);
533
- return;
534
- }
535
- if (patternNode.kind === NODE_KIND.OPEN_UNION) {
536
- pushExtra(patternNodeId, currentValue.tag, currentValue.value);
537
- return;
543
+ case NODE_KIND.CLOSED_UNION:
544
+ case NODE_KIND.OPEN_UNION: {
545
+ if (!(frame.currentValue instanceof Variant)) {
546
+ throw new Error(`Expected Variant for pattern node ${frame.patternNodeId}`);
547
+ }
548
+ const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
549
+ if (explicitTargets.has(frame.currentValue.tag)) {
550
+ stack.push({
551
+ patternNodeId: explicitTargets.get(frame.currentValue.tag),
552
+ currentValue: frame.currentValue.value
553
+ });
554
+ break;
555
+ }
556
+ if (patternNode.kind === NODE_KIND.OPEN_UNION) {
557
+ pushExtra(frame.patternNodeId, frame.currentValue.tag, frame.currentValue.value);
558
+ break;
559
+ }
560
+ throw new Error(`Unexpected variant tag '${frame.currentValue.tag}' for closed union pattern`);
538
561
  }
539
- throw new Error(`Unexpected variant tag '${currentValue.tag}' for closed union pattern`);
540
- }
541
562
 
542
- default:
543
- throw new Error(`Unknown pattern node kind: ${patternNode.kind}`);
563
+ default:
564
+ throw new Error(`Unknown pattern node kind: ${patternNode.kind}`);
565
+ }
544
566
  }
545
567
  }
546
568
 
@@ -614,6 +636,7 @@ function refinePatternForValue(pattern, value) {
614
636
  }
615
637
 
616
638
  if (baseNode.kind === NODE_KIND.CLOSED_PRODUCT || baseNode.kind === NODE_KIND.OPEN_PRODUCT) {
639
+ result.kind = NODE_KIND.CLOSED_PRODUCT;
617
640
  const values = occurrences.get(patternNodeId) || [];
618
641
  const explicit = new Map(baseNode.edges.map((edge) => [edge.label, edge.target]));
619
642
  let labels = [...explicit.keys()].sort();
@@ -713,47 +736,88 @@ function refinePatternForValue(pattern, value) {
713
736
 
714
737
  function coerceValueForPattern(pattern, value) {
715
738
  const base = normalizePattern(pattern);
716
-
717
- function coerce(patternNodeId, currentValue) {
718
- const patternNode = base.nodes[patternNodeId];
739
+ let result;
740
+ const stack = [{
741
+ patternNodeId: 0,
742
+ currentValue: value,
743
+ assign(coercedValue) {
744
+ result = coercedValue;
745
+ }
746
+ }];
747
+
748
+ while (stack.length > 0) {
749
+ const frame = stack.pop();
750
+ if (frame.passthrough) {
751
+ frame.assign();
752
+ continue;
753
+ }
754
+ const patternNode = base.nodes[frame.patternNodeId];
719
755
  if (!patternNode) {
720
- throw new Error(`Unknown pattern node ${patternNodeId}`);
756
+ throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
721
757
  }
722
758
 
723
759
  switch (patternNode.kind) {
724
760
  case NODE_KIND.ANY:
725
- return currentValue;
761
+ frame.assign(frame.currentValue);
762
+ break;
726
763
 
727
764
  case NODE_KIND.CLOSED_PRODUCT:
728
765
  case NODE_KIND.OPEN_PRODUCT: {
729
- let productValue = currentValue;
730
- if (currentValue instanceof Variant) {
731
- productValue = new Product({ [currentValue.tag]: currentValue.value });
766
+ let productValue = frame.currentValue;
767
+ if (frame.currentValue instanceof Variant) {
768
+ productValue = new Product({ [frame.currentValue.tag]: frame.currentValue.value });
732
769
  }
733
770
  if (!(productValue instanceof Product)) {
734
- throw new Error(`Expected Product for pattern node ${patternNodeId}`);
771
+ throw new Error(`Expected Product for pattern node ${frame.patternNodeId}`);
735
772
  }
736
773
 
737
774
  const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
738
775
  const product = {};
739
- for (const [label, childValue] of Object.entries(productValue.product)) {
740
- product[label] = explicitTargets.has(label)
741
- ? coerce(explicitTargets.get(label), childValue)
742
- : childValue;
776
+ frame.assign(new Product(product));
777
+ const entries = Object.entries(productValue.product);
778
+ for (let i = entries.length - 1; i >= 0; i--) {
779
+ const [label, childValue] = entries[i];
780
+ const target = explicitTargets.get(label);
781
+ if (target == null) {
782
+ stack.push({
783
+ assign() {
784
+ product[label] = childValue;
785
+ },
786
+ passthrough: true
787
+ });
788
+ } else {
789
+ stack.push({
790
+ patternNodeId: target,
791
+ currentValue: childValue,
792
+ assign(coercedValue) {
793
+ product[label] = coercedValue;
794
+ }
795
+ });
796
+ }
743
797
  }
744
- return new Product(product);
798
+ break;
745
799
  }
746
800
 
747
801
  case NODE_KIND.CLOSED_UNION:
748
802
  case NODE_KIND.OPEN_UNION: {
749
- if (!(currentValue instanceof Variant)) {
750
- throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
803
+ if (!(frame.currentValue instanceof Variant)) {
804
+ throw new Error(`Expected Variant for pattern node ${frame.patternNodeId}`);
805
+ }
806
+ const target = patternNode.edges.find((edge) => edge.label === frame.currentValue.tag)?.target;
807
+ const variant = new Variant(frame.currentValue.tag, undefined);
808
+ frame.assign(variant);
809
+ if (target == null) {
810
+ variant.value = frame.currentValue.value;
811
+ } else {
812
+ stack.push({
813
+ patternNodeId: target,
814
+ currentValue: frame.currentValue.value,
815
+ assign(coercedValue) {
816
+ variant.value = coercedValue;
817
+ }
818
+ });
751
819
  }
752
- const target = patternNode.edges.find((edge) => edge.label === currentValue.tag)?.target;
753
- return new Variant(
754
- currentValue.tag,
755
- target == null ? currentValue.value : coerce(target, currentValue.value)
756
- );
820
+ break;
757
821
  }
758
822
 
759
823
  default:
@@ -761,7 +825,7 @@ function coerceValueForPattern(pattern, value) {
761
825
  }
762
826
  }
763
827
 
764
- return coerce(0, value);
828
+ return result;
765
829
  }
766
830
 
767
831
  export { deriveClosedPattern, exportPatternGraph, refinePatternForValue, coerceValueForPattern, canonicalizePattern, intersectPropertyListPatterns, NODE_KIND };
@@ -120,87 +120,121 @@ function choiceWidth(cardinality) {
120
120
  }
121
121
 
122
122
  function encodeNode(writer, value, pattern, patternNodeId) {
123
- const patternNode = pattern.nodes[patternNodeId];
124
- if (!patternNode) {
125
- throw new Error(`Unknown pattern node ${patternNodeId}`);
126
- }
127
-
128
- switch (patternNode.kind) {
129
- case NODE_KIND.ANY:
130
- throw new Error("Pattern node 'any' is not directly encodable");
123
+ const stack = [{ value, patternNodeId }];
131
124
 
132
- case NODE_KIND.OPEN_PRODUCT:
133
- case NODE_KIND.CLOSED_PRODUCT: {
134
- if (!(value instanceof Product)) {
135
- throw new Error(`Expected Product for pattern node ${patternNodeId}`);
136
- }
137
- const actual = Object.keys(value.product).sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
138
- const expected = patternNode.edges.map((edge) => edge.label);
139
- if (actual.length !== expected.length || actual.some((label, i) => label !== expected[i])) {
140
- throw new Error(`Product fields do not match pattern node ${patternNodeId}`);
141
- }
142
- for (const edge of patternNode.edges) {
143
- encodeNode(writer, value.product[edge.label], pattern, edge.target);
144
- }
145
- return;
125
+ while (stack.length > 0) {
126
+ const frame = stack.pop();
127
+ const patternNode = pattern.nodes[frame.patternNodeId];
128
+ if (!patternNode) {
129
+ throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
146
130
  }
147
131
 
148
- case NODE_KIND.OPEN_UNION:
149
- case NODE_KIND.CLOSED_UNION: {
150
- if (!(value instanceof Variant)) {
151
- throw new Error(`Expected Variant for pattern node ${patternNodeId}`);
132
+ switch (patternNode.kind) {
133
+ case NODE_KIND.ANY:
134
+ throw new Error("Pattern node 'any' is not directly encodable");
135
+
136
+ case NODE_KIND.OPEN_PRODUCT:
137
+ case NODE_KIND.CLOSED_PRODUCT: {
138
+ if (!(frame.value instanceof Product)) {
139
+ throw new Error(`Expected Product for pattern node ${frame.patternNodeId}`);
140
+ }
141
+ const actual = Object.keys(frame.value.product).sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
142
+ const expected = patternNode.edges.map((edge) => edge.label);
143
+ if (actual.length !== expected.length || actual.some((label, i) => label !== expected[i])) {
144
+ throw new Error(`Product fields do not match pattern node ${frame.patternNodeId}`);
145
+ }
146
+ for (let i = patternNode.edges.length - 1; i >= 0; i--) {
147
+ const edge = patternNode.edges[i];
148
+ stack.push({ value: frame.value.product[edge.label], patternNodeId: edge.target });
149
+ }
150
+ break;
152
151
  }
153
- const tagOrdinal = patternNode.edges.findIndex((edge) => edge.label === value.tag);
154
- if (tagOrdinal === -1) {
155
- throw new Error(`Variant tag '${value.tag}' is not present in pattern node ${patternNodeId}`);
152
+
153
+ case NODE_KIND.OPEN_UNION:
154
+ case NODE_KIND.CLOSED_UNION: {
155
+ if (!(frame.value instanceof Variant)) {
156
+ throw new Error(`Expected Variant for pattern node ${frame.patternNodeId}`);
157
+ }
158
+ const tagOrdinal = patternNode.edges.findIndex((edge) => edge.label === frame.value.tag);
159
+ if (tagOrdinal === -1) {
160
+ throw new Error(`Variant tag '${frame.value.tag}' is not present in pattern node ${frame.patternNodeId}`);
161
+ }
162
+ const width = choiceWidth(patternNode.edges.length);
163
+ writer.writeBits(tagOrdinal, width);
164
+ stack.push({ value: frame.value.value, patternNodeId: patternNode.edges[tagOrdinal].target });
165
+ break;
156
166
  }
157
- const width = choiceWidth(patternNode.edges.length);
158
- writer.writeBits(tagOrdinal, width);
159
- encodeNode(writer, value.value, pattern, patternNode.edges[tagOrdinal].target);
160
- return;
161
- }
162
167
 
163
- default:
164
- throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
168
+ default:
169
+ throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
170
+ }
165
171
  }
166
172
  }
167
173
 
168
174
  function decodeNode(reader, pattern, patternNodeId) {
169
- const patternNode = pattern.nodes[patternNodeId];
170
- if (!patternNode) {
171
- throw new Error(`Unknown pattern node ${patternNodeId}`);
172
- }
173
-
174
- switch (patternNode.kind) {
175
- case NODE_KIND.ANY:
176
- throw new Error("Pattern node 'any' is not directly decodable");
175
+ let result;
176
+ const stack = [{
177
+ patternNodeId,
178
+ assign(value) {
179
+ result = value;
180
+ }
181
+ }];
177
182
 
178
- case NODE_KIND.OPEN_PRODUCT:
179
- case NODE_KIND.CLOSED_PRODUCT: {
180
- const product = {};
181
- for (const edge of patternNode.edges) {
182
- product[edge.label] = decodeNode(reader, pattern, edge.target);
183
- }
184
- return new Product(product);
183
+ while (stack.length > 0) {
184
+ const frame = stack.pop();
185
+ const patternNode = pattern.nodes[frame.patternNodeId];
186
+ if (!patternNode) {
187
+ throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
185
188
  }
186
189
 
187
- case NODE_KIND.OPEN_UNION:
188
- case NODE_KIND.CLOSED_UNION: {
189
- if (patternNode.edges.length === 0) {
190
- throw new Error(`Union pattern node ${patternNodeId} has no tags`);
190
+ switch (patternNode.kind) {
191
+ case NODE_KIND.ANY:
192
+ throw new Error("Pattern node 'any' is not directly decodable");
193
+
194
+ case NODE_KIND.OPEN_PRODUCT:
195
+ case NODE_KIND.CLOSED_PRODUCT: {
196
+ const product = {};
197
+ frame.assign(new Product(product));
198
+ for (let i = patternNode.edges.length - 1; i >= 0; i--) {
199
+ const edge = patternNode.edges[i];
200
+ stack.push({
201
+ patternNodeId: edge.target,
202
+ assign(value) {
203
+ product[edge.label] = value;
204
+ }
205
+ });
206
+ }
207
+ break;
191
208
  }
192
- const width = choiceWidth(patternNode.edges.length);
193
- const tagOrdinal = reader.readBits(width);
194
- if (tagOrdinal >= patternNode.edges.length) {
195
- throw new Error(`Choice ${tagOrdinal} is out of range for pattern node ${patternNodeId}`);
209
+
210
+ case NODE_KIND.OPEN_UNION:
211
+ case NODE_KIND.CLOSED_UNION: {
212
+ if (patternNode.edges.length === 0) {
213
+ throw new Error(`Union pattern node ${frame.patternNodeId} has no tags`);
214
+ }
215
+ const width = choiceWidth(patternNode.edges.length);
216
+ const tagOrdinal = reader.readBits(width);
217
+ if (tagOrdinal >= patternNode.edges.length) {
218
+ throw new Error(`Choice ${tagOrdinal} is out of range for pattern node ${frame.patternNodeId}`);
219
+ }
220
+ const edge = patternNode.edges[tagOrdinal];
221
+ const variant = new Variant(edge.label, undefined);
222
+ frame.assign(variant);
223
+ stack.push({
224
+ patternNodeId: edge.target,
225
+ assign(value) {
226
+ variant.value = value;
227
+ }
228
+ });
229
+ break;
196
230
  }
197
- const edge = patternNode.edges[tagOrdinal];
198
- return new Variant(edge.label, decodeNode(reader, pattern, edge.target));
199
- }
200
231
 
201
- default:
202
- throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
232
+ default:
233
+ throw new Error(`Unsupported pattern node kind ${patternNode.kind}`);
234
+ }
203
235
  }
236
+
237
+ return result;
204
238
  }
205
239
 
206
240
  function preparePatternAndValue(value, propertyList) {