1575 lines
39 KiB
JavaScript
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;
|
|
}
|