stellar_prune/build/dev/javascript/prelude.mjs
2025-11-30 15:44:22 +01:00

1575 lines
39 KiB
JavaScript

export class CustomType {
withFields(fields) {
let properties = Object.keys(this).map((label) =>
label in fields ? fields[label] : this[label],
);
return new this.constructor(...properties);
}
}
export class List {
static fromArray(array, tail) {
let t = tail || new Empty();
for (let i = array.length - 1; i >= 0; --i) {
t = new NonEmpty(array[i], t);
}
return t;
}
[Symbol.iterator]() {
return new ListIterator(this);
}
toArray() {
return [...this];
}
atLeastLength(desired) {
let current = this;
while (desired-- > 0 && current) current = current.tail;
return current !== undefined;
}
hasLength(desired) {
let current = this;
while (desired-- > 0 && current) current = current.tail;
return desired === -1 && current instanceof Empty;
}
countLength() {
let current = this;
let length = 0;
while (current) {
current = current.tail;
length++;
}
return length - 1;
}
}
export function prepend(element, tail) {
return new NonEmpty(element, tail);
}
export function toList(elements, tail) {
return List.fromArray(elements, tail);
}
class ListIterator {
#current;
constructor(current) {
this.#current = current;
}
next() {
if (this.#current instanceof Empty) {
return { done: true };
} else {
let { head, tail } = this.#current;
this.#current = tail;
return { value: head, done: false };
}
}
}
export class Empty extends List {}
export const List$Empty = () => new Empty();
export const List$isEmpty = (value) => value instanceof Empty;
export class NonEmpty extends List {
constructor(head, tail) {
super();
this.head = head;
this.tail = tail;
}
}
export const List$NonEmpty = (head, tail) => new NonEmpty(head, tail);
export const List$isNonEmpty = (value) => value instanceof NonEmpty;
export const List$NonEmpty$first = (value) => value.head;
export const List$NonEmpty$rest = (value) => value.tail;
/**
* A bit array is a contiguous sequence of bits similar to Erlang's Binary type.
*/
export class BitArray {
/**
* The size in bits of this bit array's data.
*
* @type {number}
*/
bitSize;
/**
* The size in bytes of this bit array's data. If this bit array doesn't store
* a whole number of bytes then this value is rounded up.
*
* @type {number}
*/
byteSize;
/**
* The number of unused high bits in the first byte of this bit array's
* buffer prior to the start of its data. The value of any unused high bits is
* undefined.
*
* The bit offset will be in the range 0-7.
*
* @type {number}
*/
bitOffset;
/**
* The raw bytes that hold this bit array's data.
*
* If `bitOffset` is not zero then there are unused high bits in the first
* byte of this buffer.
*
* If `bitOffset + bitSize` is not a multiple of 8 then there are unused low
* bits in the last byte of this buffer.
*
* @type {Uint8Array}
*/
rawBuffer;
/**
* Constructs a new bit array from a `Uint8Array`, an optional size in
* bits, and an optional bit offset.
*
* If no bit size is specified it is taken as `buffer.length * 8`, i.e. all
* bytes in the buffer make up the new bit array's data.
*
* If no bit offset is specified it defaults to zero, i.e. there are no unused
* high bits in the first byte of the buffer.
*
* @param {Uint8Array} buffer
* @param {number} [bitSize]
* @param {number} [bitOffset]
*/
constructor(buffer, bitSize, bitOffset) {
if (!(buffer instanceof Uint8Array)) {
throw globalThis.Error(
"BitArray can only be constructed from a Uint8Array",
);
}
this.bitSize = bitSize ?? buffer.length * 8;
this.byteSize = Math.trunc((this.bitSize + 7) / 8);
this.bitOffset = bitOffset ?? 0;
// Validate the bit size
if (this.bitSize < 0) {
throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`);
}
// Validate the bit offset
if (this.bitOffset < 0 || this.bitOffset > 7) {
throw globalThis.Error(
`BitArray bit offset is invalid: ${this.bitOffset}`,
);
}
// Validate the length of the buffer
if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) {
throw globalThis.Error("BitArray buffer length is invalid");
}
this.rawBuffer = buffer;
}
/**
* Returns a specific byte in this bit array. If the byte index is out of
* range then `undefined` is returned.
*
* When returning the final byte of a bit array with a bit size that's not a
* multiple of 8, the content of the unused low bits are undefined.
*
* @param {number} index
* @returns {number | undefined}
*/
byteAt(index) {
if (index < 0 || index >= this.byteSize) {
return undefined;
}
return bitArrayByteAt(this.rawBuffer, this.bitOffset, index);
}
equals(other) {
if (this.bitSize !== other.bitSize) {
return false;
}
const wholeByteCount = Math.trunc(this.bitSize / 8);
// If both bit offsets are zero do a byte-aligned equality check which is
// faster
if (this.bitOffset === 0 && other.bitOffset === 0) {
// Compare any whole bytes
for (let i = 0; i < wholeByteCount; i++) {
if (this.rawBuffer[i] !== other.rawBuffer[i]) {
return false;
}
}
// Compare any trailing bits, excluding unused low bits
const trailingBitsCount = this.bitSize % 8;
if (trailingBitsCount) {
const unusedLowBitCount = 8 - trailingBitsCount;
if (
this.rawBuffer[wholeByteCount] >> unusedLowBitCount !==
other.rawBuffer[wholeByteCount] >> unusedLowBitCount
) {
return false;
}
}
} else {
// Compare any whole bytes
for (let i = 0; i < wholeByteCount; i++) {
const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, i);
const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i);
if (a !== b) {
return false;
}
}
// Compare any trailing bits
const trailingBitsCount = this.bitSize % 8;
if (trailingBitsCount) {
const a = bitArrayByteAt(
this.rawBuffer,
this.bitOffset,
wholeByteCount,
);
const b = bitArrayByteAt(
other.rawBuffer,
other.bitOffset,
wholeByteCount,
);
const unusedLowBitCount = 8 - trailingBitsCount;
if (a >> unusedLowBitCount !== b >> unusedLowBitCount) {
return false;
}
}
}
return true;
}
/**
* Returns this bit array's internal buffer.
*
* @deprecated Use `BitArray.byteAt()` or `BitArray.rawBuffer` instead.
*
* @returns {Uint8Array}
*/
get buffer() {
bitArrayPrintDeprecationWarning(
"buffer",
"Use BitArray.byteAt() or BitArray.rawBuffer instead",
);
if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) {
throw new globalThis.Error(
"BitArray.buffer does not support unaligned bit arrays",
);
}
return this.rawBuffer;
}
/**
* Returns the length in bytes of this bit array's internal buffer.
*
* @deprecated Use `BitArray.bitSize` or `BitArray.byteSize` instead.
*
* @returns {number}
*/
get length() {
bitArrayPrintDeprecationWarning(
"length",
"Use BitArray.bitSize or BitArray.byteSize instead",
);
if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) {
throw new globalThis.Error(
"BitArray.length does not support unaligned bit arrays",
);
}
return this.rawBuffer.length;
}
}
export const BitArray$BitArray = (buffer, bitSize, bitOffset) =>
new BitArray(buffer, bitSize, bitOffset);
/**
* Returns the nth byte in the given buffer, after applying the specified bit
* offset. If the index is out of bounds then zero is returned.
*
* @param {Uint8Array} buffer
* @param {number} bitOffset
* @param {number} index
* @returns {number}
*/
function bitArrayByteAt(buffer, bitOffset, index) {
if (bitOffset === 0) {
return buffer[index] ?? 0;
} else {
const a = (buffer[index] << bitOffset) & 0xff;
const b = buffer[index + 1] >> (8 - bitOffset);
return a | b;
}
}
export class UtfCodepoint {
constructor(value) {
this.value = value;
}
}
const isBitArrayDeprecationMessagePrinted = {};
function bitArrayPrintDeprecationWarning(name, message) {
if (isBitArrayDeprecationMessagePrinted[name]) {
return;
}
console.warn(
`Deprecated BitArray.${name} property used in JavaScript FFI code. ${message}.`,
);
isBitArrayDeprecationMessagePrinted[name] = true;
}
/**
* Slices a bit array to produce a new bit array. If `end` is not supplied then
* all bits from `start` onward are returned.
*
* If the slice is out of bounds then an exception is thrown.
*
* @param {BitArray} bitArray
* @param {number} start
* @param {number} [end]
* @returns {BitArray}
*/
export function bitArraySlice(bitArray, start, end) {
end ??= bitArray.bitSize;
bitArrayValidateRange(bitArray, start, end);
// Handle zero-length slices
if (start === end) {
return new BitArray(new Uint8Array());
}
// Early return for slices that cover the whole bit array
if (start === 0 && end === bitArray.bitSize) {
return bitArray;
}
start += bitArray.bitOffset;
end += bitArray.bitOffset;
const startByteIndex = Math.trunc(start / 8);
const endByteIndex = Math.trunc((end + 7) / 8);
const byteLength = endByteIndex - startByteIndex;
// Avoid creating a new Uint8Array if the view of the underlying ArrayBuffer
// is the same. This can occur when slicing off just the first or last bit of
// a bit array, i.e. when only the bit offset or bit size need to be updated.
let buffer;
if (startByteIndex === 0 && byteLength === bitArray.rawBuffer.byteLength) {
buffer = bitArray.rawBuffer;
} else {
buffer = new Uint8Array(
bitArray.rawBuffer.buffer,
bitArray.rawBuffer.byteOffset + startByteIndex,
byteLength,
);
}
return new BitArray(buffer, end - start, start % 8);
}
/**
* Interprets a slice of this bit array as a floating point number, either
* 32-bit or 64-bit, with the specified endianness.
*
* The value of `end - start` must be exactly 32 or 64, otherwise an exception
* will be thrown.
*
* @param {BitArray} bitArray
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @returns {number}
*/
export function bitArraySliceToFloat(bitArray, start, end, isBigEndian) {
bitArrayValidateRange(bitArray, start, end);
const floatSize = end - start;
// Check size is valid
if (floatSize !== 16 && floatSize !== 32 && floatSize !== 64) {
const msg =
`Sized floats must be 16-bit, 32-bit or 64-bit, got size of ` +
`${floatSize} bits`;
throw new globalThis.Error(msg);
}
start += bitArray.bitOffset;
const isStartByteAligned = start % 8 === 0;
// If the bit range is byte aligned then the float can be read directly out
// of the existing buffer
if (isStartByteAligned) {
const view = new DataView(
bitArray.rawBuffer.buffer,
bitArray.rawBuffer.byteOffset + start / 8,
);
if (floatSize === 64) {
return view.getFloat64(0, !isBigEndian);
} else if (floatSize === 32) {
return view.getFloat32(0, !isBigEndian);
} else if (floatSize === 16) {
return fp16UintToNumber(view.getUint16(0, !isBigEndian));
}
}
// Copy the unaligned bytes into an aligned array so a DataView can be used
const alignedBytes = new Uint8Array(floatSize / 8);
const byteOffset = Math.trunc(start / 8);
for (let i = 0; i < alignedBytes.length; i++) {
alignedBytes[i] = bitArrayByteAt(
bitArray.rawBuffer,
start % 8,
byteOffset + i,
);
}
// Read the float out of the aligned buffer
const view = new DataView(alignedBytes.buffer);
if (floatSize === 64) {
return view.getFloat64(0, !isBigEndian);
} else if (floatSize === 32) {
return view.getFloat32(0, !isBigEndian);
} else {
return fp16UintToNumber(view.getUint16(0, !isBigEndian));
}
}
/**
* Interprets a slice of this bit array as a signed or unsigned integer with the
* specified endianness.
*
* @param {BitArray} bitArray
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
export function bitArraySliceToInt(
bitArray,
start,
end,
isBigEndian,
isSigned,
) {
bitArrayValidateRange(bitArray, start, end);
if (start === end) {
return 0;
}
start += bitArray.bitOffset;
end += bitArray.bitOffset;
const isStartByteAligned = start % 8 === 0;
const isEndByteAligned = end % 8 === 0;
// If the slice is byte-aligned then there is no need to handle unaligned
// slices, meaning a simpler and faster implementation can be used instead
if (isStartByteAligned && isEndByteAligned) {
return intFromAlignedSlice(
bitArray,
start / 8,
end / 8,
isBigEndian,
isSigned,
);
}
const size = end - start;
const startByteIndex = Math.trunc(start / 8);
const endByteIndex = Math.trunc((end - 1) / 8);
// Handle the case of the slice being completely contained in a single byte
if (startByteIndex == endByteIndex) {
const mask = 0xff >> start % 8;
const unusedLowBitCount = (8 - (end % 8)) % 8;
let value =
(bitArray.rawBuffer[startByteIndex] & mask) >> unusedLowBitCount;
// For signed integers, if the high bit is set reinterpret as two's
// complement
if (isSigned) {
const highBit = 2 ** (size - 1);
if (value >= highBit) {
value -= highBit * 2;
}
}
return value;
}
// The integer value to be read is not aligned and crosses at least one byte
// boundary in the input array
if (size <= 53) {
return intFromUnalignedSliceUsingNumber(
bitArray.rawBuffer,
start,
end,
isBigEndian,
isSigned,
);
} else {
return intFromUnalignedSliceUsingBigInt(
bitArray.rawBuffer,
start,
end,
isBigEndian,
isSigned,
);
}
}
/**
* Joins the given segments into a new bit array, tightly packing them together.
* Each segment must be one of the following types:
*
* - A `number`: A single byte value in the range 0-255. Values outside this
* range will be wrapped.
* - A `Uint8Array`: A sequence of byte values of any length.
* - A `BitArray`: A sequence of bits of any length, which may not be byte
* aligned.
*
* The bit size of the returned bit array will be the sum of the size in bits
* of the input segments.
*
* @param {(number | Uint8Array | BitArray)[]} segments
* @returns {BitArray}
*/
export function toBitArray(segments) {
if (segments.length === 0) {
return new BitArray(new Uint8Array());
}
if (segments.length === 1) {
const segment = segments[0];
// When there is a single BitArray segment it can be returned as-is
if (segment instanceof BitArray) {
return segment;
}
// When there is a single Uint8Array segment, pass it directly to the bit
// array constructor to avoid a copy
if (segment instanceof Uint8Array) {
return new BitArray(segment);
}
return new BitArray(new Uint8Array(/** @type {number[]} */ (segments)));
}
// Count the total number of bits and check if all segments are numbers, i.e.
// single bytes
let bitSize = 0;
let areAllSegmentsNumbers = true;
for (const segment of segments) {
if (segment instanceof BitArray) {
bitSize += segment.bitSize;
areAllSegmentsNumbers = false;
} else if (segment instanceof Uint8Array) {
bitSize += segment.byteLength * 8;
areAllSegmentsNumbers = false;
} else {
bitSize += 8;
}
}
// If all segments are numbers then pass the segments array directly to the
// Uint8Array constructor
if (areAllSegmentsNumbers) {
return new BitArray(new Uint8Array(/** @type {number[]} */ (segments)));
}
// Pack the segments into a Uint8Array
const buffer = new Uint8Array(Math.trunc((bitSize + 7) / 8));
// The current write position in bits into the above array. Byte-aligned
// segments, i.e. when the cursor is a multiple of 8, are able to be processed
// faster due to being able to copy bytes directly.
let cursor = 0;
for (let segment of segments) {
const isCursorByteAligned = cursor % 8 === 0;
if (segment instanceof BitArray) {
if (isCursorByteAligned && segment.bitOffset === 0) {
buffer.set(segment.rawBuffer, cursor / 8);
cursor += segment.bitSize;
// Zero any unused bits in the last byte of the buffer. Their content is
// undefined and shouldn't be included in the output.
const trailingBitsCount = segment.bitSize % 8;
if (trailingBitsCount !== 0) {
const lastByteIndex = Math.trunc(cursor / 8);
buffer[lastByteIndex] >>= 8 - trailingBitsCount;
buffer[lastByteIndex] <<= 8 - trailingBitsCount;
}
} else {
appendUnalignedBits(
segment.rawBuffer,
segment.bitSize,
segment.bitOffset,
);
}
} else if (segment instanceof Uint8Array) {
if (isCursorByteAligned) {
buffer.set(segment, cursor / 8);
cursor += segment.byteLength * 8;
} else {
appendUnalignedBits(segment, segment.byteLength * 8, 0);
}
} else {
if (isCursorByteAligned) {
buffer[cursor / 8] = segment;
cursor += 8;
} else {
appendUnalignedBits(new Uint8Array([segment]), 8, 0);
}
}
}
function appendUnalignedBits(unalignedBits, size, offset) {
if (size === 0) {
return;
}
const byteSize = Math.trunc(size + 7 / 8);
const highBitsCount = cursor % 8;
const lowBitsCount = 8 - highBitsCount;
let byteIndex = Math.trunc(cursor / 8);
for (let i = 0; i < byteSize; i++) {
let byte = bitArrayByteAt(unalignedBits, offset, i);
// If this is a partial byte then zero out the trailing bits as their
// content is undefined and shouldn't be included in the output
if (size < 8) {
byte >>= 8 - size;
byte <<= 8 - size;
}
// Copy the high bits of the input byte to the low bits of the current
// output byte
buffer[byteIndex] |= byte >> highBitsCount;
let appendedBitsCount = size - Math.max(0, size - lowBitsCount);
size -= appendedBitsCount;
cursor += appendedBitsCount;
if (size === 0) {
break;
}
// Copy the low bits of the input byte to the high bits of the next output
// byte
buffer[++byteIndex] = byte << lowBitsCount;
appendedBitsCount = size - Math.max(0, size - highBitsCount);
size -= appendedBitsCount;
cursor += appendedBitsCount;
}
}
return new BitArray(buffer, bitSize);
}
/**
* Encodes a floating point value into a `Uint8Array`. This is used to create
* float segments that are part of bit array expressions.
*
* @param {number} value
* @param {number} size
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
export function sizedFloat(value, size, isBigEndian) {
if (size !== 16 && size !== 32 && size !== 64) {
const msg = `Sized floats must be 16-bit, 32-bit or 64-bit, got size of ${size} bits`;
throw new globalThis.Error(msg);
}
if (size === 16) {
return numberToFp16Uint(value, isBigEndian);
}
const buffer = new Uint8Array(size / 8);
const view = new DataView(buffer.buffer);
if (size == 64) {
view.setFloat64(0, value, !isBigEndian);
} else {
view.setFloat32(0, value, !isBigEndian);
}
return buffer;
}
/**
* Encodes an integer value into a `Uint8Array`, or a `BitArray` if the size in
* bits is not a multiple of 8. This is used to create integer segments used in
* bit array expressions.
*
* @param {number} value
* @param {number} size
* @param {boolean} isBigEndian
* @returns {Uint8Array | BitArray}
*/
export function sizedInt(value, size, isBigEndian) {
if (size <= 0) {
return new Uint8Array();
}
// Fast path when size is 8 bits. This relies on the rounding behavior of the
// Uint8Array constructor.
if (size === 8) {
return new Uint8Array([value]);
}
// Fast path when size is less than 8 bits: shift the value up to the high
// bits
if (size < 8) {
value <<= 8 - size;
return new BitArray(new Uint8Array([value]), size);
}
// Allocate output buffer
const buffer = new Uint8Array(Math.trunc((size + 7) / 8));
// The number of trailing bits in the final byte. Will be zero if the size is
// an exact number of bytes.
const trailingBitsCount = size % 8;
// The number of unused bits in the final byte of the buffer
const unusedBitsCount = 8 - trailingBitsCount;
// For output sizes not exceeding 32 bits the number type is used. For larger
// output sizes the BigInt type is needed.
//
// The code in each of these two paths must be kept in sync.
if (size <= 32) {
if (isBigEndian) {
let i = buffer.length - 1;
// Set the trailing bits at the end of the output buffer
if (trailingBitsCount) {
buffer[i--] = (value << unusedBitsCount) & 0xff;
value >>= trailingBitsCount;
}
for (; i >= 0; i--) {
buffer[i] = value;
value >>= 8;
}
} else {
let i = 0;
const wholeByteCount = Math.trunc(size / 8);
for (; i < wholeByteCount; i++) {
buffer[i] = value;
value >>= 8;
}
// Set the trailing bits at the end of the output buffer
if (trailingBitsCount) {
buffer[i] = value << unusedBitsCount;
}
}
} else {
const bigTrailingBitsCount = BigInt(trailingBitsCount);
const bigUnusedBitsCount = BigInt(unusedBitsCount);
let bigValue = BigInt(value);
if (isBigEndian) {
let i = buffer.length - 1;
// Set the trailing bits at the end of the output buffer
if (trailingBitsCount) {
buffer[i--] = Number(bigValue << bigUnusedBitsCount);
bigValue >>= bigTrailingBitsCount;
}
for (; i >= 0; i--) {
buffer[i] = Number(bigValue);
bigValue >>= 8n;
}
} else {
let i = 0;
const wholeByteCount = Math.trunc(size / 8);
for (; i < wholeByteCount; i++) {
buffer[i] = Number(bigValue);
bigValue >>= 8n;
}
// Set the trailing bits at the end of the output buffer
if (trailingBitsCount) {
buffer[i] = Number(bigValue << bigUnusedBitsCount);
}
}
}
// Integers that aren't a whole number of bytes are returned as a BitArray so
// their size in bits is tracked
if (trailingBitsCount) {
return new BitArray(buffer, size);
}
return buffer;
}
/**
* Reads an aligned slice of any size as an integer.
*
* @param {BitArray} bitArray
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
function intFromAlignedSlice(bitArray, start, end, isBigEndian, isSigned) {
const byteSize = end - start;
if (byteSize <= 6) {
return intFromAlignedSliceUsingNumber(
bitArray.rawBuffer,
start,
end,
isBigEndian,
isSigned,
);
} else {
return intFromAlignedSliceUsingBigInt(
bitArray.rawBuffer,
start,
end,
isBigEndian,
isSigned,
);
}
}
/**
* Reads an aligned slice up to 48 bits in size as an integer. Uses the
* JavaScript `number` type internally.
*
* @param {Uint8Array} buffer
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
function intFromAlignedSliceUsingNumber(
buffer,
start,
end,
isBigEndian,
isSigned,
) {
const byteSize = end - start;
let value = 0;
// Read bytes as an unsigned integer
if (isBigEndian) {
for (let i = start; i < end; i++) {
value *= 256;
value += buffer[i];
}
} else {
for (let i = end - 1; i >= start; i--) {
value *= 256;
value += buffer[i];
}
}
// For signed integers, if the high bit is set reinterpret as two's
// complement
if (isSigned) {
const highBit = 2 ** (byteSize * 8 - 1);
if (value >= highBit) {
value -= highBit * 2;
}
}
return value;
}
/**
* Reads an aligned slice of any size as an integer. Uses the JavaScript
* `BigInt` type internally.
*
* @param {Uint8Array} buffer
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
function intFromAlignedSliceUsingBigInt(
buffer,
start,
end,
isBigEndian,
isSigned,
) {
const byteSize = end - start;
let value = 0n;
// Read bytes as an unsigned integer value
if (isBigEndian) {
for (let i = start; i < end; i++) {
value *= 256n;
value += BigInt(buffer[i]);
}
} else {
for (let i = end - 1; i >= start; i--) {
value *= 256n;
value += BigInt(buffer[i]);
}
}
// For signed integers, if the high bit is set reinterpret as two's
// complement
if (isSigned) {
const highBit = 1n << BigInt(byteSize * 8 - 1);
if (value >= highBit) {
value -= highBit * 2n;
}
}
// Convert the result into a JS number. This may cause quantizing/error on
// values outside JavaScript's safe integer range.
return Number(value);
}
/**
* Reads an unaligned slice up to 53 bits in size as an integer. Uses the
* JavaScript `number` type internally.
*
* This function assumes that the slice crosses at least one byte boundary in
* the input.
*
* @param {Uint8Array} buffer
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
function intFromUnalignedSliceUsingNumber(
buffer,
start,
end,
isBigEndian,
isSigned,
) {
const isStartByteAligned = start % 8 === 0;
let size = end - start;
let byteIndex = Math.trunc(start / 8);
let value = 0;
if (isBigEndian) {
// Read any leading bits
if (!isStartByteAligned) {
const leadingBitsCount = 8 - (start % 8);
value = buffer[byteIndex++] & ((1 << leadingBitsCount) - 1);
size -= leadingBitsCount;
}
// Read any whole bytes
while (size >= 8) {
value *= 256;
value += buffer[byteIndex++];
size -= 8;
}
// Read any trailing bits
if (size > 0) {
value *= 2 ** size;
value += buffer[byteIndex] >> (8 - size);
}
} else {
// For little endian, if the start is aligned then whole bytes can be read
// directly out of the input array, with the trailing bits handled at the
// end
if (isStartByteAligned) {
let size = end - start;
let scale = 1;
// Read whole bytes
while (size >= 8) {
value += buffer[byteIndex++] * scale;
scale *= 256;
size -= 8;
}
// Read trailing bits
value += (buffer[byteIndex] >> (8 - size)) * scale;
} else {
// Read little endian data where the start is not byte-aligned. This is
// done by reading whole bytes that cross a byte boundary in the input
// data, then reading any trailing bits.
const highBitsCount = start % 8;
const lowBitsCount = 8 - highBitsCount;
let size = end - start;
let scale = 1;
// Extract whole bytes
while (size >= 8) {
const byte =
(buffer[byteIndex] << highBitsCount) |
(buffer[byteIndex + 1] >> lowBitsCount);
value += (byte & 0xff) * scale;
scale *= 256;
size -= 8;
byteIndex++;
}
// Read any trailing bits. These trailing bits may cross a byte boundary
// in the input buffer.
if (size > 0) {
const lowBitsUsed = size - Math.max(0, size - lowBitsCount);
let trailingByte =
(buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >>
(lowBitsCount - lowBitsUsed);
size -= lowBitsUsed;
if (size > 0) {
trailingByte *= 2 ** size;
trailingByte += buffer[byteIndex + 1] >> (8 - size);
}
value += trailingByte * scale;
}
}
}
// For signed integers, if the high bit is set reinterpret as two's
// complement
if (isSigned) {
const highBit = 2 ** (end - start - 1);
if (value >= highBit) {
value -= highBit * 2;
}
}
return value;
}
/**
* Reads an unaligned slice of any size as an integer. Uses the JavaScript
* `BigInt` type internally.
*
* This function assumes that the slice crosses at least one byte boundary in
* the input.
*
* @param {Uint8Array} buffer
* @param {number} start
* @param {number} end
* @param {boolean} isBigEndian
* @param {boolean} isSigned
* @returns {number}
*/
function intFromUnalignedSliceUsingBigInt(
buffer,
start,
end,
isBigEndian,
isSigned,
) {
const isStartByteAligned = start % 8 === 0;
let size = end - start;
let byteIndex = Math.trunc(start / 8);
let value = 0n;
if (isBigEndian) {
// Read any leading bits
if (!isStartByteAligned) {
const leadingBitsCount = 8 - (start % 8);
value = BigInt(buffer[byteIndex++] & ((1 << leadingBitsCount) - 1));
size -= leadingBitsCount;
}
// Read any whole bytes
while (size >= 8) {
value *= 256n;
value += BigInt(buffer[byteIndex++]);
size -= 8;
}
// Read any trailing bits
if (size > 0) {
value <<= BigInt(size);
value += BigInt(buffer[byteIndex] >> (8 - size));
}
} else {
// For little endian, if the start is aligned then whole bytes can be read
// directly out of the input array, with the trailing bits handled at the
// end
if (isStartByteAligned) {
let size = end - start;
let shift = 0n;
// Read whole bytes
while (size >= 8) {
value += BigInt(buffer[byteIndex++]) << shift;
shift += 8n;
size -= 8;
}
// Read trailing bits
value += BigInt(buffer[byteIndex] >> (8 - size)) << shift;
} else {
// Read little endian data where the start is not byte-aligned. This is
// done by reading whole bytes that cross a byte boundary in the input
// data, then reading any trailing bits.
const highBitsCount = start % 8;
const lowBitsCount = 8 - highBitsCount;
let size = end - start;
let shift = 0n;
// Extract whole bytes
while (size >= 8) {
const byte =
(buffer[byteIndex] << highBitsCount) |
(buffer[byteIndex + 1] >> lowBitsCount);
value += BigInt(byte & 0xff) << shift;
shift += 8n;
size -= 8;
byteIndex++;
}
// Read any trailing bits. These trailing bits may cross a byte boundary
// in the input buffer.
if (size > 0) {
const lowBitsUsed = size - Math.max(0, size - lowBitsCount);
let trailingByte =
(buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >>
(lowBitsCount - lowBitsUsed);
size -= lowBitsUsed;
if (size > 0) {
trailingByte <<= size;
trailingByte += buffer[byteIndex + 1] >> (8 - size);
}
value += BigInt(trailingByte) << shift;
}
}
}
// For signed integers, if the high bit is set reinterpret as two's
// complement
if (isSigned) {
const highBit = 2n ** BigInt(end - start - 1);
if (value >= highBit) {
value -= highBit * 2n;
}
}
// Convert the result into a JS number. This may cause quantizing/error on
// values outside JavaScript's safe integer range.
return Number(value);
}
/**
* Interprets a 16-bit unsigned integer value as a 16-bit floating point value.
*
* @param {number} intValue
* @returns {number}
*/
function fp16UintToNumber(intValue) {
const sign = intValue >= 0x8000 ? -1 : 1;
const exponent = (intValue & 0x7c00) >> 10;
const fraction = intValue & 0x03ff;
let value;
if (exponent === 0) {
value = 6.103515625e-5 * (fraction / 0x400);
} else if (exponent === 0x1f) {
value = fraction === 0 ? Infinity : NaN;
} else {
value = Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
}
return sign * value;
}
/**
* Converts a floating point number to bytes for a 16-bit floating point value.
*
* @param {number} intValue
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
function numberToFp16Uint(value, isBigEndian) {
const buffer = new Uint8Array(2);
if (isNaN(value)) {
buffer[1] = 0x7e;
} else if (value === Infinity) {
buffer[1] = 0x7c;
} else if (value === -Infinity) {
buffer[1] = 0xfc;
} else if (value === 0) {
// Both values are already zero
} else {
const sign = value < 0 ? 1 : 0;
value = Math.abs(value);
let exponent = Math.floor(Math.log2(value));
let fraction = value / Math.pow(2, exponent) - 1;
exponent += 15;
if (exponent <= 0) {
exponent = 0;
fraction = value / Math.pow(2, -14);
} else if (exponent >= 31) {
exponent = 31;
fraction = 0;
}
fraction = Math.round(fraction * 1024);
buffer[1] =
(sign << 7) | ((exponent & 0x1f) << 2) | ((fraction >> 8) & 0x03);
buffer[0] = fraction & 0xff;
}
if (isBigEndian) {
const a = buffer[0];
buffer[0] = buffer[1];
buffer[1] = a;
}
return buffer;
}
/**
* Throws an exception if the given start and end values are out of bounds for
* a bit array.
*
* @param {BitArray} bitArray
* @param {number} start
* @param {number} end
*/
function bitArrayValidateRange(bitArray, start, end) {
if (
start < 0 ||
start > bitArray.bitSize ||
end < start ||
end > bitArray.bitSize
) {
const msg =
`Invalid bit array slice: start = ${start}, end = ${end}, ` +
`bit size = ${bitArray.bitSize}`;
throw new globalThis.Error(msg);
}
}
/** @type {TextEncoder | undefined} */
let utf8Encoder;
/**
* Returns the UTF-8 bytes for a string.
*
* @param {string} string
* @returns {Uint8Array}
*/
export function stringBits(string) {
utf8Encoder ??= new TextEncoder();
return utf8Encoder.encode(string);
}
/**
* Returns the UTF-8 bytes for a single UTF codepoint.
*
* @param {UtfCodepoint} codepoint
* @returns {Uint8Array}
*/
export function codepointBits(codepoint) {
return stringBits(String.fromCodePoint(codepoint.value));
}
/**
* Returns the UTF-16 bytes for a string.
*
* @param {string} string
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
export function stringToUtf16(string, isBigEndian) {
const buffer = new ArrayBuffer(string.length * 2);
const bufferView = new DataView(buffer);
for (let i = 0; i < string.length; i++) {
bufferView.setUint16(i * 2, string.charCodeAt(i), !isBigEndian);
}
return new Uint8Array(buffer);
}
/**
* Returns the UTF-16 bytes for a single UTF codepoint.
*
* @param {UtfCodepoint} codepoint
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
export function codepointToUtf16(codepoint, isBigEndian) {
return stringToUtf16(String.fromCodePoint(codepoint.value), isBigEndian);
}
/**
* Returns the UTF-32 bytes for a string.
*
* @param {string} string
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
export function stringToUtf32(string, isBigEndian) {
const buffer = new ArrayBuffer(string.length * 4);
const bufferView = new DataView(buffer);
let length = 0;
for (let i = 0; i < string.length; i++) {
const codepoint = string.codePointAt(i);
bufferView.setUint32(length * 4, codepoint, !isBigEndian);
length++;
if (codepoint > 0xffff) {
i++;
}
}
return new Uint8Array(buffer.slice(0, length * 4));
}
/**
* Returns the UTF-32 bytes for a single UTF codepoint.
*
* @param {UtfCodepoint} codepoint
* @param {boolean} isBigEndian
* @returns {Uint8Array}
*/
export function codepointToUtf32(codepoint, isBigEndian) {
return stringToUtf32(String.fromCodePoint(codepoint.value), isBigEndian);
}
export class Result extends CustomType {
static isResult(data) {
return data instanceof Result;
}
}
export class Ok extends Result {
constructor(value) {
super();
this[0] = value;
}
isOk() {
return true;
}
}
export const Result$Ok = (value) => new Ok(value);
export const Result$isOk = (value) => value instanceof Ok;
export const Result$Ok$0 = (value) => value[0];
export class Error extends Result {
constructor(detail) {
super();
this[0] = detail;
}
isOk() {
return false;
}
}
export const Result$Error = (detail) => new Error(detail);
export const Result$isError = (value) => value instanceof Error;
export const Result$Error$0 = (value) => value[0];
export function isEqual(x, y) {
let values = [x, y];
while (values.length) {
let a = values.pop();
let b = values.pop();
if (a === b) continue;
if (!isObject(a) || !isObject(b)) return false;
let unequal =
!structurallyCompatibleObjects(a, b) ||
unequalDates(a, b) ||
unequalBuffers(a, b) ||
unequalArrays(a, b) ||
unequalMaps(a, b) ||
unequalSets(a, b) ||
unequalRegExps(a, b);
if (unequal) return false;
const proto = Object.getPrototypeOf(a);
if (proto !== null && typeof proto.equals === "function") {
try {
if (a.equals(b)) continue;
else return false;
} catch {}
}
let [keys, get] = getters(a);
const ka = keys(a);
const kb = keys(b);
if (ka.length !== kb.length) return false;
for (let k of ka) {
values.push(get(a, k), get(b, k));
}
}
return true;
}
function getters(object) {
if (object instanceof Map) {
return [(x) => x.keys(), (x, y) => x.get(y)];
} else {
let extra = object instanceof globalThis.Error ? ["message"] : [];
return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]];
}
}
function unequalDates(a, b) {
return a instanceof Date && (a > b || a < b);
}
function unequalBuffers(a, b) {
return (
!(a instanceof BitArray) &&
a.buffer instanceof ArrayBuffer &&
a.BYTES_PER_ELEMENT &&
!(a.byteLength === b.byteLength && a.every((n, i) => n === b[i]))
);
}
function unequalArrays(a, b) {
return Array.isArray(a) && a.length !== b.length;
}
function unequalMaps(a, b) {
return a instanceof Map && a.size !== b.size;
}
function unequalSets(a, b) {
return (
a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e)))
);
}
function unequalRegExps(a, b) {
return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags);
}
function isObject(a) {
return typeof a === "object" && a !== null;
}
function structurallyCompatibleObjects(a, b) {
if (typeof a !== "object" && typeof b !== "object" && (!a || !b))
return false;
let nonstructural = [Promise, WeakSet, WeakMap, Function];
if (nonstructural.some((c) => a instanceof c)) return false;
return a.constructor === b.constructor;
}
export function remainderInt(a, b) {
if (b === 0) {
return 0;
} else {
return a % b;
}
}
export function divideInt(a, b) {
return Math.trunc(divideFloat(a, b));
}
export function divideFloat(a, b) {
if (b === 0) {
return 0;
} else {
return a / b;
}
}
export function makeError(variant, file, module, line, fn, message, extra) {
let error = new globalThis.Error(message);
error.gleam_error = variant;
error.file = file;
error.module = module;
error.line = line;
error.function = fn;
// TODO: Remove this with Gleam v2.0.0
error.fn = fn;
for (let k in extra) error[k] = extra[k];
return error;
}