@fraczak/k 7.0.2 → 7.2.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 (71) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/DOCS/CODECS.md +37 -2
  3. package/DOCS/CODEC_ARCHITECTURE.md +12 -5
  4. package/DOCS/FILE_FORMATS.md +238 -15
  5. package/DOCS/POLYMORPHIC_IR.md +104 -0
  6. package/DOCS/REPL.md +11 -3
  7. package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
  8. package/JOURNAL.md +288 -0
  9. package/README.md +12 -3
  10. package/Value.mjs +28 -3
  11. package/access-table.mjs +343 -0
  12. package/backend-api.mjs +23 -1
  13. package/backends/llvm/README.md +20 -21
  14. package/backends/llvm/bin/k-llvm-build.mjs +105 -32
  15. package/backends/llvm/bin/k-llvm-compile.mjs +63 -24
  16. package/backends/llvm/bin/k-llvm.mjs +292 -0
  17. package/backends/llvm/runtime/krt.c +8 -0
  18. package/backends/llvm/runtime/krt.h +1 -0
  19. package/backends/llvm/src/cli.mjs +95 -17
  20. package/backends/llvm/src/executable.mjs +128 -11
  21. package/backends/llvm/src/kvm2llvm.mjs +10 -5
  22. package/backends/llvm/src/llvm.mjs +121 -9
  23. package/backends/llvm/tests/integration.sh +11 -11
  24. package/backends/llvm/{bin → tests}/k-llvm-run.mjs +48 -22
  25. package/backends/llvm/tests/perf-support.mjs +24 -3
  26. package/backends/llvm/tests/test-llvm.mjs +22 -1
  27. package/backends/wasm/bin/k-wasm-compile.mjs +46 -20
  28. package/backends/wasm/bin/k-wasm-run.mjs +61 -20
  29. package/backends/wasm/bin/k-wasm.mjs +47 -13
  30. package/backends/wasm/src/cli.mjs +141 -19
  31. package/backends/wasm/src/wasm.mjs +130 -23
  32. package/browser/repl-browser.mjs +16 -5
  33. package/browser/tree-view.mjs +47 -8
  34. package/browser/vfs-data.mjs +6 -5
  35. package/codecs/README.md +1 -0
  36. package/codecs/djson.mjs +270 -0
  37. package/codecs/int.mjs +2 -1
  38. package/codecs/json.mjs +7 -2
  39. package/codecs/k-parse.mjs +36 -9
  40. package/codecs/k-print.mjs +26 -6
  41. package/codecs/runtime/codec-sdk.mjs +24 -6
  42. package/codecs/runtime/pattern-json.mjs +80 -1
  43. package/codecs/runtime/prefix-codec.mjs +629 -67
  44. package/codecs/runtime/show-value.mjs +93 -51
  45. package/codecs/runtime/unicode-string.mjs +29 -6
  46. package/codecs/show.mjs +24 -6
  47. package/codecs/utf16.mjs +2 -1
  48. package/k.mjs +56 -16
  49. package/kir.mjs +58 -19
  50. package/kvm.mjs +49 -12
  51. package/objects/compile.mjs +37 -23
  52. package/objects/decompile.mjs +40 -10
  53. package/objects/extract-aliases.mjs +44 -14
  54. package/objects/inspect.mjs +19 -14
  55. package/objects/validate.mjs +18 -11
  56. package/package.json +11 -3
  57. package/pkir.mjs +772 -0
  58. package/repl.mjs +92 -23
  59. package/scripts/build-repl-html.mjs +22 -3
  60. package/scripts/perf-ieee.mjs +264 -13
  61. package/scripts/perf-int.mjs +315 -22
  62. package/scripts/perf-multiplication.mjs +58 -7
  63. package/scripts/perf-poly.mjs +306 -24
  64. package/scripts/run-tests.mjs +3 -0
  65. package/tests/test-access-table.mjs +213 -0
  66. package/tests/test-codecs.mjs +168 -0
  67. package/tests/test-pkir.mjs +530 -0
  68. package/tests/test-polymorphic-tdd.mjs +196 -0
  69. package/tests/test-repl-html.mjs +16 -2
  70. package/tests/test-repl.mjs +63 -1
  71. package/backends/llvm/bin/k-llvm-jit.mjs +0 -160
@@ -1,7 +1,20 @@
1
1
  import { Value, withPattern, isProduct, isVariant } from "../../Value.mjs";
2
- import { refinePatternForValue, coerceValueForPattern, NODE_KIND } from "./codec.mjs";
3
- import { patternToPropertyList, propertyListToPattern } from "./pattern-json.mjs";
4
- import { textToStringValue, stringValueToText } from "./unicode-string.mjs";
2
+ import {
3
+ refinePatternForValue,
4
+ coerceValueForPattern,
5
+ canonicalizePattern,
6
+ NODE_KIND
7
+ } from "./codec.mjs";
8
+ import {
9
+ patternToPropertyList,
10
+ propertyListToPattern
11
+ } from "./pattern-json.mjs";
12
+ import {
13
+ textToStringValue,
14
+ stringValueToText,
15
+ unicodeValueToCodePoint,
16
+ setLazyStringDecoder
17
+ } from "./unicode-string.mjs";
5
18
 
6
19
  const CORE_PATTERN_PROPERTY_LIST = [
7
20
  ["closed-union", [["cons", 1], ["nil", 3]]],
@@ -34,19 +47,37 @@ const CORE_PATTERN_PROPERTY_LIST = [
34
47
 
35
48
  const CORE_PATTERN_PATTERN = propertyListToPattern(CORE_PATTERN_PROPERTY_LIST);
36
49
  const UNIT = Value.product({});
50
+ const BIT0 = Value.variant("0", UNIT);
51
+ const BIT1 = Value.variant("1", UNIT);
37
52
 
38
53
  class BitWriter {
39
- constructor() {
40
- this.bytes = [];
54
+ constructor(initialCapacity = 4096) {
55
+ this.buffer = new Uint8Array(initialCapacity);
56
+ this.byteLength = 0;
41
57
  this.current = 0;
42
58
  this.bitCount = 0;
43
59
  }
44
60
 
61
+ ensureCapacity(extraBytes) {
62
+ if (this.byteLength + extraBytes > this.buffer.length) {
63
+ const newCap = Math.max(
64
+ this.buffer.length * 2,
65
+ this.byteLength + extraBytes + 1024
66
+ );
67
+ const newBuf = new Uint8Array(newCap);
68
+ newBuf.set(this.buffer.subarray(0, this.byteLength));
69
+ this.buffer = newBuf;
70
+ }
71
+ }
72
+
45
73
  writeBit(bit) {
46
74
  this.current = (this.current << 1) | (bit ? 1 : 0);
47
75
  this.bitCount += 1;
48
76
  if (this.bitCount === 8) {
49
- this.bytes.push(this.current);
77
+ if (this.byteLength >= this.buffer.length) {
78
+ this.ensureCapacity(1);
79
+ }
80
+ this.buffer[this.byteLength++] = this.current;
50
81
  this.current = 0;
51
82
  this.bitCount = 0;
52
83
  }
@@ -67,9 +98,19 @@ class BitWriter {
67
98
 
68
99
  toBuffer() {
69
100
  if (this.bitCount > 0) {
70
- this.bytes.push(this.current << (8 - this.bitCount));
101
+ if (this.byteLength >= this.buffer.length) {
102
+ this.ensureCapacity(1);
103
+ }
104
+ this.buffer[this.byteLength++] =
105
+ this.current << (8 - this.bitCount);
106
+ this.current = 0;
107
+ this.bitCount = 0;
71
108
  }
72
- return Buffer.from(this.bytes);
109
+ return Buffer.from(
110
+ this.buffer.buffer,
111
+ this.buffer.byteOffset,
112
+ this.byteLength
113
+ );
73
114
  }
74
115
  }
75
116
 
@@ -95,6 +136,8 @@ class BitReader {
95
136
  }
96
137
 
97
138
  readBits(width) {
139
+ if (width === 0) return 0;
140
+ if (width === 1) return this.readBit();
98
141
  let value = 0;
99
142
  for (let i = 0; i < width; i++) {
100
143
  value = (value << 1) | this.readBit();
@@ -136,16 +179,32 @@ function encodeNode(writer, value, pattern, patternNodeId) {
136
179
  case NODE_KIND.OPEN_PRODUCT:
137
180
  case NODE_KIND.CLOSED_PRODUCT: {
138
181
  if (!isProduct(frame.value)) {
139
- throw new Error(`Expected Product for pattern node ${frame.patternNodeId}`);
182
+ throw new Error(
183
+ `Expected Product for pattern node ${frame.patternNodeId}`
184
+ );
140
185
  }
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}`);
186
+ const p = frame.value.product;
187
+ const edges = patternNode.edges;
188
+ const edgeCount = edges.length;
189
+ if (edgeCount === 0) {
190
+ for (const _ in p) {
191
+ throw new Error(
192
+ `Product fields do not match pattern node ` +
193
+ `${frame.patternNodeId}`
194
+ );
195
+ }
196
+ break;
145
197
  }
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 });
198
+ for (let i = edgeCount - 1; i >= 0; i--) {
199
+ const edge = edges[i];
200
+ const childVal = p[edge.label];
201
+ if (childVal === undefined) {
202
+ throw new Error(
203
+ `Product fields do not match pattern node ` +
204
+ `${frame.patternNodeId}`
205
+ );
206
+ }
207
+ stack.push({ value: childVal, patternNodeId: edge.target });
149
208
  }
150
209
  break;
151
210
  }
@@ -153,15 +212,34 @@ function encodeNode(writer, value, pattern, patternNodeId) {
153
212
  case NODE_KIND.OPEN_UNION:
154
213
  case NODE_KIND.CLOSED_UNION: {
155
214
  if (!isVariant(frame.value)) {
156
- throw new Error(`Expected Variant for pattern node ${frame.patternNodeId}`);
215
+ throw new Error(
216
+ `Expected Variant for pattern node ${frame.patternNodeId}`
217
+ );
157
218
  }
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}`);
219
+ const tag = frame.value.tag;
220
+ const edges = patternNode.edges;
221
+ let tagOrdinal = patternNode.tagIndex !== undefined
222
+ ? patternNode.tagIndex[tag]
223
+ : undefined;
224
+ if (tagOrdinal === undefined) {
225
+ tagOrdinal = edges.findIndex((edge) => edge.label === tag);
226
+ if (tagOrdinal === -1) {
227
+ throw new Error(
228
+ `Variant tag '${tag}' is not present in pattern node ` +
229
+ `${frame.patternNodeId}`
230
+ );
231
+ }
161
232
  }
162
- const width = choiceWidth(patternNode.edges.length);
163
- writer.writeBits(tagOrdinal, width);
164
- stack.push({ value: frame.value.value, patternNodeId: patternNode.edges[tagOrdinal].target });
233
+ const width = patternNode.choiceBits !== undefined
234
+ ? patternNode.choiceBits
235
+ : choiceWidth(edges.length);
236
+ if (width > 0) {
237
+ writer.writeBits(tagOrdinal, width);
238
+ }
239
+ stack.push({
240
+ value: frame.value.value,
241
+ patternNodeId: edges[tagOrdinal].target
242
+ });
165
243
  break;
166
244
  }
167
245
 
@@ -171,20 +249,15 @@ function encodeNode(writer, value, pattern, patternNodeId) {
171
249
  }
172
250
  }
173
251
 
174
- function decodeNode(reader, pattern, patternNodeId) {
252
+ function decodeNode(reader, pattern, rootPatternNodeId) {
175
253
  let result;
176
- const stack = [{
177
- patternNodeId,
178
- assign(value) {
179
- result = value;
180
- }
181
- }];
254
+ const stack = [[rootPatternNodeId, null, null]];
182
255
 
183
256
  while (stack.length > 0) {
184
- const frame = stack.pop();
185
- const patternNode = pattern.nodes[frame.patternNodeId];
257
+ const [patternNodeId, parent, key] = stack.pop();
258
+ const patternNode = pattern.nodes[patternNodeId];
186
259
  if (!patternNode) {
187
- throw new Error(`Unknown pattern node ${frame.patternNodeId}`);
260
+ throw new Error(`Unknown pattern node ${patternNodeId}`);
188
261
  }
189
262
 
190
263
  switch (patternNode.kind) {
@@ -194,38 +267,46 @@ function decodeNode(reader, pattern, patternNodeId) {
194
267
  case NODE_KIND.OPEN_PRODUCT:
195
268
  case NODE_KIND.CLOSED_PRODUCT: {
196
269
  const product = {};
197
- frame.assign(Value.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
- });
270
+ const val = Value.product(product);
271
+ if (parent) {
272
+ parent[key] = val;
273
+ } else {
274
+ result = val;
275
+ }
276
+ const edges = patternNode.edges;
277
+ for (let i = edges.length - 1; i >= 0; i--) {
278
+ const edge = edges[i];
279
+ stack.push([edge.target, product, edge.label]);
206
280
  }
207
281
  break;
208
282
  }
209
283
 
210
284
  case NODE_KIND.OPEN_UNION:
211
285
  case NODE_KIND.CLOSED_UNION: {
212
- if (patternNode.edges.length === 0) {
213
- throw new Error(`Union pattern node ${frame.patternNodeId} has no tags`);
286
+ const edges = patternNode.edges;
287
+ if (edges.length === 0) {
288
+ throw new Error(
289
+ `Union pattern node ${patternNodeId} has no tags`
290
+ );
214
291
  }
215
- const width = choiceWidth(patternNode.edges.length);
292
+ const width = patternNode.choiceBits !== undefined
293
+ ? patternNode.choiceBits
294
+ : choiceWidth(edges.length);
216
295
  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}`);
296
+ if (tagOrdinal >= edges.length) {
297
+ throw new Error(
298
+ `Choice ${tagOrdinal} is out of range for pattern node ` +
299
+ `${patternNodeId}`
300
+ );
219
301
  }
220
- const edge = patternNode.edges[tagOrdinal];
302
+ const edge = edges[tagOrdinal];
221
303
  const variant = Value.variant(edge.label, undefined);
222
- frame.assign(variant);
223
- stack.push({
224
- patternNodeId: edge.target,
225
- assign(value) {
226
- variant.value = value;
227
- }
228
- });
304
+ if (parent) {
305
+ parent[key] = variant;
306
+ } else {
307
+ result = variant;
308
+ }
309
+ stack.push([edge.target, variant, "value"]);
229
310
  break;
230
311
  }
231
312
 
@@ -237,23 +318,57 @@ function decodeNode(reader, pattern, patternNodeId) {
237
318
  return result;
238
319
  }
239
320
 
321
+ function isClosedPropertyList(propertyList) {
322
+ if (!Array.isArray(propertyList) || propertyList.length === 0) {
323
+ return false;
324
+ }
325
+ for (let i = 0; i < propertyList.length; i++) {
326
+ const kind = propertyList[i]?.[0];
327
+ if (kind !== "closed-product" && kind !== "closed-union") {
328
+ return false;
329
+ }
330
+ }
331
+ return true;
332
+ }
333
+
240
334
  function preparePatternAndValue(value, propertyList) {
241
335
  const valuePattern = propertyList || value.pattern;
242
- const basePattern = valuePattern ? propertyListToPattern(valuePattern) : deriveClosedPatternFromValue(value);
243
- const coercedValue = valuePattern ? coerceValueForPattern(basePattern, value) : value;
336
+ if (valuePattern && isClosedPropertyList(valuePattern)) {
337
+ const basePattern = propertyListToPattern(valuePattern);
338
+ const canonical = canonicalizePattern(basePattern);
339
+ return {
340
+ coercedValue: value,
341
+ refinedPattern: canonical,
342
+ propertyList: patternToPropertyList(canonical)
343
+ };
344
+ }
345
+ const basePattern = valuePattern
346
+ ? propertyListToPattern(valuePattern)
347
+ : deriveClosedPatternFromValue(value);
348
+ const coercedValue = valuePattern
349
+ ? coerceValueForPattern(basePattern, value)
350
+ : value;
244
351
  const refinedPattern = refinePatternForValue(basePattern, coercedValue);
245
- return { coercedValue, refinedPattern, propertyList: patternToPropertyList(refinedPattern) };
352
+ return {
353
+ coercedValue,
354
+ refinedPattern,
355
+ propertyList: patternToPropertyList(refinedPattern)
356
+ };
246
357
  }
247
358
 
248
359
  function deriveClosedPatternFromValue(value) {
249
360
  const nodes = [];
361
+ const labelSet = new Set();
250
362
 
251
363
  function visit(node) {
252
364
  const nodeId = nodes.length;
253
365
  if (isProduct(node)) {
254
366
  const placeholder = { kind: NODE_KIND.CLOSED_PRODUCT, edges: [] };
255
367
  nodes.push(placeholder);
256
- const labels = Object.keys(node.product).sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
368
+ const labels = Object.keys(node.product).sort((a, b) =>
369
+ Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
370
+ );
371
+ for (let i = 0; i < labels.length; i++) labelSet.add(labels[i]);
257
372
  placeholder.edges = labels.map((label) => ({
258
373
  label,
259
374
  symbolId: 0,
@@ -264,6 +379,7 @@ function deriveClosedPatternFromValue(value) {
264
379
  if (isVariant(node)) {
265
380
  const placeholder = { kind: NODE_KIND.OPEN_UNION, edges: [] };
266
381
  nodes.push(placeholder);
382
+ labelSet.add(node.tag);
267
383
  placeholder.edges = [{
268
384
  label: node.tag,
269
385
  symbolId: 0,
@@ -271,12 +387,17 @@ function deriveClosedPatternFromValue(value) {
271
387
  }];
272
388
  return nodeId;
273
389
  }
274
- throw new Error(`Unsupported runtime value node: ${node?.constructor?.name || typeof node}`);
390
+ throw new Error(
391
+ `Unsupported runtime value node: ${
392
+ node?.constructor?.name || typeof node
393
+ }`
394
+ );
275
395
  }
276
396
 
277
397
  visit(value);
278
- const dictionary = [...new Set(nodes.flatMap((node) => node.edges.map((edge) => edge.label)))]
279
- .sort((a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8")));
398
+ const dictionary = Array.from(labelSet).sort((a, b) =>
399
+ Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"))
400
+ );
280
401
  const symbolIds = new Map(dictionary.map((label, index) => [label, index]));
281
402
 
282
403
  return {
@@ -429,21 +550,458 @@ function encodeToWire(value, propertyList) {
429
550
  return writer.toBuffer();
430
551
  }
431
552
 
432
- function decodeWire(buffer) {
553
+ function readBitsAt(buffer, bitOffset, width) {
554
+ if (width === 0) return 0;
555
+ if ((bitOffset + width - 1) >> 3 >= buffer.length) {
556
+ throw new Error("Unexpected end of bitstream");
557
+ }
558
+ if (width === 1) {
559
+ return (buffer[bitOffset >> 3] >> (7 - (bitOffset & 7))) & 1;
560
+ }
561
+ let v = 0;
562
+ for (let i = 0; i < width; i++) {
563
+ const pos = bitOffset + i;
564
+ v = (v << 1) | ((buffer[pos >> 3] >> (7 - (pos & 7))) & 1);
565
+ }
566
+ return v;
567
+ }
568
+
569
+ const SKIP_STACK = new Int32Array(2048);
570
+
571
+ function fastSkipSubtree(
572
+ buffer,
573
+ startBitOffset,
574
+ pattern,
575
+ rootPatternNodeId
576
+ ) {
577
+ let bitOffset = startBitOffset;
578
+ let stack = SKIP_STACK;
579
+ stack[0] = rootPatternNodeId;
580
+ let stackPtr = 1;
581
+ const nodes = pattern.nodes;
582
+
583
+ while (stackPtr > 0) {
584
+ const nodeId = stack[--stackPtr];
585
+ const node = nodes[nodeId];
586
+ if (node.fixedBitWidth !== undefined) {
587
+ bitOffset += node.fixedBitWidth;
588
+ continue;
589
+ }
590
+ if (
591
+ node.kind === NODE_KIND.CLOSED_PRODUCT ||
592
+ node.kind === NODE_KIND.OPEN_PRODUCT
593
+ ) {
594
+ const edges = node.edges;
595
+ const count = edges.length;
596
+ if (stackPtr + count > stack.length) {
597
+ const newStack = new Int32Array(stack.length * 2);
598
+ newStack.set(stack);
599
+ stack = newStack;
600
+ }
601
+ for (let i = count - 1; i >= 0; i--) {
602
+ stack[stackPtr++] = edges[i].target;
603
+ }
604
+ } else if (
605
+ node.kind === NODE_KIND.CLOSED_UNION ||
606
+ node.kind === NODE_KIND.OPEN_UNION
607
+ ) {
608
+ const edges = node.edges;
609
+ const width = node.choiceBits !== undefined
610
+ ? node.choiceBits
611
+ : choiceWidth(edges.length);
612
+ const tagOrdinal = width > 0
613
+ ? readBitsAt(buffer, bitOffset, width)
614
+ : 0;
615
+ bitOffset += width;
616
+ if (tagOrdinal >= edges.length) {
617
+ throw new Error(
618
+ `Choice ${tagOrdinal} is out of range for pattern ` +
619
+ `node ${nodeId}`
620
+ );
621
+ }
622
+ if (stackPtr >= stack.length) {
623
+ const newStack = new Int32Array(stack.length * 2);
624
+ newStack.set(stack);
625
+ stack = newStack;
626
+ }
627
+ stack[stackPtr++] = edges[tagOrdinal].target;
628
+ }
629
+ }
630
+ return bitOffset;
631
+ }
632
+
633
+ class LazyVariant extends Value {
634
+ constructor(tag, buffer, bitOffset, pattern, targetId) {
635
+ super();
636
+ this.type = "<>";
637
+ this.tag = tag;
638
+ this.pattern = null;
639
+ this._buffer = buffer;
640
+ this._bitOffset = bitOffset;
641
+ this._pattern = pattern;
642
+ this._targetId = targetId;
643
+ Object.defineProperty(this, "value", {
644
+ get: this._resolveValue,
645
+ set: this._setValue,
646
+ enumerable: true,
647
+ configurable: true
648
+ });
649
+ }
650
+
651
+ _resolveValue() {
652
+ const val = decodeLazyNode(
653
+ this._buffer,
654
+ this._bitOffset,
655
+ this._pattern,
656
+ this._targetId
657
+ );
658
+ Object.defineProperty(this, "value", {
659
+ value: val,
660
+ writable: true,
661
+ enumerable: true,
662
+ configurable: true
663
+ });
664
+ return val;
665
+ }
666
+
667
+ _setValue(v) {
668
+ this._buffer = null;
669
+ Object.defineProperty(this, "value", {
670
+ value: v,
671
+ writable: true,
672
+ enumerable: true,
673
+ configurable: true
674
+ });
675
+ }
676
+ }
677
+
678
+ function createLazyVariant(buffer, bitOffset, pattern, patternNodeId) {
679
+ const node = pattern.nodes[patternNodeId];
680
+ const edges = node.edges;
681
+ if (edges.length === 0) {
682
+ throw new Error(`Union pattern node ${patternNodeId} has no tags`);
683
+ }
684
+ const width = node.choiceBits !== undefined
685
+ ? node.choiceBits
686
+ : choiceWidth(edges.length);
687
+ const tagOrdinal = width > 0 ? readBitsAt(buffer, bitOffset, width) : 0;
688
+ if (tagOrdinal >= edges.length) {
689
+ throw new Error(
690
+ `Choice ${tagOrdinal} is out of range for pattern ` +
691
+ `node ${patternNodeId}`
692
+ );
693
+ }
694
+ const edge = edges[tagOrdinal];
695
+
696
+ // Fast-path: terminal variant whose target is empty product {}
697
+ const targetNode = pattern.nodes[edge.target];
698
+ if (
699
+ targetNode &&
700
+ (targetNode.kind === NODE_KIND.CLOSED_PRODUCT ||
701
+ targetNode.kind === NODE_KIND.OPEN_PRODUCT) &&
702
+ targetNode.edges.length === 0
703
+ ) {
704
+ if (edge.label === "0") return BIT0;
705
+ if (edge.label === "1") return BIT1;
706
+ return Value.variant(edge.label, UNIT);
707
+ }
708
+
709
+ const variant = new LazyVariant(
710
+ edge.label,
711
+ buffer,
712
+ bitOffset + width,
713
+ pattern,
714
+ edge.target
715
+ );
716
+ if (edge.fixedTargetBitWidth !== undefined) {
717
+ variant.bitWidth = width + edge.fixedTargetBitWidth;
718
+ }
719
+ return variant;
720
+ }
721
+
722
+ class LazyConsProduct extends Value {
723
+ constructor(buffer, bitOffset, pattern, carTarget, cdrTarget) {
724
+ super();
725
+ this.type = "{}";
726
+ this.pattern = null;
727
+ this.buffer = buffer;
728
+ this.bitOffset = bitOffset;
729
+ this.pattern = pattern;
730
+ this.carTarget = carTarget;
731
+ this.cdrTarget = cdrTarget;
732
+ let carVal = undefined;
733
+ let cdrVal = undefined;
734
+ let cdrOffset = -1;
735
+
736
+ this.product = {
737
+ get car() {
738
+ if (carVal === undefined) {
739
+ carVal = decodeLazyNode(buffer, bitOffset, pattern, carTarget);
740
+ }
741
+ return carVal;
742
+ },
743
+ set car(v) {
744
+ carVal = v;
745
+ },
746
+ get cdr() {
747
+ if (cdrVal === undefined) {
748
+ if (cdrOffset === -1) {
749
+ if (carVal?.bitWidth !== undefined) {
750
+ cdrOffset = bitOffset + carVal.bitWidth;
751
+ } else if (pattern.nodes[carTarget].fixedBitWidth !== undefined) {
752
+ cdrOffset = bitOffset + pattern.nodes[carTarget].fixedBitWidth;
753
+ } else {
754
+ cdrOffset = fastSkipSubtree(
755
+ buffer,
756
+ bitOffset,
757
+ pattern,
758
+ carTarget
759
+ );
760
+ }
761
+ }
762
+ cdrVal = decodeLazyNode(buffer, cdrOffset, pattern, cdrTarget);
763
+ }
764
+ return cdrVal;
765
+ },
766
+ set cdr(v) {
767
+ cdrVal = v;
768
+ }
769
+ };
770
+ }
771
+ }
772
+
773
+ class LazyGeneralProduct extends Value {
774
+ constructor(buffer, bitOffset, pattern, patternNodeId) {
775
+ super();
776
+ this.type = "{}";
777
+ this.pattern = null;
778
+ const node = pattern.nodes[patternNodeId];
779
+ const edges = node.edges;
780
+ const edgeCount = edges.length;
781
+ const product = {};
782
+ this.product = product;
783
+
784
+ if (node.staticOffsets !== undefined) {
785
+ const staticOffsets = node.staticOffsets;
786
+ for (let i = 0; i < edgeCount; i++) {
787
+ const edge = edges[i];
788
+ const off = bitOffset + staticOffsets[i];
789
+ let cachedVal = undefined;
790
+ let resolved = false;
791
+
792
+ Object.defineProperty(product, edge.label, {
793
+ get() {
794
+ if (!resolved) {
795
+ cachedVal = decodeLazyNode(buffer, off, pattern, edge.target);
796
+ resolved = true;
797
+ }
798
+ return cachedVal;
799
+ },
800
+ set(v) {
801
+ cachedVal = v;
802
+ resolved = true;
803
+ },
804
+ enumerable: true,
805
+ configurable: true
806
+ });
807
+ }
808
+ return;
809
+ }
810
+
811
+ const edgeOffsets = new Array(edgeCount);
812
+ edgeOffsets[0] = bitOffset;
813
+ let nextKnownOffsetIndex = 1;
814
+
815
+ function ensureOffset(targetIndex) {
816
+ while (nextKnownOffsetIndex <= targetIndex) {
817
+ const prevIdx = nextKnownOffsetIndex - 1;
818
+ const prevStart = edgeOffsets[prevIdx];
819
+ const prevTargetId = edges[prevIdx].target;
820
+ edgeOffsets[nextKnownOffsetIndex] = fastSkipSubtree(
821
+ buffer,
822
+ prevStart,
823
+ pattern,
824
+ prevTargetId
825
+ );
826
+ nextKnownOffsetIndex++;
827
+ }
828
+ return edgeOffsets[targetIndex];
829
+ }
830
+
831
+ for (let i = 0; i < edgeCount; i++) {
832
+ const edge = edges[i];
833
+ const edgeIndex = i;
834
+ let cachedVal = undefined;
835
+ let resolved = false;
836
+
837
+ Object.defineProperty(product, edge.label, {
838
+ get() {
839
+ if (!resolved) {
840
+ const start = ensureOffset(edgeIndex);
841
+ cachedVal = decodeLazyNode(
842
+ buffer,
843
+ start,
844
+ pattern,
845
+ edge.target
846
+ );
847
+ resolved = true;
848
+ }
849
+ return cachedVal;
850
+ },
851
+ set(v) {
852
+ cachedVal = v;
853
+ resolved = true;
854
+ },
855
+ enumerable: true,
856
+ configurable: true
857
+ });
858
+ }
859
+ }
860
+ }
861
+
862
+ function decodeLazyNode(buffer, bitOffset, pattern, patternNodeId) {
863
+ const node = pattern.nodes[patternNodeId];
864
+ if (!node) {
865
+ throw new Error(`Unknown pattern node ${patternNodeId}`);
866
+ }
867
+ if (
868
+ node.kind === NODE_KIND.CLOSED_PRODUCT ||
869
+ node.kind === NODE_KIND.OPEN_PRODUCT
870
+ ) {
871
+ if (node.edges.length === 0) return UNIT;
872
+ if (
873
+ node.edges.length === 2 &&
874
+ node.edges[0].label === "car" &&
875
+ node.edges[1].label === "cdr"
876
+ ) {
877
+ return new LazyConsProduct(
878
+ buffer,
879
+ bitOffset,
880
+ pattern,
881
+ node.edges[0].target,
882
+ node.edges[1].target
883
+ );
884
+ }
885
+ return new LazyGeneralProduct(
886
+ buffer,
887
+ bitOffset,
888
+ pattern,
889
+ patternNodeId
890
+ );
891
+ }
892
+ return createLazyVariant(buffer, bitOffset, pattern, patternNodeId);
893
+ }
894
+
895
+ function decodeWire(buffer, options = {}) {
433
896
  if (!Buffer.isBuffer(buffer)) {
434
897
  throw new Error("Wire input must be a Buffer");
435
898
  }
436
899
  const reader = new BitReader(buffer);
437
- const patternPropertyList = patternFromKValue(decodeNode(reader, CORE_PATTERN_PATTERN, 0));
900
+ const patternPropertyList = patternFromKValue(
901
+ decodeNode(reader, CORE_PATTERN_PATTERN, 0)
902
+ );
438
903
  const pattern = propertyListToPattern(patternPropertyList);
439
- const value = decodeNode(reader, pattern, 0);
440
- reader.assertZeroPadding();
441
- return { pattern: patternPropertyList, value: withPattern(value, patternPropertyList) };
904
+
905
+ if (options.eager) {
906
+ const value = decodeNode(reader, pattern, 0);
907
+ reader.assertZeroPadding();
908
+ return {
909
+ pattern: patternPropertyList,
910
+ value: withPattern(value, patternPropertyList)
911
+ };
912
+ }
913
+
914
+ const valBitOffset = reader.byteOffset * 8 + reader.bitOffset;
915
+ const lazyRoot = decodeLazyNode(buffer, valBitOffset, pattern, 0);
916
+ lazyRoot.pattern = patternPropertyList;
917
+ return {
918
+ pattern: patternPropertyList,
919
+ value: withPattern(lazyRoot, patternPropertyList)
920
+ };
921
+ }
922
+
923
+ function decodeWireLazy(buffer) {
924
+ return decodeWire(buffer, { eager: false });
442
925
  }
443
926
 
927
+ function isLazyCanonicalString(val) {
928
+ if (!val || typeof val !== "object" || !val._buffer || !val._pattern) {
929
+ return false;
930
+ }
931
+ const nodes = val._pattern.nodes;
932
+ if (!nodes || nodes.length < 4) return false;
933
+ const n0 = nodes[0];
934
+ const n1 = nodes[1];
935
+ const n2 = nodes[2];
936
+ const n3 = nodes[3];
937
+ return (
938
+ n0?.edges?.length === 2 &&
939
+ n0.edges[0]?.label === "cons" &&
940
+ n0.edges[1]?.label === "nil" &&
941
+ n1?.edges?.length === 2 &&
942
+ n1.edges[0]?.label === "car" &&
943
+ n1.edges[1]?.label === "cdr" &&
944
+ n2?.edges?.[0]?.label === "ascii" &&
945
+ n3?.edges?.length === 7
946
+ );
947
+ }
948
+
949
+ function fastDecodeLazyString(lazyVariant) {
950
+ if (!isLazyCanonicalString(lazyVariant)) return null;
951
+ if (lazyVariant.tag === "nil") return "";
952
+ if (lazyVariant.tag !== "cons") return null;
953
+
954
+ const buffer = lazyVariant._buffer;
955
+ const pattern = lazyVariant._pattern;
956
+ let bitOffset = lazyVariant._bitOffset;
957
+
958
+ const chunks = [];
959
+ let chunk = "";
960
+
961
+ while (true) {
962
+ const carTag = readBitsAt(buffer, bitOffset, 3);
963
+ if (carTag === 0) {
964
+ let cp = 0;
965
+ for (let i = 0; i < 7; i++) {
966
+ cp |= (readBitsAt(buffer, bitOffset + 3 + i, 1) << i);
967
+ }
968
+ chunk += String.fromCharCode(cp);
969
+ if (chunk.length >= 8192) {
970
+ chunks.push(chunk);
971
+ chunk = "";
972
+ }
973
+ bitOffset += 10;
974
+ } else {
975
+ const charVal = decodeLazyNode(buffer, bitOffset, pattern, 2);
976
+ const cp = unicodeValueToCodePoint(charVal);
977
+ chunk += String.fromCodePoint(cp);
978
+ if (chunk.length >= 8192) {
979
+ chunks.push(chunk);
980
+ chunk = "";
981
+ }
982
+ bitOffset = fastSkipSubtree(buffer, bitOffset, pattern, 2);
983
+ }
984
+
985
+ const listTag = readBitsAt(buffer, bitOffset, 1);
986
+ bitOffset += 1;
987
+ if (listTag !== 0) {
988
+ break;
989
+ }
990
+ }
991
+
992
+ if (chunk.length > 0) chunks.push(chunk);
993
+ return chunks.join("");
994
+ }
995
+
996
+ setLazyStringDecoder(fastDecodeLazyString);
997
+
444
998
  export {
445
999
  encodeToWire,
446
1000
  decodeWire,
1001
+ decodeWireLazy,
1002
+ decodeLazyNode,
1003
+ fastSkipSubtree,
1004
+ readBitsAt,
447
1005
  propertyListToPattern,
448
1006
  patternToPropertyList,
449
1007
  choiceWidth,
@@ -452,6 +1010,10 @@ export {
452
1010
  export default {
453
1011
  encodeToWire,
454
1012
  decodeWire,
1013
+ decodeWireLazy,
1014
+ decodeLazyNode,
1015
+ fastSkipSubtree,
1016
+ readBitsAt,
455
1017
  propertyListToPattern,
456
1018
  patternToPropertyList,
457
1019
  choiceWidth,