Fork of daniellemaywood.uk/gleam — Wasm codegen work
40 kB
1579 lines
1export class CustomType {
2 withFields(fields) {
3 let properties = Object.keys(this).map((label) =>
4 label in fields ? fields[label] : this[label],
5 );
6 return new this.constructor(...properties);
7 }
8}
9
10export class List {
11 static fromArray(array, tail) {
12 return toList(array, tail)
13 }
14
15 [Symbol.iterator]() {
16 return new ListIterator(this);
17 }
18
19 toArray() {
20 return [...this];
21 }
22
23 atLeastLength(desired) {
24 let current = this;
25 while (desired-- > 0 && current) current = current.tail;
26 return current !== undefined;
27 }
28
29 hasLength(desired) {
30 let current = this;
31 while (desired-- > 0 && current) current = current.tail;
32 return desired === -1 && current instanceof Empty;
33 }
34
35 countLength() {
36 let current = this;
37 let length = 0;
38 while (current) {
39 current = current.tail;
40 length++;
41 }
42 return length - 1;
43 }
44}
45
46export function prepend(element, tail) {
47 return new NonEmpty(element, tail);
48}
49
50export function toList(elements, tail) {
51 let t = tail || List$Empty$const
52 for (let i = elements.length - 1; i >= 0; --i) {
53 t = new NonEmpty(elements[i], t);
54 }
55 return t;
56}
57
58class ListIterator {
59 #current;
60
61 constructor(current) {
62 this.#current = current;
63 }
64
65 next() {
66 if (this.#current instanceof Empty) {
67 return { done: true };
68 } else {
69 let { head, tail } = this.#current;
70 this.#current = tail;
71 return { value: head, done: false };
72 }
73 }
74}
75
76export class Empty extends List { }
77export const List$Empty$const = new Empty();
78export const List$Empty = () => List$Empty$const;
79export const List$isEmpty = (value) => value instanceof Empty;
80
81export class NonEmpty extends List {
82 constructor(head, tail) {
83 super();
84 this.head = head;
85 this.tail = tail;
86 }
87}
88export const List$NonEmpty = (head, tail) => new NonEmpty(head, tail);
89export const List$isNonEmpty = (value) => value instanceof NonEmpty;
90
91export const List$NonEmpty$first = (value) => value.head;
92export const List$NonEmpty$rest = (value) => value.tail;
93
94/**
95 * A bit array is a contiguous sequence of bits similar to Erlang's Binary type.
96 */
97export class BitArray {
98 /**
99 * The size in bits of this bit array's data.
100 *
101 * @type {number}
102 */
103 bitSize;
104
105 /**
106 * The size in bytes of this bit array's data. If this bit array doesn't store
107 * a whole number of bytes then this value is rounded up.
108 *
109 * @type {number}
110 */
111 byteSize;
112
113 /**
114 * The number of unused high bits in the first byte of this bit array's
115 * buffer prior to the start of its data. The value of any unused high bits is
116 * undefined.
117 *
118 * The bit offset will be in the range 0-7.
119 *
120 * @type {number}
121 */
122 bitOffset;
123
124 /**
125 * The raw bytes that hold this bit array's data.
126 *
127 * If `bitOffset` is not zero then there are unused high bits in the first
128 * byte of this buffer.
129 *
130 * If `bitOffset + bitSize` is not a multiple of 8 then there are unused low
131 * bits in the last byte of this buffer.
132 *
133 * @type {Uint8Array}
134 */
135 rawBuffer;
136
137 /**
138 * Constructs a new bit array from a `Uint8Array`, an optional size in
139 * bits, and an optional bit offset.
140 *
141 * If no bit size is specified it is taken as `buffer.length * 8`, i.e. all
142 * bytes in the buffer make up the new bit array's data.
143 *
144 * If no bit offset is specified it defaults to zero, i.e. there are no unused
145 * high bits in the first byte of the buffer.
146 *
147 * @param {Uint8Array} buffer
148 * @param {number} [bitSize]
149 * @param {number} [bitOffset]
150 */
151 constructor(buffer, bitSize, bitOffset) {
152 if (!(buffer instanceof Uint8Array)) {
153 throw globalThis.Error(
154 "BitArray can only be constructed from a Uint8Array",
155 );
156 }
157
158 this.bitSize = bitSize ?? buffer.length * 8;
159 this.byteSize = Math.trunc((this.bitSize + 7) / 8);
160 this.bitOffset = bitOffset ?? 0;
161
162 // Validate the bit size
163 if (this.bitSize < 0) {
164 throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`);
165 }
166
167 // Validate the bit offset
168 if (this.bitOffset < 0 || this.bitOffset > 7) {
169 throw globalThis.Error(
170 `BitArray bit offset is invalid: ${this.bitOffset}`,
171 );
172 }
173
174 // Validate the length of the buffer
175 if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) {
176 throw globalThis.Error("BitArray buffer length is invalid");
177 }
178
179 this.rawBuffer = buffer;
180 }
181
182 /**
183 * Returns a specific byte in this bit array. If the byte index is out of
184 * range then `undefined` is returned.
185 *
186 * When returning the final byte of a bit array with a bit size that's not a
187 * multiple of 8, the content of the unused low bits are undefined.
188 *
189 * @param {number} index
190 * @returns {number | undefined}
191 */
192 byteAt(index) {
193 if (index < 0 || index >= this.byteSize) {
194 return undefined;
195 }
196
197 return bitArrayByteAt(this.rawBuffer, this.bitOffset, index);
198 }
199
200 equals(other) {
201 if (this.bitSize !== other.bitSize) {
202 return false;
203 }
204
205 const wholeByteCount = Math.trunc(this.bitSize / 8);
206
207 // If both bit offsets are zero do a byte-aligned equality check which is
208 // faster
209 if (this.bitOffset === 0 && other.bitOffset === 0) {
210 // Compare any whole bytes
211 for (let i = 0; i < wholeByteCount; i++) {
212 if (this.rawBuffer[i] !== other.rawBuffer[i]) {
213 return false;
214 }
215 }
216
217 // Compare any trailing bits, excluding unused low bits
218 const trailingBitsCount = this.bitSize % 8;
219 if (trailingBitsCount) {
220 const unusedLowBitCount = 8 - trailingBitsCount;
221 if (
222 this.rawBuffer[wholeByteCount] >> unusedLowBitCount !==
223 other.rawBuffer[wholeByteCount] >> unusedLowBitCount
224 ) {
225 return false;
226 }
227 }
228 } else {
229 // Compare any whole bytes
230 for (let i = 0; i < wholeByteCount; i++) {
231 const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, i);
232 const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i);
233
234 if (a !== b) {
235 return false;
236 }
237 }
238
239 // Compare any trailing bits
240 const trailingBitsCount = this.bitSize % 8;
241 if (trailingBitsCount) {
242 const a = bitArrayByteAt(
243 this.rawBuffer,
244 this.bitOffset,
245 wholeByteCount,
246 );
247 const b = bitArrayByteAt(
248 other.rawBuffer,
249 other.bitOffset,
250 wholeByteCount,
251 );
252
253 const unusedLowBitCount = 8 - trailingBitsCount;
254 if (a >> unusedLowBitCount !== b >> unusedLowBitCount) {
255 return false;
256 }
257 }
258 }
259
260 return true;
261 }
262
263 /**
264 * Returns this bit array's internal buffer.
265 *
266 * @deprecated
267 *
268 * @returns {Uint8Array}
269 */
270 get buffer() {
271 if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) {
272 throw new globalThis.Error(
273 "BitArray.buffer does not support unaligned bit arrays",
274 );
275 }
276
277 return this.rawBuffer;
278 }
279
280 /**
281 * Returns the length in bytes of this bit array's internal buffer.
282 *
283 * @deprecated
284 *
285 * @returns {number}
286 */
287 get length() {
288 if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) {
289 throw new globalThis.Error(
290 "BitArray.length does not support unaligned bit arrays",
291 );
292 }
293
294 return this.rawBuffer.length;
295 }
296}
297
298export const BitArray$BitArray = (buffer, bitSize, bitOffset) =>
299 new BitArray(buffer, bitSize, bitOffset);
300export const BitArray$isBitArray = (value) => value instanceof BitArray;
301export const BitArray$BitArray$data = (bitArray) => {
302 if (bitArray.bitSize % 8 !== 0)
303 throw new globalThis.Error(
304 "BitArray$BitArray$data called on un-aligned bit array",
305 );
306 const array = bitArray.rawBuffer;
307 return new DataView(array.buffer, array.byteOffset, bitArray.byteSize);
308};
309
310/**
311 * Returns the nth byte in the given buffer, after applying the specified bit
312 * offset. If the index is out of bounds then zero is returned.
313 *
314 * @param {Uint8Array} buffer
315 * @param {number} bitOffset
316 * @param {number} index
317 * @returns {number}
318 */
319function bitArrayByteAt(buffer, bitOffset, index) {
320 if (bitOffset === 0) {
321 return buffer[index] ?? 0;
322 } else {
323 const a = (buffer[index] << bitOffset) & 0xff;
324 const b = buffer[index + 1] >> (8 - bitOffset);
325
326 return a | b;
327 }
328}
329
330export class UtfCodepoint {
331 constructor(value) {
332 this.value = value;
333 }
334}
335
336/**
337 * Slices a bit array to produce a new bit array. If `end` is not supplied then
338 * all bits from `start` onward are returned.
339 *
340 * If the slice is out of bounds then an exception is thrown.
341 *
342 * @param {BitArray} bitArray
343 * @param {number} start
344 * @param {number} [end]
345 * @returns {BitArray}
346 */
347export function bitArraySlice(bitArray, start, end) {
348 end ??= bitArray.bitSize;
349
350 bitArrayValidateRange(bitArray, start, end);
351
352 // Handle zero-length slices
353 if (start === end) {
354 return new BitArray(new Uint8Array());
355 }
356
357 // Early return for slices that cover the whole bit array
358 if (start === 0 && end === bitArray.bitSize) {
359 return bitArray;
360 }
361
362 start += bitArray.bitOffset;
363 end += bitArray.bitOffset;
364
365 const startByteIndex = Math.trunc(start / 8);
366 const endByteIndex = Math.trunc((end + 7) / 8);
367 const byteLength = endByteIndex - startByteIndex;
368
369 // Avoid creating a new Uint8Array if the view of the underlying ArrayBuffer
370 // is the same. This can occur when slicing off just the first or last bit of
371 // a bit array, i.e. when only the bit offset or bit size need to be updated.
372 let buffer;
373 if (startByteIndex === 0 && byteLength === bitArray.rawBuffer.byteLength) {
374 buffer = bitArray.rawBuffer;
375 } else {
376 buffer = new Uint8Array(
377 bitArray.rawBuffer.buffer,
378 bitArray.rawBuffer.byteOffset + startByteIndex,
379 byteLength,
380 );
381 }
382
383 return new BitArray(buffer, end - start, start % 8);
384}
385
386/**
387 * Interprets a slice of this bit array as a floating point number, either
388 * 32-bit or 64-bit, with the specified endianness.
389 *
390 * The value of `end - start` must be exactly 32 or 64, otherwise an exception
391 * will be thrown.
392 *
393 * @param {BitArray} bitArray
394 * @param {number} start
395 * @param {number} end
396 * @param {boolean} isBigEndian
397 * @returns {number}
398 */
399export function bitArraySliceToFloat(bitArray, start, end, isBigEndian) {
400 bitArrayValidateRange(bitArray, start, end);
401
402 const floatSize = end - start;
403
404 // Check size is valid
405 if (floatSize !== 16 && floatSize !== 32 && floatSize !== 64) {
406 const msg =
407 `Sized floats must be 16-bit, 32-bit or 64-bit, got size of ` +
408 `${floatSize} bits`;
409 throw new globalThis.Error(msg);
410 }
411
412 start += bitArray.bitOffset;
413
414 const isStartByteAligned = start % 8 === 0;
415
416 // If the bit range is byte aligned then the float can be read directly out
417 // of the existing buffer
418 if (isStartByteAligned) {
419 const view = new DataView(
420 bitArray.rawBuffer.buffer,
421 bitArray.rawBuffer.byteOffset + start / 8,
422 );
423
424 if (floatSize === 64) {
425 return view.getFloat64(0, !isBigEndian);
426 } else if (floatSize === 32) {
427 return view.getFloat32(0, !isBigEndian);
428 } else if (floatSize === 16) {
429 return fp16UintToNumber(view.getUint16(0, !isBigEndian));
430 }
431 }
432
433 // Copy the unaligned bytes into an aligned array so a DataView can be used
434 const alignedBytes = new Uint8Array(floatSize / 8);
435 const byteOffset = Math.trunc(start / 8);
436 for (let i = 0; i < alignedBytes.length; i++) {
437 alignedBytes[i] = bitArrayByteAt(
438 bitArray.rawBuffer,
439 start % 8,
440 byteOffset + i,
441 );
442 }
443
444 // Read the float out of the aligned buffer
445 const view = new DataView(alignedBytes.buffer);
446 if (floatSize === 64) {
447 return view.getFloat64(0, !isBigEndian);
448 } else if (floatSize === 32) {
449 return view.getFloat32(0, !isBigEndian);
450 } else {
451 return fp16UintToNumber(view.getUint16(0, !isBigEndian));
452 }
453}
454
455/**
456 * Interprets a slice of this bit array as a signed or unsigned integer with the
457 * specified endianness.
458 *
459 * @param {BitArray} bitArray
460 * @param {number} start
461 * @param {number} end
462 * @param {boolean} isBigEndian
463 * @param {boolean} isSigned
464 * @returns {number}
465 */
466export function bitArraySliceToInt(
467 bitArray,
468 start,
469 end,
470 isBigEndian,
471 isSigned,
472) {
473 bitArrayValidateRange(bitArray, start, end);
474
475 if (start === end) {
476 return 0;
477 }
478
479 start += bitArray.bitOffset;
480 end += bitArray.bitOffset;
481
482 const isStartByteAligned = start % 8 === 0;
483 const isEndByteAligned = end % 8 === 0;
484
485 // If the slice is byte-aligned then there is no need to handle unaligned
486 // slices, meaning a simpler and faster implementation can be used instead
487 if (isStartByteAligned && isEndByteAligned) {
488 return intFromAlignedSlice(
489 bitArray,
490 start / 8,
491 end / 8,
492 isBigEndian,
493 isSigned,
494 );
495 }
496
497 const size = end - start;
498
499 const startByteIndex = Math.trunc(start / 8);
500 const endByteIndex = Math.trunc((end - 1) / 8);
501
502 // Handle the case of the slice being completely contained in a single byte
503 if (startByteIndex == endByteIndex) {
504 const mask = 0xff >> (start % 8);
505 const unusedLowBitCount = (8 - (end % 8)) % 8;
506
507 let value =
508 (bitArray.rawBuffer[startByteIndex] & mask) >> unusedLowBitCount;
509
510 // For signed integers, if the high bit is set reinterpret as two's
511 // complement
512 if (isSigned) {
513 const highBit = 2 ** (size - 1);
514 if (value >= highBit) {
515 value -= highBit * 2;
516 }
517 }
518
519 return value;
520 }
521
522 // The integer value to be read is not aligned and crosses at least one byte
523 // boundary in the input array
524
525 if (size <= 53) {
526 return intFromUnalignedSliceUsingNumber(
527 bitArray.rawBuffer,
528 start,
529 end,
530 isBigEndian,
531 isSigned,
532 );
533 } else {
534 return intFromUnalignedSliceUsingBigInt(
535 bitArray.rawBuffer,
536 start,
537 end,
538 isBigEndian,
539 isSigned,
540 );
541 }
542}
543
544/**
545 * Joins the given segments into a new bit array, tightly packing them together.
546 * Each segment must be one of the following types:
547 *
548 * - A `number`: A single byte value in the range 0-255. Values outside this
549 * range will be wrapped.
550 * - A `Uint8Array`: A sequence of byte values of any length.
551 * - A `BitArray`: A sequence of bits of any length, which may not be byte
552 * aligned.
553 *
554 * The bit size of the returned bit array will be the sum of the size in bits
555 * of the input segments.
556 *
557 * @param {(number | Uint8Array | BitArray)[]} segments
558 * @returns {BitArray}
559 */
560export function toBitArray(segments) {
561 if (segments.length === 0) {
562 return new BitArray(new Uint8Array());
563 }
564
565 if (segments.length === 1) {
566 const segment = segments[0];
567
568 // When there is a single BitArray segment it can be returned as-is
569 if (segment instanceof BitArray) {
570 return segment;
571 }
572
573 // When there is a single Uint8Array segment, pass it directly to the bit
574 // array constructor to avoid a copy
575 if (segment instanceof Uint8Array) {
576 return new BitArray(segment);
577 }
578
579 return new BitArray(new Uint8Array(/** @type {number[]} */(segments)));
580 }
581
582 // Count the total number of bits and check if all segments are numbers, i.e.
583 // single bytes
584 let bitSize = 0;
585 let areAllSegmentsNumbers = true;
586 for (const segment of segments) {
587 if (segment instanceof BitArray) {
588 bitSize += segment.bitSize;
589 areAllSegmentsNumbers = false;
590 } else if (segment instanceof Uint8Array) {
591 bitSize += segment.byteLength * 8;
592 areAllSegmentsNumbers = false;
593 } else {
594 bitSize += 8;
595 }
596 }
597
598 // If all segments are numbers then pass the segments array directly to the
599 // Uint8Array constructor
600 if (areAllSegmentsNumbers) {
601 return new BitArray(new Uint8Array(/** @type {number[]} */(segments)));
602 }
603
604 // Pack the segments into a Uint8Array
605 const buffer = new Uint8Array(Math.trunc((bitSize + 7) / 8));
606
607 // The current write position in bits into the above array. Byte-aligned
608 // segments, i.e. when the cursor is a multiple of 8, are able to be processed
609 // faster due to being able to copy bytes directly.
610 let cursor = 0;
611
612 for (let segment of segments) {
613 const isCursorByteAligned = cursor % 8 === 0;
614
615 if (segment instanceof BitArray) {
616 if (isCursorByteAligned && segment.bitOffset === 0) {
617 buffer.set(segment.rawBuffer, cursor / 8);
618 cursor += segment.bitSize;
619
620 // Zero any unused bits in the last byte of the buffer. Their content is
621 // undefined and shouldn't be included in the output.
622 const trailingBitsCount = segment.bitSize % 8;
623 if (trailingBitsCount !== 0) {
624 const lastByteIndex = Math.trunc(cursor / 8);
625 buffer[lastByteIndex] >>= 8 - trailingBitsCount;
626 buffer[lastByteIndex] <<= 8 - trailingBitsCount;
627 }
628 } else {
629 appendUnalignedBits(
630 segment.rawBuffer,
631 segment.bitSize,
632 segment.bitOffset,
633 );
634 }
635 } else if (segment instanceof Uint8Array) {
636 if (isCursorByteAligned) {
637 buffer.set(segment, cursor / 8);
638 cursor += segment.byteLength * 8;
639 } else {
640 appendUnalignedBits(segment, segment.byteLength * 8, 0);
641 }
642 } else {
643 if (isCursorByteAligned) {
644 buffer[cursor / 8] = segment;
645 cursor += 8;
646 } else {
647 appendUnalignedBits(new Uint8Array([segment]), 8, 0);
648 }
649 }
650 }
651
652 function appendUnalignedBits(unalignedBits, size, offset) {
653 if (size === 0) {
654 return;
655 }
656
657 const byteSize = Math.trunc(size + 7 / 8);
658
659 const highBitsCount = cursor % 8;
660 const lowBitsCount = 8 - highBitsCount;
661
662 let byteIndex = Math.trunc(cursor / 8);
663
664 for (let i = 0; i < byteSize; i++) {
665 let byte = bitArrayByteAt(unalignedBits, offset, i);
666
667 // If this is a partial byte then zero out the trailing bits as their
668 // content is undefined and shouldn't be included in the output
669 if (size < 8) {
670 byte >>= 8 - size;
671 byte <<= 8 - size;
672 }
673
674 // Copy the high bits of the input byte to the low bits of the current
675 // output byte
676 buffer[byteIndex] |= byte >> highBitsCount;
677
678 let appendedBitsCount = size - Math.max(0, size - lowBitsCount);
679 size -= appendedBitsCount;
680 cursor += appendedBitsCount;
681
682 if (size === 0) {
683 break;
684 }
685
686 // Copy the low bits of the input byte to the high bits of the next output
687 // byte
688 buffer[++byteIndex] = byte << lowBitsCount;
689 appendedBitsCount = size - Math.max(0, size - highBitsCount);
690 size -= appendedBitsCount;
691 cursor += appendedBitsCount;
692 }
693 }
694
695 return new BitArray(buffer, bitSize);
696}
697
698/**
699 * Encodes a floating point value into a `Uint8Array`. This is used to create
700 * float segments that are part of bit array expressions.
701 *
702 * @param {number} value
703 * @param {number} size
704 * @param {boolean} isBigEndian
705 * @returns {Uint8Array}
706 */
707export function sizedFloat(value, size, isBigEndian) {
708 if (size !== 16 && size !== 32 && size !== 64) {
709 const msg = `Sized floats must be 16-bit, 32-bit or 64-bit, got size of ${size} bits`;
710 throw new globalThis.Error(msg);
711 }
712
713 if (size === 16) {
714 return numberToFp16Uint(value, isBigEndian);
715 }
716
717 const buffer = new Uint8Array(size / 8);
718
719 const view = new DataView(buffer.buffer);
720
721 if (size == 64) {
722 view.setFloat64(0, value, !isBigEndian);
723 } else {
724 view.setFloat32(0, value, !isBigEndian);
725 }
726
727 return buffer;
728}
729
730/**
731 * Encodes an integer value into a `Uint8Array`, or a `BitArray` if the size in
732 * bits is not a multiple of 8. This is used to create integer segments used in
733 * bit array expressions.
734 *
735 * @param {number} value
736 * @param {number} size
737 * @param {boolean} isBigEndian
738 * @returns {Uint8Array | BitArray}
739 */
740export function sizedInt(value, size, isBigEndian) {
741 if (size <= 0) {
742 return new Uint8Array();
743 }
744
745 // Fast path when size is 8 bits. This relies on the rounding behavior of the
746 // Uint8Array constructor.
747 if (size === 8) {
748 return new Uint8Array([value]);
749 }
750
751 // Fast path when size is less than 8 bits: shift the value up to the high
752 // bits
753 if (size < 8) {
754 value <<= 8 - size;
755 return new BitArray(new Uint8Array([value]), size);
756 }
757
758 // Allocate output buffer
759 const buffer = new Uint8Array(Math.trunc((size + 7) / 8));
760
761 // The number of trailing bits in the final byte. Will be zero if the size is
762 // an exact number of bytes.
763 const trailingBitsCount = size % 8;
764
765 // The number of unused bits in the final byte of the buffer
766 const unusedBitsCount = 8 - trailingBitsCount;
767
768 // For output sizes not exceeding 32 bits the number type is used. For larger
769 // output sizes the BigInt type is needed.
770 //
771 // The code in each of these two paths must be kept in sync.
772 if (size <= 32) {
773 if (isBigEndian) {
774 let i = buffer.length - 1;
775
776 // Set the trailing bits at the end of the output buffer
777 if (trailingBitsCount) {
778 buffer[i--] = (value << unusedBitsCount) & 0xff;
779 value >>= trailingBitsCount;
780 }
781
782 for (; i >= 0; i--) {
783 buffer[i] = value;
784 value >>= 8;
785 }
786 } else {
787 let i = 0;
788
789 const wholeByteCount = Math.trunc(size / 8);
790 for (; i < wholeByteCount; i++) {
791 buffer[i] = value;
792 value >>= 8;
793 }
794
795 // Set the trailing bits at the end of the output buffer
796 if (trailingBitsCount) {
797 buffer[i] = value << unusedBitsCount;
798 }
799 }
800 } else {
801 const bigTrailingBitsCount = BigInt(trailingBitsCount);
802 const bigUnusedBitsCount = BigInt(unusedBitsCount);
803
804 let bigValue = BigInt(value);
805
806 if (isBigEndian) {
807 let i = buffer.length - 1;
808
809 // Set the trailing bits at the end of the output buffer
810 if (trailingBitsCount) {
811 buffer[i--] = Number(bigValue << bigUnusedBitsCount);
812 bigValue >>= bigTrailingBitsCount;
813 }
814
815 for (; i >= 0; i--) {
816 buffer[i] = Number(bigValue);
817 bigValue >>= 8n;
818 }
819 } else {
820 let i = 0;
821
822 const wholeByteCount = Math.trunc(size / 8);
823 for (; i < wholeByteCount; i++) {
824 buffer[i] = Number(bigValue);
825 bigValue >>= 8n;
826 }
827
828 // Set the trailing bits at the end of the output buffer
829 if (trailingBitsCount) {
830 buffer[i] = Number(bigValue << bigUnusedBitsCount);
831 }
832 }
833 }
834
835 // Integers that aren't a whole number of bytes are returned as a BitArray so
836 // their size in bits is tracked
837 if (trailingBitsCount) {
838 return new BitArray(buffer, size);
839 }
840
841 return buffer;
842}
843
844/**
845 * Reads an aligned slice of any size as an integer.
846 *
847 * @param {BitArray} bitArray
848 * @param {number} start
849 * @param {number} end
850 * @param {boolean} isBigEndian
851 * @param {boolean} isSigned
852 * @returns {number}
853 */
854function intFromAlignedSlice(bitArray, start, end, isBigEndian, isSigned) {
855 const byteSize = end - start;
856
857 if (byteSize <= 6) {
858 return intFromAlignedSliceUsingNumber(
859 bitArray.rawBuffer,
860 start,
861 end,
862 isBigEndian,
863 isSigned,
864 );
865 } else {
866 return intFromAlignedSliceUsingBigInt(
867 bitArray.rawBuffer,
868 start,
869 end,
870 isBigEndian,
871 isSigned,
872 );
873 }
874}
875
876/**
877 * Reads an aligned slice up to 48 bits in size as an integer. Uses the
878 * JavaScript `number` type internally.
879 *
880 * @param {Uint8Array} buffer
881 * @param {number} start
882 * @param {number} end
883 * @param {boolean} isBigEndian
884 * @param {boolean} isSigned
885 * @returns {number}
886 */
887function intFromAlignedSliceUsingNumber(
888 buffer,
889 start,
890 end,
891 isBigEndian,
892 isSigned,
893) {
894 const byteSize = end - start;
895
896 let value = 0;
897
898 // Read bytes as an unsigned integer
899 if (isBigEndian) {
900 for (let i = start; i < end; i++) {
901 value *= 256;
902 value += buffer[i];
903 }
904 } else {
905 for (let i = end - 1; i >= start; i--) {
906 value *= 256;
907 value += buffer[i];
908 }
909 }
910
911 // For signed integers, if the high bit is set reinterpret as two's
912 // complement
913 if (isSigned) {
914 const highBit = 2 ** (byteSize * 8 - 1);
915 if (value >= highBit) {
916 value -= highBit * 2;
917 }
918 }
919
920 return value;
921}
922
923/**
924 * Reads an aligned slice of any size as an integer. Uses the JavaScript
925 * `BigInt` type internally.
926 *
927 * @param {Uint8Array} buffer
928 * @param {number} start
929 * @param {number} end
930 * @param {boolean} isBigEndian
931 * @param {boolean} isSigned
932 * @returns {number}
933 */
934function intFromAlignedSliceUsingBigInt(
935 buffer,
936 start,
937 end,
938 isBigEndian,
939 isSigned,
940) {
941 const byteSize = end - start;
942
943 let value = 0n;
944
945 // Read bytes as an unsigned integer value
946 if (isBigEndian) {
947 for (let i = start; i < end; i++) {
948 value *= 256n;
949 value += BigInt(buffer[i]);
950 }
951 } else {
952 for (let i = end - 1; i >= start; i--) {
953 value *= 256n;
954 value += BigInt(buffer[i]);
955 }
956 }
957
958 // For signed integers, if the high bit is set reinterpret as two's
959 // complement
960 if (isSigned) {
961 const highBit = 1n << BigInt(byteSize * 8 - 1);
962 if (value >= highBit) {
963 value -= highBit * 2n;
964 }
965 }
966
967 // Convert the result into a JS number. This may cause quantizing/error on
968 // values outside JavaScript's safe integer range.
969 return Number(value);
970}
971
972/**
973 * Reads an unaligned slice up to 53 bits in size as an integer. Uses the
974 * JavaScript `number` type internally.
975 *
976 * This function assumes that the slice crosses at least one byte boundary in
977 * the input.
978 *
979 * @param {Uint8Array} buffer
980 * @param {number} start
981 * @param {number} end
982 * @param {boolean} isBigEndian
983 * @param {boolean} isSigned
984 * @returns {number}
985 */
986function intFromUnalignedSliceUsingNumber(
987 buffer,
988 start,
989 end,
990 isBigEndian,
991 isSigned,
992) {
993 const isStartByteAligned = start % 8 === 0;
994
995 let size = end - start;
996 let byteIndex = Math.trunc(start / 8);
997
998 let value = 0;
999
1000 if (isBigEndian) {
1001 // Read any leading bits
1002 if (!isStartByteAligned) {
1003 const leadingBitsCount = 8 - (start % 8);
1004 value = buffer[byteIndex++] & ((1 << leadingBitsCount) - 1);
1005 size -= leadingBitsCount;
1006 }
1007
1008 // Read any whole bytes
1009 while (size >= 8) {
1010 value *= 256;
1011 value += buffer[byteIndex++];
1012 size -= 8;
1013 }
1014
1015 // Read any trailing bits
1016 if (size > 0) {
1017 value *= 2 ** size;
1018 value += buffer[byteIndex] >> (8 - size);
1019 }
1020 } else {
1021 // For little endian, if the start is aligned then whole bytes can be read
1022 // directly out of the input array, with the trailing bits handled at the
1023 // end
1024 if (isStartByteAligned) {
1025 let size = end - start;
1026 let scale = 1;
1027
1028 // Read whole bytes
1029 while (size >= 8) {
1030 value += buffer[byteIndex++] * scale;
1031 scale *= 256;
1032 size -= 8;
1033 }
1034
1035 // Read trailing bits
1036 value += (buffer[byteIndex] >> (8 - size)) * scale;
1037 } else {
1038 // Read little endian data where the start is not byte-aligned. This is
1039 // done by reading whole bytes that cross a byte boundary in the input
1040 // data, then reading any trailing bits.
1041
1042 const highBitsCount = start % 8;
1043 const lowBitsCount = 8 - highBitsCount;
1044
1045 let size = end - start;
1046 let scale = 1;
1047
1048 // Extract whole bytes
1049 while (size >= 8) {
1050 const byte =
1051 (buffer[byteIndex] << highBitsCount) |
1052 (buffer[byteIndex + 1] >> lowBitsCount);
1053
1054 value += (byte & 0xff) * scale;
1055
1056 scale *= 256;
1057 size -= 8;
1058 byteIndex++;
1059 }
1060
1061 // Read any trailing bits. These trailing bits may cross a byte boundary
1062 // in the input buffer.
1063 if (size > 0) {
1064 const lowBitsUsed = size - Math.max(0, size - lowBitsCount);
1065
1066 let trailingByte =
1067 (buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >>
1068 (lowBitsCount - lowBitsUsed);
1069
1070 size -= lowBitsUsed;
1071
1072 if (size > 0) {
1073 trailingByte *= 2 ** size;
1074 trailingByte += buffer[byteIndex + 1] >> (8 - size);
1075 }
1076
1077 value += trailingByte * scale;
1078 }
1079 }
1080 }
1081
1082 // For signed integers, if the high bit is set reinterpret as two's
1083 // complement
1084 if (isSigned) {
1085 const highBit = 2 ** (end - start - 1);
1086 if (value >= highBit) {
1087 value -= highBit * 2;
1088 }
1089 }
1090
1091 return value;
1092}
1093
1094/**
1095 * Reads an unaligned slice of any size as an integer. Uses the JavaScript
1096 * `BigInt` type internally.
1097 *
1098 * This function assumes that the slice crosses at least one byte boundary in
1099 * the input.
1100 *
1101 * @param {Uint8Array} buffer
1102 * @param {number} start
1103 * @param {number} end
1104 * @param {boolean} isBigEndian
1105 * @param {boolean} isSigned
1106 * @returns {number}
1107 */
1108function intFromUnalignedSliceUsingBigInt(
1109 buffer,
1110 start,
1111 end,
1112 isBigEndian,
1113 isSigned,
1114) {
1115 const isStartByteAligned = start % 8 === 0;
1116
1117 let size = end - start;
1118 let byteIndex = Math.trunc(start / 8);
1119
1120 let value = 0n;
1121
1122 if (isBigEndian) {
1123 // Read any leading bits
1124 if (!isStartByteAligned) {
1125 const leadingBitsCount = 8 - (start % 8);
1126 value = BigInt(buffer[byteIndex++] & ((1 << leadingBitsCount) - 1));
1127 size -= leadingBitsCount;
1128 }
1129
1130 // Read any whole bytes
1131 while (size >= 8) {
1132 value *= 256n;
1133 value += BigInt(buffer[byteIndex++]);
1134 size -= 8;
1135 }
1136
1137 // Read any trailing bits
1138 if (size > 0) {
1139 value <<= BigInt(size);
1140 value += BigInt(buffer[byteIndex] >> (8 - size));
1141 }
1142 } else {
1143 // For little endian, if the start is aligned then whole bytes can be read
1144 // directly out of the input array, with the trailing bits handled at the
1145 // end
1146 if (isStartByteAligned) {
1147 let size = end - start;
1148 let shift = 0n;
1149
1150 // Read whole bytes
1151 while (size >= 8) {
1152 value += BigInt(buffer[byteIndex++]) << shift;
1153 shift += 8n;
1154 size -= 8;
1155 }
1156
1157 // Read trailing bits
1158 value += BigInt(buffer[byteIndex] >> (8 - size)) << shift;
1159 } else {
1160 // Read little endian data where the start is not byte-aligned. This is
1161 // done by reading whole bytes that cross a byte boundary in the input
1162 // data, then reading any trailing bits.
1163
1164 const highBitsCount = start % 8;
1165 const lowBitsCount = 8 - highBitsCount;
1166
1167 let size = end - start;
1168 let shift = 0n;
1169
1170 // Extract whole bytes
1171 while (size >= 8) {
1172 const byte =
1173 (buffer[byteIndex] << highBitsCount) |
1174 (buffer[byteIndex + 1] >> lowBitsCount);
1175
1176 value += BigInt(byte & 0xff) << shift;
1177
1178 shift += 8n;
1179 size -= 8;
1180 byteIndex++;
1181 }
1182
1183 // Read any trailing bits. These trailing bits may cross a byte boundary
1184 // in the input buffer.
1185 if (size > 0) {
1186 const lowBitsUsed = size - Math.max(0, size - lowBitsCount);
1187
1188 let trailingByte =
1189 (buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >>
1190 (lowBitsCount - lowBitsUsed);
1191
1192 size -= lowBitsUsed;
1193
1194 if (size > 0) {
1195 trailingByte <<= size;
1196 trailingByte += buffer[byteIndex + 1] >> (8 - size);
1197 }
1198
1199 value += BigInt(trailingByte) << shift;
1200 }
1201 }
1202 }
1203
1204 // For signed integers, if the high bit is set reinterpret as two's
1205 // complement
1206 if (isSigned) {
1207 const highBit = 2n ** BigInt(end - start - 1);
1208 if (value >= highBit) {
1209 value -= highBit * 2n;
1210 }
1211 }
1212
1213 // Convert the result into a JS number. This may cause quantizing/error on
1214 // values outside JavaScript's safe integer range.
1215 return Number(value);
1216}
1217
1218/**
1219 * Interprets a 16-bit unsigned integer value as a 16-bit floating point value.
1220 *
1221 * @param {number} intValue
1222 * @returns {number}
1223 */
1224function fp16UintToNumber(intValue) {
1225 const sign = intValue >= 0x8000 ? -1 : 1;
1226 const exponent = (intValue & 0x7c00) >> 10;
1227 const fraction = intValue & 0x03ff;
1228
1229 let value;
1230 if (exponent === 0) {
1231 value = 6.103515625e-5 * (fraction / 0x400);
1232 } else if (exponent === 0x1f) {
1233 value = fraction === 0 ? Infinity : NaN;
1234 } else {
1235 value = Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
1236 }
1237
1238 return sign * value;
1239}
1240
1241/**
1242 * Converts a floating point number to bytes for a 16-bit floating point value.
1243 *
1244 * @param {number} intValue
1245 * @param {boolean} isBigEndian
1246 * @returns {Uint8Array}
1247 */
1248function numberToFp16Uint(value, isBigEndian) {
1249 const buffer = new Uint8Array(2);
1250
1251 if (isNaN(value)) {
1252 buffer[1] = 0x7e;
1253 } else if (value === Infinity) {
1254 buffer[1] = 0x7c;
1255 } else if (value === -Infinity) {
1256 buffer[1] = 0xfc;
1257 } else if (value === 0) {
1258 // 0 === -0, so we need to do an additional check here
1259 if (isNegativeZero(value)) {
1260 buffer[1] = 128;
1261 }
1262 // Otherwise, both values are already zero
1263 } else {
1264 const sign = value < 0 ? 1 : 0;
1265 value = Math.abs(value);
1266
1267 let exponent = Math.floor(Math.log2(value));
1268 let fraction = value / Math.pow(2, exponent) - 1;
1269
1270 exponent += 15;
1271
1272 if (exponent <= 0) {
1273 exponent = 0;
1274 fraction = value / Math.pow(2, -14);
1275 } else if (exponent >= 31) {
1276 exponent = 31;
1277 fraction = 0;
1278 }
1279
1280 fraction = Math.round(fraction * 1024);
1281
1282 buffer[1] =
1283 (sign << 7) | ((exponent & 0x1f) << 2) | ((fraction >> 8) & 0x03);
1284 buffer[0] = fraction & 0xff;
1285 }
1286
1287 if (isBigEndian) {
1288 const a = buffer[0];
1289 buffer[0] = buffer[1];
1290 buffer[1] = a;
1291 }
1292
1293 return buffer;
1294}
1295
1296/**
1297 * Returns whether or not a value is `-0`, since this cannot be checked using
1298 * equality.
1299 *
1300 * @param {number} value
1301 * @returns {boolean}
1302 */
1303function isNegativeZero(value) {
1304 // One of the few differences between 0 and -0 is that division by -0 returns
1305 // -Infinity rather than Infinity.
1306 return 1 / value === -Infinity;
1307}
1308
1309/**
1310 * Throws an exception if the given start and end values are out of bounds for
1311 * a bit array.
1312 *
1313 * @param {BitArray} bitArray
1314 * @param {number} start
1315 * @param {number} end
1316 */
1317function bitArrayValidateRange(bitArray, start, end) {
1318 if (
1319 start < 0 ||
1320 start > bitArray.bitSize ||
1321 end < start ||
1322 end > bitArray.bitSize
1323 ) {
1324 const msg =
1325 `Invalid bit array slice: start = ${start}, end = ${end}, ` +
1326 `bit size = ${bitArray.bitSize}`;
1327 throw new globalThis.Error(msg);
1328 }
1329}
1330
1331/** @type {TextEncoder | undefined} */
1332let utf8Encoder;
1333
1334/**
1335 * Returns the UTF-8 bytes for a string.
1336 *
1337 * @param {string} string
1338 * @returns {Uint8Array}
1339 */
1340export function stringBits(string) {
1341 utf8Encoder ??= new TextEncoder();
1342 return utf8Encoder.encode(string);
1343}
1344
1345/**
1346 * Returns the UTF-8 bytes for a single UTF codepoint.
1347 *
1348 * @param {UtfCodepoint} codepoint
1349 * @returns {Uint8Array}
1350 */
1351export function codepointBits(codepoint) {
1352 return stringBits(String.fromCodePoint(codepoint.value));
1353}
1354
1355/**
1356 * Returns the UTF-16 bytes for a string.
1357 *
1358 * @param {string} string
1359 * @param {boolean} isBigEndian
1360 * @returns {Uint8Array}
1361 */
1362export function stringToUtf16(string, isBigEndian) {
1363 const buffer = new ArrayBuffer(string.length * 2);
1364 const bufferView = new DataView(buffer);
1365
1366 for (let i = 0; i < string.length; i++) {
1367 bufferView.setUint16(i * 2, string.charCodeAt(i), !isBigEndian);
1368 }
1369
1370 return new Uint8Array(buffer);
1371}
1372
1373/**
1374 * Returns the UTF-16 bytes for a single UTF codepoint.
1375 *
1376 * @param {UtfCodepoint} codepoint
1377 * @param {boolean} isBigEndian
1378 * @returns {Uint8Array}
1379 */
1380export function codepointToUtf16(codepoint, isBigEndian) {
1381 return stringToUtf16(String.fromCodePoint(codepoint.value), isBigEndian);
1382}
1383
1384/**
1385 * Returns the UTF-32 bytes for a string.
1386 *
1387 * @param {string} string
1388 * @param {boolean} isBigEndian
1389 * @returns {Uint8Array}
1390 */
1391export function stringToUtf32(string, isBigEndian) {
1392 const buffer = new ArrayBuffer(string.length * 4);
1393 const bufferView = new DataView(buffer);
1394 let length = 0;
1395
1396 for (let i = 0; i < string.length; i++) {
1397 const codepoint = string.codePointAt(i);
1398
1399 bufferView.setUint32(length * 4, codepoint, !isBigEndian);
1400 length++;
1401
1402 if (codepoint > 0xffff) {
1403 i++;
1404 }
1405 }
1406
1407 return new Uint8Array(buffer.slice(0, length * 4));
1408}
1409
1410/**
1411 * Returns the UTF-32 bytes for a single UTF codepoint.
1412 *
1413 * @param {UtfCodepoint} codepoint
1414 * @param {boolean} isBigEndian
1415 * @returns {Uint8Array}
1416 */
1417export function codepointToUtf32(codepoint, isBigEndian) {
1418 return stringToUtf32(String.fromCodePoint(codepoint.value), isBigEndian);
1419}
1420
1421export class Result extends CustomType {
1422 static isResult(data) {
1423 return data instanceof Result;
1424 }
1425}
1426
1427export class Ok extends Result {
1428 constructor(value) {
1429 super();
1430 this[0] = value;
1431 }
1432
1433 isOk() {
1434 return true;
1435 }
1436}
1437export const Result$Ok = (value) => new Ok(value);
1438export const Result$isOk = (value) => value instanceof Ok;
1439export const Result$Ok$0 = (value) => value[0];
1440
1441export class Error extends Result {
1442 constructor(detail) {
1443 super();
1444 this[0] = detail;
1445 }
1446
1447 isOk() {
1448 return false;
1449 }
1450}
1451export const Result$Error = (detail) => new Error(detail);
1452export const Result$isError = (value) => value instanceof Error;
1453export const Result$Error$0 = (value) => value[0];
1454
1455export function isEqual(x, y) {
1456 let values = [x, y];
1457
1458 while (values.length) {
1459 let a = values.pop();
1460 let b = values.pop();
1461 if (a === b) continue;
1462
1463 if (!isObject(a) || !isObject(b)) return false;
1464 let unequal =
1465 !structurallyCompatibleObjects(a, b) ||
1466 unequalDates(a, b) ||
1467 unequalBuffers(a, b) ||
1468 unequalArrays(a, b) ||
1469 unequalMaps(a, b) ||
1470 unequalSets(a, b) ||
1471 unequalRegExps(a, b);
1472 if (unequal) return false;
1473
1474 const proto = Object.getPrototypeOf(a);
1475 if (proto !== null && typeof proto.equals === "function") {
1476 try {
1477 if (a.equals(b)) continue;
1478 else return false;
1479 } catch { }
1480 }
1481
1482 let [keys, get] = getters(a);
1483 const ka = keys(a);
1484 const kb = keys(b);
1485 if (ka.length !== kb.length) return false;
1486 for (let k of ka) {
1487 values.push(get(a, k), get(b, k));
1488 }
1489 }
1490
1491 return true;
1492}
1493
1494function getters(object) {
1495 if (object instanceof Map) {
1496 return [(x) => x.keys(), (x, y) => x.get(y)];
1497 } else {
1498 let extra = object instanceof globalThis.Error ? ["message"] : [];
1499 return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]];
1500 }
1501}
1502
1503function unequalDates(a, b) {
1504 return a instanceof Date && (a > b || a < b);
1505}
1506
1507function unequalBuffers(a, b) {
1508 return (
1509 !(a instanceof BitArray) &&
1510 a.buffer instanceof ArrayBuffer &&
1511 a.BYTES_PER_ELEMENT &&
1512 !(a.byteLength === b.byteLength && a.every((n, i) => n === b[i]))
1513 );
1514}
1515
1516function unequalArrays(a, b) {
1517 return Array.isArray(a) && a.length !== b.length;
1518}
1519
1520function unequalMaps(a, b) {
1521 return a instanceof Map && a.size !== b.size;
1522}
1523
1524function unequalSets(a, b) {
1525 return (
1526 a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e)))
1527 );
1528}
1529
1530function unequalRegExps(a, b) {
1531 return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags);
1532}
1533
1534function isObject(a) {
1535 return typeof a === "object" && a !== null;
1536}
1537
1538function structurallyCompatibleObjects(a, b) {
1539 if (typeof a !== "object" && typeof b !== "object" && (!a || !b))
1540 return false;
1541
1542 let nonstructural = [Promise, WeakSet, WeakMap, Function];
1543 if (nonstructural.some((c) => a instanceof c)) return false;
1544
1545 return a.constructor === b.constructor;
1546}
1547
1548export function remainderInt(a, b) {
1549 if (b === 0) {
1550 return 0;
1551 } else {
1552 return a % b;
1553 }
1554}
1555
1556export function divideInt(a, b) {
1557 return Math.trunc(divideFloat(a, b));
1558}
1559
1560export function divideFloat(a, b) {
1561 if (b === 0) {
1562 return 0;
1563 } else {
1564 return a / b;
1565 }
1566}
1567
1568export function makeError(variant, file, module, line, fn, message, extra) {
1569 let error = new globalThis.Error(message);
1570 error.gleam_error = variant;
1571 error.file = file;
1572 error.module = module;
1573 error.line = line;
1574 error.function = fn;
1575 // TODO: Remove this with Gleam v2.0.0
1576 error.fn = fn;
1577 for (let k in extra) error[k] = extra[k];
1578 return error;
1579}