@fraczak/k 6.2.1 → 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.
@@ -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
 
@@ -714,47 +736,88 @@ function refinePatternForValue(pattern, value) {
714
736
 
715
737
  function coerceValueForPattern(pattern, value) {
716
738
  const base = normalizePattern(pattern);
717
-
718
- function coerce(patternNodeId, currentValue) {
719
- 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];
720
755
  if (!patternNode) {
721
- throw new Error(`Unknown pattern node ${patternNodeId}`);
756
+ throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
722
757
  }
723
758
 
724
759
  switch (patternNode.kind) {
725
760
  case NODE_KIND.ANY:
726
- return currentValue;
761
+ frame.assign(frame.currentValue);
762
+ break;
727
763
 
728
764
  case NODE_KIND.CLOSED_PRODUCT:
729
765
  case NODE_KIND.OPEN_PRODUCT: {
730
- let productValue = currentValue;
731
- if (currentValue instanceof Variant) {
732
- 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 });
733
769
  }
734
770
  if (!(productValue instanceof Product)) {
735
- throw new Error(`Expected Product for pattern node ${patternNodeId}`);
771
+ throw new Error(`Expected Product for pattern node ${frame.patternNodeId}`);
736
772
  }
737
773
 
738
774
  const explicitTargets = new Map(patternNode.edges.map((edge) => [edge.label, edge.target]));
739
775
  const product = {};
740
- for (const [label, childValue] of Object.entries(productValue.product)) {
741
- product[label] = explicitTargets.has(label)
742
- ? coerce(explicitTargets.get(label), childValue)
743
- : 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
+ }
744
797
  }
745
- return new Product(product);
798
+ break;
746
799
  }
747
800
 
748
801
  case NODE_KIND.CLOSED_UNION:
749
802
  case NODE_KIND.OPEN_UNION: {
750
- if (!(currentValue instanceof Variant)) {
751
- 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
+ });
752
819
  }
753
- const target = patternNode.edges.find((edge) => edge.label === currentValue.tag)?.target;
754
- return new Variant(
755
- currentValue.tag,
756
- target == null ? currentValue.value : coerce(target, currentValue.value)
757
- );
820
+ break;
758
821
  }
759
822
 
760
823
  default:
@@ -762,7 +825,7 @@ function coerceValueForPattern(pattern, value) {
762
825
  }
763
826
  }
764
827
 
765
- return coerce(0, value);
828
+ return result;
766
829
  }
767
830
 
768
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.2.1",
3
+ "version": "6.2.2",
4
4
  "description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
@@ -18,6 +18,7 @@ const tests = [
18
18
  ["tests/test-fingerprint.mjs", node, ["tests/test-fingerprint.mjs"]],
19
19
  ["tests/test-hash-fuzz.mjs", node, ["tests/test-hash-fuzz.mjs"]],
20
20
  ["tests/test-hash-normalization.mjs", node, ["tests/test-hash-normalization.mjs"]],
21
+ ["tests/test-deep-wire-value.mjs", node, ["tests/test-deep-wire-value.mjs"]],
21
22
  ["tests/test-k-object.mjs", node, ["tests/test-k-object.mjs"]],
22
23
  ["tests/test-repl.mjs", node, ["tests/test-repl.mjs"]],
23
24
  ["tests/test-kvm.mjs", node, ["tests/test-kvm.mjs"]],
@@ -0,0 +1,29 @@
1
+ import assert from "node:assert/strict";
2
+ import { Product, Variant } from "../Value.mjs";
3
+ import { decodeWire, encodeToWire } from "../codecs/runtime/prefix-codec.mjs";
4
+
5
+ const depth = 12000;
6
+ const pattern = [
7
+ ["closed-union", [["+", 1]]],
8
+ ["closed-union", [["_", 2], ["1", 1]]],
9
+ ["closed-product", []]
10
+ ];
11
+
12
+ let bits = new Variant("_", new Product({}));
13
+ for (let i = 0; i < depth; i++) {
14
+ bits = new Variant("1", bits);
15
+ }
16
+
17
+ const wire = encodeToWire(new Variant("+", bits), pattern);
18
+ const decoded = decodeWire(wire).value;
19
+ assert.equal(decoded.tag, "+");
20
+
21
+ bits = decoded.value;
22
+ for (let i = 0; i < depth; i++) {
23
+ assert.equal(bits.tag, "1");
24
+ bits = bits.value;
25
+ }
26
+ assert.equal(bits.tag, "_");
27
+ assert.deepEqual(bits.value.product, {});
28
+
29
+ console.log("deep wire value: ok");