Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / templates / prelude.mjs
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}