From: Lady Date: Tue, 27 Jun 2023 01:07:19 +0000 (-0700) Subject: Add buffer getters and setters to binary.js X-Git-Tag: 0.4.0 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/0.4.0?ds=inline;hp=0.3.1 Add buffer getters and setters to binary.js These are rough equivalents to the `DataView` methods, but can be called with any kind of array buffer or array buffer view. Internally, they (weakly) remember a `DataView` instance for each buffer and use that for accessing values (to ensure correct endianness behaviour). --- diff --git a/binary.js b/binary.js index 5ee2bda..c149daf 100644 --- a/binary.js +++ b/binary.js @@ -17,23 +17,24 @@ import { stringFromCodeUnits, stringReplace, } from "./string.js"; +import { ITERATOR } from "./value.js"; const Buffer = ArrayBuffer; const View = DataView; const TypedArray = Object.getPrototypeOf(Uint8Array); const { prototype: arrayPrototype } = Array; const { prototype: bufferPrototype } = Buffer; -const { iterator: iteratorSymbol } = Symbol; +const { prototype: sharedBufferPrototype } = SharedArrayBuffer; const { prototype: rePrototype } = RegExp; const { prototype: typedArrayPrototype } = TypedArray; const { prototype: viewPrototype } = View; -const { [iteratorSymbol]: arrayIterator } = arrayPrototype; +const { [ITERATOR]: arrayIterator } = arrayPrototype; const { next: arrayIteratorNext, -} = Object.getPrototypeOf([][iteratorSymbol]()); +} = Object.getPrototypeOf([][ITERATOR]()); const argumentIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind( arrayIteratorNext, @@ -44,7 +45,7 @@ const argumentIterablePrototype = { }, }; const binaryCodeUnitIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind( arrayIteratorNext, @@ -55,31 +56,61 @@ const binaryCodeUnitIterablePrototype = { }, }; +const { exec: reExec } = rePrototype; +const { slice: bufferSlice } = bufferPrototype; const getBufferByteLength = Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get; +const { slice: sharedBufferSlice } = sharedBufferPrototype; +const getSharedBufferByteLength = + Object.getOwnPropertyDescriptor(sharedBufferPrototype, "byteLength") + .get; const getTypedArrayBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get; +const getTypedArrayByteLength = + Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength") + .get; +const getTypedArrayByteOffset = + Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset") + .get; const getViewBuffer = Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get; -const { exec: reExec } = rePrototype; +const getViewByteLength = + Object.getOwnPropertyDescriptor(viewPrototype, "byteLength").get; +const getViewByteOffset = + Object.getOwnPropertyDescriptor(viewPrototype, "byteOffset").get; const { + getBigInt64: viewGetInt64, + getBigUint64: viewGetUint64, + getFloat32: viewGetFloat32, + getFloat64: viewGetFloat64, + getInt8: viewGetInt8, + getInt16: viewGetInt16, + getInt32: viewGetInt32, getUint8: viewGetUint8, + getUint16: viewGetUint16, + getUint32: viewGetUint32, + setFloat32: viewSetFloat32, + setFloat64: viewSetFloat64, setUint8: viewSetUint8, setUint16: viewSetUint16, + setUint32: viewSetUint32, + setBigUint64: viewSetUint64, } = viewPrototype; /** * Returns an ArrayBuffer for encoding generated from the provided * arguments. */ -const bufferFromArgs = ($, $s) => - $ instanceof Buffer - ? $ - : $ instanceof View - ? call(getViewBuffer, $, []) - : $ instanceof TypedArray - ? call(getTypedArrayBuffer, $, []) - : ((string) => +const bufferFromArgs = ($, $s) => { + try { + // Try just getting the array buffer associated with the first + // argument and returning it if possible. + return toArrayBuffer($); + } catch { + // There is no array buffer associated with the first argument. + // + // Construct a string and convert it to an array buffer instead. + return ((string) => call( getViewBuffer, reduce( @@ -94,7 +125,7 @@ const bufferFromArgs = ($, $s) => ), [], ))( - typeof $ == "string" + typeof $ === "string" ? $ : hasOwnProperty($, "raw") ? rawString( @@ -105,6 +136,8 @@ const bufferFromArgs = ($, $s) => ) : `${$}`, ); + } +}; /** * Returns the result of decoding the provided base16 string into an @@ -125,16 +158,19 @@ const decodeBase16 = (source) => { ? code - 87 : -1; if (result < 0) { + // The source contains an invalid character. throw new RangeError( `Piscēs: Invalid character in Base64: ${ucsCharacter}.`, ); } else { + // The source contains a valid character with a recognized + // mapping. return result; } }, ); const { length } = u4s; - if (length % 2 == 1) { + if (length % 2 === 1) { // The length is such that an entire letter would be dropped during // a forgiving decode. throw new RangeError( @@ -144,6 +180,8 @@ const decodeBase16 = (source) => { // Every letter contributes at least some bits to the result. const dataView = new View(new Buffer(floor(length / 2))); for (let index = 0; index < length - 1;) { + // Iterate over the characters and assign their bits to the + // buffer. call(viewSetUint8, dataView, [ floor(index / 2), (u4s[index] << 4) | u4s[++index, index++], @@ -167,7 +205,7 @@ const decodeBase32 = (source, wrmg) => { const u5s = map( wrmg ? stringReplace(source, /-/gu, "") - : source.length % 8 == 0 + : source.length % 8 === 0 ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "") : source, (ucsCharacter) => { @@ -177,15 +215,15 @@ const decodeBase32 = (source, wrmg) => { ? code - 48 : code >= 0x41 && code <= 0x48 ? code - 55 - : code == 0x49 + : code === 0x49 ? 1 // I : code >= 0x4A && code <= 0x4B ? code - 56 - : code == 0x4C + : code === 0x4C ? 1 // L : code >= 0x4D && code <= 0x4E ? code - 57 - : code == 0x4F + : code === 0x4F ? 0 // O : code >= 0x50 && code <= 0x54 ? code - 58 @@ -194,15 +232,15 @@ const decodeBase32 = (source, wrmg) => { ? code - 59 : code >= 0x61 && code <= 0x68 ? code - 87 - : code == 0x69 + : code === 0x69 ? 1 // i : code >= 0x6A && code <= 0x6B ? code - 88 - : code == 0x6C + : code === 0x6C ? 1 // l : code >= 0x6D && code <= 0x6E ? code - 89 - : code == 0x6F + : code === 0x6F ? 0 // o : code >= 0x70 && code <= 0x74 ? code - 90 @@ -218,17 +256,20 @@ const decodeBase32 = (source, wrmg) => { ? code - 24 // digits 2–7 map to 26–31 : -1; if (result < 0) { + // The source contains an invalid character. throw new RangeError( `Piscēs: Invalid character in Base32: ${ucsCharacter}.`, ); } else { + // The source contains a valid character with a recognized + // mapping. return result; } }, ); const { length } = u5s; const lengthMod8 = length % 8; - if (lengthMod8 == 1 || lengthMod8 == 3 || lengthMod8 == 6) { + if (lengthMod8 === 1 || lengthMod8 === 3 || lengthMod8 === 6) { // The length is such that an entire letter would be dropped during // a forgiving decode. throw new RangeError( @@ -238,37 +279,26 @@ const decodeBase32 = (source, wrmg) => { // Every letter contributes at least some bits to the result. const dataView = new View(new Buffer(floor(length * 5 / 8))); for (let index = 0; index < length - 1;) { + // Iterate over the characters and assign their bits to the + // buffer. + // // The final index is not handled; if the string is not divisible // by 8, some bits might be dropped. This matches the “forgiving // decode” behaviour specified by WhatW·G for base64. const dataIndex = ceil(index * 5 / 8); const remainder = index % 8; - if (remainder == 0) { - call(viewSetUint8, dataView, [ - dataIndex, - u5s[index] << 3 | u5s[++index] >> 2, - ]); - } else if (remainder == 1) { - call(viewSetUint8, dataView, [ - dataIndex, - u5s[index] << 6 | u5s[++index] << 1 | u5s[++index] >> 4, - ]); - } else if (remainder == 3) { - call(viewSetUint8, dataView, [ - dataIndex, - u5s[index] << 4 | u5s[++index] >> 1, - ]); - } else if (remainder == 4) { - call(viewSetUint8, dataView, [ - dataIndex, - u5s[index] << 7 | u5s[++index] << 2 | u5s[++index] >> 3, - ]); - } else { // remainder == 6 - call(viewSetUint8, dataView, [ - dataIndex, - u5s[index] << 5 | u5s[++index, index++], - ]); - } + call(viewSetUint8, dataView, [ + dataIndex, + remainder === 0 + ? u5s[index] << 3 | u5s[++index] >> 2 + : remainder === 1 + ? u5s[index] << 6 | u5s[++index] << 1 | u5s[++index] >> 4 + : remainder === 3 + ? u5s[index] << 4 | u5s[++index] >> 1 + : remainder === 4 + ? u5s[index] << 7 | u5s[++index] << 2 | u5s[++index] >> 3 + : u5s[index] << 5 | u5s[++index, index++], // remainder === 6 + ]); } return call(getViewBuffer, dataView, []); } @@ -282,7 +312,7 @@ const decodeBase32 = (source, wrmg) => { */ const decodeBase64 = (source, safe = false) => { const u6s = map( - source.length % 4 == 0 + source.length % 4 === 0 ? stringReplace(source, /={1,2}$/u, "") : source, (ucsCharacter) => { @@ -293,22 +323,25 @@ const decodeBase64 = (source, safe = false) => { ? code - 71 : code >= 0x30 && code <= 0x39 ? code + 4 - : code == (safe ? 0x2D : 0x2B) + : code === (safe ? 0x2D : 0x2B) ? 62 - : code == (safe ? 0x5F : 0x2F) + : code === (safe ? 0x5F : 0x2F) ? 63 : -1; if (result < 0) { + // The source contains an invalid character. throw new RangeError( `Piscēs: Invalid character in Base64: ${ucsCharacter}.`, ); } else { + // The source contains a valid character with a recognized + // mapping. return result; } }, ); const { length } = u6s; - if (length % 4 == 1) { + if (length % 4 === 1) { // The length is such that an entire letter would be dropped during // a forgiving decode. throw new RangeError( @@ -318,27 +351,22 @@ const decodeBase64 = (source, safe = false) => { // Every letter contributes at least some bits to the result. const dataView = new View(new Buffer(floor(length * 3 / 4))); for (let index = 0; index < length - 1;) { + // Iterate over the characters and assign their bits to the + // buffer. + // // The final index is not handled; if the string is not divisible // by 4, some bits might be dropped. This matches the “forgiving // decode” behaviour specified by WhatW·G for base64. const dataIndex = ceil(index * 3 / 4); const remainder = index % 4; - if (remainder == 0) { - call(viewSetUint8, dataView, [ - dataIndex, - u6s[index] << 2 | u6s[++index] >> 4, - ]); - } else if (remainder == 1) { - call(viewSetUint8, dataView, [ - dataIndex, - u6s[index] << 4 | u6s[++index] >> 2, - ]); - } else { // remainder == 2 - call(viewSetUint8, dataView, [ - dataIndex, - u6s[index] << 6 | u6s[++index, index++], - ]); - } + call(viewSetUint8, dataView, [ + dataIndex, + remainder === 0 + ? u6s[index] << 2 | u6s[++index] >> 4 + : remainder === 1 + ? u6s[index] << 4 | u6s[++index] >> 2 + : u6s[index] << 6 | u6s[++index, index++], // remainder === 2 + ]); } return call(getViewBuffer, dataView, []); } @@ -362,17 +390,23 @@ const encodeBase16 = (buffer) => { 0x3D, ); for (let index = 0; index < byteLength;) { + // Iterate over the bytes and generate code units for them. const codeUnitIndex = index * 2; const datum = call(viewGetUint8, dataView, [index++]); const u4s = [datum >> 4, datum & 0xF]; for (let u4i = 0; u4i < 2; ++u4i) { + // Handle the high four bits, then the low four bits. const u4 = u4s[u4i]; const result = u4 < 10 ? u4 + 48 : u4 < 16 ? u4 + 55 : -1; if (result < 0) { + // No mapping exists for these four bits. + // + // ※ This shouldn’t be possible! throw new RangeError( `Piscēs: Unexpected Base16 value: ${u4}.`, ); } else { + // A mapping exists for the bits. resultingCodeUnits[codeUnitIndex + u4i] = result; } } @@ -404,6 +438,7 @@ const encodeBase32 = (buffer, wrmg = false) => { fillByte, ); for (let index = 0; index < byteLength;) { + // Iterate over the bytes and generate code units for them. const codeUnitIndex = ceil(index * 8 / 5); const currentIndex = codeUnitIndex + +( 0b01011 & 1 << index % 5 && @@ -416,21 +451,21 @@ const encodeBase32 = (buffer, wrmg = false) => { // digits 1, 3, 4 & 6 span multiple bytes ? call(viewGetUint8, dataView, [index]) : 0; - const u5 = remainder == 0 + const u5 = remainder === 0 ? currentByte >> 3 - : remainder == 1 + : remainder === 1 ? (currentByte & 0b00000111) << 2 | nextByte >> 6 - : remainder == 2 + : remainder === 2 ? (currentByte & 0b00111111) >> 1 - : remainder == 3 + : remainder === 3 ? (currentByte & 0b00000001) << 4 | nextByte >> 4 - : remainder == 4 + : remainder === 4 ? (currentByte & 0b00001111) << 1 | nextByte >> 7 - : remainder == 5 + : remainder === 5 ? (currentByte & 0b01111111) >> 2 - : remainder == 6 + : remainder === 6 ? (currentByte & 0b00000011) << 3 | nextByte >> 5 - : (++index, currentByte & 0b00011111); // remainder == 7 + : (++index, currentByte & 0b00011111); // remainder === 7 const result = wrmg ? u5 < 10 ? u5 + 48 : u5 < 18 ? u5 + 55 @@ -453,8 +488,12 @@ const encodeBase32 = (buffer, wrmg = false) => { ? u5 + 24 : -1; if (result < 0) { + // No mapping exists for these five bits. + // + // ※ This shouldn’t be possible! throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`); } else { + // A mapping exists for the bits. resultingCodeUnits[currentIndex] = result; } } @@ -485,9 +524,10 @@ const encodeBase64 = (buffer, safe = false) => { 0x3D, ); for (let index = 0; index < byteLength;) { + // Iterate over the bytes and generate code units for them. const codeUnitIndex = ceil(index * 4 / 3); const currentIndex = codeUnitIndex + +( - index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D + index % 3 === 0 && resultingCodeUnits[codeUnitIndex] != 0x3D ); // every third byte handles two letters; this is for the second const remainder = currentIndex % 4; const currentByte = call(viewGetUint8, dataView, [index]); @@ -495,13 +535,13 @@ const encodeBase64 = (buffer, safe = false) => { // digits 1 & 2 span multiple bytes ? call(viewGetUint8, dataView, [index]) : 0; - const u6 = remainder == 0 + const u6 = remainder === 0 ? currentByte >> 2 - : remainder == 1 + : remainder === 1 ? (currentByte & 0b00000011) << 4 | nextByte >> 4 - : remainder == 2 + : remainder === 2 ? (currentByte & 0b00001111) << 2 | nextByte >> 6 - : (++index, currentByte & 0b00111111); // remainder == 3 + : (++index, currentByte & 0b00111111); // remainder === 3 const result = u6 < 26 ? u6 + 65 : u6 < 52 @@ -514,8 +554,12 @@ const encodeBase64 = (buffer, safe = false) => { ? (safe ? 0x5F : 0x2F) : -1; if (result < 0) { + // No mapping exists for these six bits. + // + // ※ This shouldn’t be possible! throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`); } else { + // A mapping exists for the bits. resultingCodeUnits[currentIndex] = result; } } @@ -530,7 +574,7 @@ const encodeBase64 = (buffer, safe = false) => { */ const sourceFromArgs = ($, $s) => stringReplace( - typeof $ == "string" ? $ : hasOwnProperty($, "raw") + typeof $ === "string" ? $ : hasOwnProperty($, "raw") ? rawString( $, ...objectCreate(argumentIterablePrototype, { @@ -542,6 +586,26 @@ const sourceFromArgs = ($, $s) => "", ); +/** + * Returns a slice of the provided value according to the algorithm of + * `ArrayBuffer::slice` (or `SharedArrayBuffer::slice`). + * + * ☡ This function throws if the provided value is not an array buffer. + */ +export const arrayBufferSlice = ($, start, end, ...args) => + call( + isSharedArrayBuffer($) ? sharedBufferSlice : bufferSlice, + $, + [ + start, + end, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ], + ); + /** * Returns an ArrayBuffer generated from the provided base16 string. * @@ -631,6 +695,548 @@ export const filenameSafeBase64Binary = ($, ...$s) => export const filenameSafeBase64String = ($, ...$s) => encodeBase64(bufferFromArgs($, $s), true); +export const { + /** + * Returns the signed 8‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt8`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get8BitSignedIntegralItem, + + /** + * Returns the unsigned 8‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint8`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get8BitUnsignedIntegralItem, + + /** + * Returns the signed 16‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt16`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get16BitSignedIntegralItem, + + /** + * Returns the unsigned 16‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint16`, but works on all + * array buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get16BitUnsignedIntegralItem, + + /** + * Returns the 32‐bit floating point value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getFloat32`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitFloatingPointItem, + + /** + * Returns the signed 32‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt32`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitSignedIntegralItem, + + /** + * Returns the unsigned 32‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint32`, but works on all + * array buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitUnsignedIntegralItem, + + /** + * Returns the 64‐bit floating point value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getFloat64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitFloatingPointItem, + + /** + * Returns the signed 64‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getBigInt64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitSignedIntegralItem, + + /** + * Returns the unsigned 64‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getBigUint64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitUnsignedIntegralItem, + + /** + * Sets the 8‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt8`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set8BitIntegralItem, + + /** + * Sets the 16‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt16`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set16BitIntegralItem, + + /** + * Sets the 32‐bit floating point value in the provided array buffer + * or array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setFloat32`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set32BitFloatingPointItem, + + /** + * Sets the 32‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt32`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set32BitIntegralItem, + + /** + * Sets the 64‐bit floating point value in the provided array buffer + * or array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setFloat64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set64BitFloatingPointItem, + + /** + * Sets the 64‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt32`, but works on all array + * buffers and array buffer views. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array, or if the third argument is not + * a big·int. + */ + set64BitIntegralItem, +} = (() => { + const makeBigInt = BigInt; + const { asUintN } = BigInt; + const makeNumber = Number; + + const viewMap = new WeakMap(); + const view = ($) => { + const buffer = toArrayBuffer($); + if (viewMap.has(buffer)) { + // A view has already been allocated for this buffer; use it. + return viewMap.get(buffer); + } else { + // No view has been created for this buffer yet. + const result = new View(buffer); + viewMap.set(buffer, result); + return result; + } + }; + + return { + get8BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt8, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get8BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint8, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get16BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt16, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get16BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint16, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get32BitFloatingPointItem: ($, byteOffset, ...args) => + call(viewGetFloat32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get32BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get32BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get64BitFloatingPointItem: ($, byteOffset, ...args) => + call(viewGetFloat64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get64BitSignedIntegralItem: ($, byteOffset, ...args) => + call(viewGetInt64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get64BitUnsignedIntegralItem: ($, byteOffset, ...args) => + call(viewGetUint64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set8BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint8, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set16BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint16, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set32BitFloatingPointItem: ($, byteOffset, value, ...args) => + call(viewSetFloat32, view($), [ + getByteOffset($) + byteOffset, + value, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set32BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint32, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set64BitFloatingPointItem: ($, byteOffset, value, ...args) => + call(viewSetFloat64, view($), [ + getByteOffset($) + byteOffset, + value, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set64BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint64, view($), [ + getByteOffset($) + byteOffset, + asUintN(64, value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + }; +})(); + +/** + * Returns the byte length for the provided array buffer or array + * buffer view. + * + * ☡ This function throws if the provided value is not an array buffer, + * data view, or typed array. + */ +export const getByteLength = ($) => { + try { + // Attempt to get the byte length from the provided value as an + // `ArrayBuffer`. + return call(getBufferByteLength, $, []); + } catch { + // The provided value is not an `ArrayBuffer`. + /* do nothing */ + } + try { + // Attempt to get the byte length from the provided value as a + // `SharedArrayBuffer`. + return call(getSharedBufferByteLength, $, []); + } catch { + // The provided value is not a `SharedArrayBuffer`. + /* do nothing */ + } + try { + // Attempt to get the byte length from the provided value as a + // data view. + return call(getViewByteLength, $, []); + } catch { + // The provided value is not a data view. + /* do nothing */ + } + try { + // Attempt to get the byte length from the provided value as a + // typed array. + return call(getTypedArrayByteLength, $, []); + } catch { + // The provided value is not a typed array. + /* do nothing */ + } + throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`); +}; + +/** + * Returns the byte offset for the provided array buffer or array + * buffer view. + * + * ※ This function always returns `0` for array buffers. + * + * ☡ This function throws if the provided value is not an array buffer, + * data view, or typed array. + */ +export const getByteOffset = ($) => { + if (isArrayBuffer($)) { + // The provided value is an array buffer. + return 0; + } else { + try { + // Attempt to get the byte offset from the provided value as a + // data view. + return call(getViewByteOffset, $, []); + } catch { + // The provided value is not a data view. + /* do nothing */ + } + try { + // Attempt to get the byte offset from the provided value as a + // typed array. + return call(getTypedArrayByteOffset, $, []); + } catch { + // The provided value is not a typed array. + /* do nothing */ + } + throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`); + } +}; + +/** + * Returns whether the provided value is a view on an underlying array + * buffer. + * + * ※ This function returns true for typed arrays and data views. + */ +export const { isView: isArrayBufferView } = Buffer; + +/** + * Returns whether the provided value is an array buffer. + * + * ※ This function returns true for both `ArrayBuffer`s and + * `SharedArrayBuffer`s. + */ +export const isArrayBuffer = ($) => { + try { + // Try to see if the provided argument has array buffer internal + // slots and return true if so. + return call(getBufferByteLength, $, []), true; + } catch { + // The provided argument does not have array buffer internal slots. + /* do nothing */ + } + try { + // Try to see if the provided argument has array buffer internal + // slots and return true if so. + return call(getSharedBufferByteLength, $, []), true; + } catch { + // The provided argument does not have array buffer internal slots. + /* do nothing */ + } + return false; +}; + /** * Returns whether the provided value is a base16 string. * @@ -639,11 +1245,13 @@ export const filenameSafeBase64String = ($, ...$s) => */ export const isBase16 = ($) => { if (typeof $ !== "string") { + // The provided value is not a string. return false; } else { + // The provided value is a string. const source = stringReplace($, /[\t\n\f\r ]+/gu, ""); - return source.length % 2 != 1 && - call(reExec, /[^0-9A-F]/iu, [source]) == null; + return source.length % 2 !== 1 && + call(reExec, /[^0-9A-F]/iu, [source]) === null; } }; @@ -655,14 +1263,16 @@ export const isBase16 = ($) => { */ export const isBase32 = ($) => { if (typeof $ !== "string") { + // The provided value is not a string. return false; } else { + // The provided value is a string. const source = stringReplace($, /[\t\n\f\r ]+/gu, ""); - const trimmed = source.length % 8 == 0 + const trimmed = source.length % 8 === 0 ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "") : source; - return trimmed.length % 8 != 1 && - call(reExec, /[^2-7A-Z/]/iu, [trimmed]) == null; + return trimmed.length % 8 !== 1 && + call(reExec, /[^2-7A-Z/]/iu, [trimmed]) === null; } }; @@ -674,14 +1284,16 @@ export const isBase32 = ($) => { */ export const isBase64 = ($) => { if (typeof $ !== "string") { + // The provided value is not a string. return false; } else { + // The provided value is a string. const source = stringReplace($, /[\t\n\f\r ]+/gu, ""); - const trimmed = source.length % 4 == 0 + const trimmed = source.length % 4 === 0 ? stringReplace(source, /={1,2}$/u, "") : source; - return trimmed.length % 4 != 1 && - call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null; + return trimmed.length % 4 !== 1 && + call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) === null; } }; @@ -693,14 +1305,40 @@ export const isBase64 = ($) => { */ export const isFilenameSafeBase64 = ($) => { if (typeof $ !== "string") { + // The provided value is not a string. return false; } else { + // The provided value is a string. const source = stringReplace($, /[\t\n\f\r ]+/gu, ""); - const trimmed = source.length % 4 == 0 + const trimmed = source.length % 4 === 0 ? stringReplace(source, /={1,2}$/u, "") : source; - return trimmed.length % 4 != 1 && - call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null; + return trimmed.length % 4 !== 1 && + call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) === null; + } +}; + +/** Returns whether the provided value is a shared array buffer. */ +export const isSharedArrayBuffer = ($) => { + try { + // Try to see if the provided argument has shared array buffer + // internal slots and return true if so. + return call(getSharedBufferByteLength, $, []), true; + } catch { + // The provided argument does not have data view internal slots. + return false; + } +}; + +/** Returns whether the provided value is a typed array. */ +export const isTypedArray = ($) => { + try { + // Try to see if the provided argument has typed array internal + // slots and return true if so. + return call(getTypedArrayBuffer, $, []), true; + } catch { + // The provided argument does not have typed array internal slots. + return false; } }; @@ -713,12 +1351,42 @@ export const isFilenameSafeBase64 = ($) => { */ export const isWRMGBase32 = ($) => { if (typeof $ !== "string") { + // The provided value is not a string. return false; } else { + // The provided value is a string. const source = stringReplace($, /[\t\n\f\r ]+/gu, ""); const trimmed = stringReplace(source, /-/gu, ""); - return trimmed.length % 8 != 1 && - call(reExec, /[^0-9A-TV-Z]/iu, [trimmed]) == null; + return trimmed.length % 8 !== 1 && + call(reExec, /[^0-9A-TV-Z]/iu, [trimmed]) === null; + } +}; + +/** + * Returns the array buffer associated with the provided object. + * + * ☡ This function throws if the provided object is not a data view or + * typed array. + */ +export const toArrayBuffer = ($) => { + if (isArrayBuffer($)) { + // The provided argument has array buffer internal slots. + return $; + } else { + // The provided argument does not have array buffer internal slots. + try { + // The provided argument has typed array internal slots. + return call(getTypedArrayBuffer, $, []); + } catch { + /* do nothing */ + } + try { + // The provided argument has data view internal slots. + return call(getViewBuffer, $, []); + } catch { + /* do nothing */ + } + throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`); } }; diff --git a/binary.test.js b/binary.test.js old mode 100755 new mode 100644 index 82fa4e0..98f441a --- a/binary.test.js +++ b/binary.test.js @@ -16,6 +16,7 @@ import { it, } from "./dev-deps.js"; import { + arrayBufferSlice, base16Binary, base16String, base32Binary, @@ -24,11 +25,32 @@ import { base64String, filenameSafeBase64Binary, filenameSafeBase64String, + get16BitSignedIntegralItem, + get16BitUnsignedIntegralItem, + get32BitFloatingPointItem, + get32BitSignedIntegralItem, + get32BitUnsignedIntegralItem, + get64BitFloatingPointItem, + get64BitSignedIntegralItem, + get64BitUnsignedIntegralItem, + get8BitSignedIntegralItem, + get8BitUnsignedIntegralItem, + isArrayBuffer, + isArrayBufferView, isBase16, isBase32, isBase64, isFilenameSafeBase64, + isSharedArrayBuffer, + isTypedArray, isWRMGBase32, + set16BitIntegralItem, + set32BitFloatingPointItem, + set32BitIntegralItem, + set64BitFloatingPointItem, + set64BitIntegralItem, + set8BitIntegralItem, + toArrayBuffer, wrmgBase32Binary, wrmgBase32String, } from "./binary.js"; @@ -137,6 +159,45 @@ const data = new Map([ }], ]); +describe("arrayBufferSlice", () => { + it("[[Call]] slices an `ArrayBuffer`", () => { + const baseBuffer = Uint8Array.from([2, 3, 1, 9, 8, 5]).buffer; + assertEquals( + new Uint8Array(arrayBufferSlice(baseBuffer, 1, 4)), + Uint8Array.from([3, 1, 9]), + ); + }); + + it("[[Call]] slices an `SharedArrayBuffer`", () => { + const baseBuffer = new SharedArrayBuffer(6); + new Uint8Array(baseBuffer).set([2, 3, 1, 9, 8, 5], 0); + assertEquals( + new Uint8Array(arrayBufferSlice(baseBuffer, 1, 4)), + Uint8Array.from([3, 1, 9]), + ); + }); + + it("[[Call]] throws for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + new DataView(new ArrayBuffer()), + new Uint8Array(), + ].forEach((value) => + assertThrows(() => arrayBufferSlice(value, 0, 0)) + ); + }); +}); + describe("base16Binary", () => { it("[[Call]] returns the correct data", () => { assertEquals( @@ -578,11 +639,266 @@ describe("filenameSafeBase64String", () => { }); }); +describe("get8BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, -1, 0]).buffer; + assertStrictEquals(get8BitSignedIntegralItem(buffer, 1), -1n); + assertStrictEquals( + get8BitSignedIntegralItem(new DataView(buffer), 1), + -1n, + ); + assertStrictEquals( + get8BitSignedIntegralItem(new Uint8Array(buffer), 1), + -1n, + ); + }); +}); + +describe("get8BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, -1, 0]).buffer; + assertStrictEquals(get8BitUnsignedIntegralItem(buffer, 1), 255n); + assertStrictEquals( + get8BitUnsignedIntegralItem(new DataView(buffer), 1), + 255n, + ); + assertStrictEquals( + get8BitUnsignedIntegralItem(new Int8Array(buffer), 1), + 255n, + ); + }); +}); + +describe("get16BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, 0, -1, -1, 0, 0]).buffer; + assertStrictEquals(get16BitSignedIntegralItem(buffer, 2), -1n); + assertStrictEquals( + get16BitSignedIntegralItem(new DataView(buffer), 2), + -1n, + ); + assertStrictEquals( + get16BitSignedIntegralItem(new Uint16Array(buffer), 2), + -1n, + ); + }); +}); + +describe("get16BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, 0, -1, -1, 0, 0]).buffer; + assertStrictEquals( + get16BitUnsignedIntegralItem(buffer, 2), + (1n << 16n) - 1n, + ); + assertStrictEquals( + get16BitUnsignedIntegralItem(new DataView(buffer), 2), + (1n << 16n) - 1n, + ); + assertStrictEquals( + get16BitUnsignedIntegralItem(new Int16Array(buffer), 2), + (1n << 16n) - 1n, + ); + }); +}); + +describe("get32BitFloatingPointItem", () => { + it("[[Call]] gets the item", () => { + const buffer = new ArrayBuffer(12); + const view = new DataView(buffer); + view.setFloat32(0, NaN); + view.setFloat32(4, -Infinity); + view.setFloat32(8, -0); + assertStrictEquals(get32BitFloatingPointItem(buffer, 0), NaN); + assertStrictEquals( + get32BitFloatingPointItem(buffer, 4), + -Infinity, + ); + assertStrictEquals(get32BitFloatingPointItem(buffer, 8), -0); + assertStrictEquals( + get32BitFloatingPointItem(new DataView(buffer), 4), + -Infinity, + ); + assertStrictEquals( + get32BitFloatingPointItem(new Uint32Array(buffer), 4), + -Infinity, + ); + }); +}); + +describe("get32BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0], + ).buffer; + assertStrictEquals(get32BitSignedIntegralItem(buffer, 4), -1n); + assertStrictEquals( + get32BitSignedIntegralItem(new DataView(buffer), 4), + -1n, + ); + assertStrictEquals( + get32BitSignedIntegralItem(new Uint32Array(buffer), 4), + -1n, + ); + }); +}); + +describe("get32BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0], + ).buffer; + assertStrictEquals( + get32BitUnsignedIntegralItem(buffer, 4), + (1n << 32n) - 1n, + ); + assertStrictEquals( + get32BitUnsignedIntegralItem(new DataView(buffer), 4), + (1n << 32n) - 1n, + ); + assertStrictEquals( + get32BitUnsignedIntegralItem(new Int32Array(buffer), 4), + (1n << 32n) - 1n, + ); + }); +}); + +describe("get64BitFloatingPointItem", () => { + it("[[Call]] gets the item", () => { + const buffer = new ArrayBuffer(24); + const view = new DataView(buffer); + view.setFloat64(0, NaN); + view.setFloat64(8, -Infinity); + view.setFloat64(16, -0); + assertStrictEquals(get64BitFloatingPointItem(buffer, 0), NaN); + assertStrictEquals( + get64BitFloatingPointItem(buffer, 8), + -Infinity, + ); + assertStrictEquals(get64BitFloatingPointItem(buffer, 16), -0); + assertStrictEquals( + get64BitFloatingPointItem(new DataView(buffer), 8), + -Infinity, + ); + assertStrictEquals( + get64BitFloatingPointItem(new BigUint64Array(buffer), 8), + -Infinity, + ); + }); +}); + +describe("get64BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1], + ).buffer; + assertStrictEquals(get64BitSignedIntegralItem(buffer, 8), -1n); + assertStrictEquals( + get64BitSignedIntegralItem(new DataView(buffer), 8), + -1n, + ); + assertStrictEquals( + get64BitSignedIntegralItem(new BigUint64Array(buffer), 8), + -1n, + ); + }); +}); + +describe("get64BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1], + ).buffer; + assertStrictEquals( + get64BitUnsignedIntegralItem(buffer, 8), + (1n << 64n) - 1n, + ); + assertStrictEquals( + get64BitUnsignedIntegralItem(new DataView(buffer), 8), + (1n << 64n) - 1n, + ); + assertStrictEquals( + get64BitUnsignedIntegralItem(new BigInt64Array(buffer), 8), + (1n << 64n) - 1n, + ); + }); +}); + +describe("isArrayBuffer", () => { + it("[[Call]] returns true for array buffers", () => { + assertStrictEquals( + isArrayBuffer(new ArrayBuffer()), + true, + ); + assertStrictEquals( + isArrayBuffer(new SharedArrayBuffer()), + true, + ); + }); + + it("[[Call]] returns false for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + new DataView(new ArrayBuffer()), + new Uint8Array(), + ].forEach((value) => + assertStrictEquals(isArrayBuffer(value), false) + ); + }); +}); + +describe("isArrayBufferView", () => { + it("[[Call]] returns true for data views", () => { + assertStrictEquals( + isArrayBufferView(new DataView(new ArrayBuffer())), + true, + ); + }); + + it("[[Call]] returns true for typed arrays", () => { + assertStrictEquals( + isArrayBufferView(new Uint8ClampedArray()), + true, + ); + assertStrictEquals(isArrayBufferView(new BigInt64Array()), true); + }); + + it("[[Call]] returns false for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + new ArrayBuffer(), + new SharedArrayBuffer(), + ].forEach((value) => + assertStrictEquals(isArrayBufferView(value), false) + ); + }); +}); + describe("isBase16", () => { it("[[Call]] returns true for base64 strings", () => { for (const { base16 } of data.values()) { - assert(isBase16(base16)); - assert(isBase16(base16.toLowerCase())); + assertStrictEquals(isBase16(base16), true); + assertStrictEquals(isBase16(base16.toLowerCase()), true); } }); @@ -602,15 +918,15 @@ describe("isBase16", () => { "a", "abc", "abcg", - ].forEach((value) => assert(!isBase16(value))); + ].forEach((value) => assertStrictEquals(isBase16(value), false)); }); }); describe("isBase32", () => { it("[[Call]] returns true for base32 strings", () => { for (const { base32 } of data.values()) { - assert(isBase32(base32)); - assert(isBase32(base32.toLowerCase())); + assertStrictEquals(isBase32(base32), true); + assertStrictEquals(isBase32(base32.toLowerCase()), true); } }); @@ -629,14 +945,14 @@ describe("isBase32", () => { "ABC1", "A=======", "ABCDEFGHI", - ].forEach((value) => assert(!isBase32(value))); + ].forEach((value) => assertStrictEquals(isBase32(value), false)); }); }); describe("isBase64", () => { it("[[Call]] returns true for base64 strings", () => { for (const { base64 } of data.values()) { - assert(isBase64(base64)); + assertStrictEquals(isBase64(base64), true); } }); @@ -655,17 +971,18 @@ describe("isBase64", () => { "abc_", "a", "abc==", - ].forEach((value) => assert(!isBase64(value))); + ].forEach((value) => assertStrictEquals(isBase64(value), false)); }); }); describe("isFilenameSafeBase64", () => { it("[[Call]] returns true for filename‐safe base64 strings", () => { for (const { base64 } of data.values()) { - assert( + assertStrictEquals( isFilenameSafeBase64( base64.replace("+", "-").replace("/", "_"), ), + true, ); } }); @@ -685,32 +1002,119 @@ describe("isFilenameSafeBase64", () => { "abc/", "a", "abc==", - ].forEach((value) => assert(!isFilenameSafeBase64(value))); + ].forEach((value) => + assertStrictEquals(isFilenameSafeBase64(value), false) + ); + }); +}); + +describe("isSharedArrayBuffer", () => { + it("[[Call]] returns true for shared array buffers", () => { + assertStrictEquals( + isSharedArrayBuffer(new SharedArrayBuffer()), + true, + ); + }); + + it("[[Call]] returns false for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + new ArrayBuffer(), + new DataView(new ArrayBuffer()), + new Uint8Array(), + ].forEach((value) => + assertStrictEquals(isSharedArrayBuffer(value), false) + ); + }); +}); + +describe("isTypedArray", () => { + it("[[Call]] returns true for typed arrays", () => { + assertStrictEquals( + isTypedArray(new Uint8Array()), + true, + ); + assertStrictEquals( + isTypedArray(new BigInt64Array()), + true, + ); + }); + + it("[[Call]] returns false for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + new ArrayBuffer(), + new SharedArrayBuffer(), + new DataView(new ArrayBuffer()), + ].forEach((value) => + assertStrictEquals(isTypedArray(value), false) + ); }); }); describe("isWRMGBase32", () => { it("[[Call]] returns true for W·R·M·G base32 strings", () => { for (const { wrmg } of data.values()) { - assert(isWRMGBase32(wrmg)); - assert(isWRMGBase32(wrmg.toLowerCase())); - assert(isWRMGBase32(`--${wrmg}--`)); - assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "I"))); - assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "L"))); - assert(isWRMGBase32(wrmg.replaceAll(/0/gu, "O"))); - assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "i"))); - assert(isWRMGBase32(wrmg.replaceAll(/1/gu, "l"))); - assert(isWRMGBase32(wrmg.replaceAll(/0/gu, "o"))); - assert(isWRMGBase32(wrmg.replaceAll(/./gu, ($) => { - const rand = Math.random(); - return rand < 0.25 - ? $ - : rand < 0.5 - ? `-${$}` - : rand < 0.75 - ? `${$}-` - : `-${$}-`; - }))); + assertStrictEquals(isWRMGBase32(wrmg), true); + assertStrictEquals(isWRMGBase32(wrmg.toLowerCase()), true); + assertStrictEquals(isWRMGBase32(`--${wrmg}--`), true); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/1/gu, "I")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/1/gu, "L")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/0/gu, "O")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/1/gu, "i")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/1/gu, "l")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/0/gu, "o")), + true, + ); + assertStrictEquals( + isWRMGBase32(wrmg.replaceAll(/./gu, ($) => { + const rand = Math.random(); + return rand < 0.25 + ? $ + : rand < 0.5 + ? `-${$}` + : rand < 0.75 + ? `${$}-` + : `-${$}-`; + })), + true, + ); } }); @@ -729,7 +1133,133 @@ describe("isWRMGBase32", () => { "ABCU", "A", "ABCDEFGH1", - ].forEach((value) => assert(!isWRMGBase32(value))); + ].forEach((value) => + assertStrictEquals(isWRMGBase32(value), false) + ); + }); +}); + +describe("set8BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(3); + set8BitIntegralItem(buffer, 1, -1n); + set8BitIntegralItem(buffer, 2, (1 << 8) - 1); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, -1, -1]), + ); + }); +}); + +describe("set16BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(6); + set16BitIntegralItem(buffer, 2, -1n); + set16BitIntegralItem(buffer, 4, (1 << 16) - 1); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, -1, -1, -1, -1]), + ); + }); +}); + +describe("set32BitFloatingPointItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + const expected = new ArrayBuffer(12); + const view = new DataView(expected); + view.setFloat32(0, NaN); + set32BitFloatingPointItem(buffer, 0, NaN); + view.setFloat32(4, -Infinity); + set32BitFloatingPointItem(buffer, 4, -Infinity); + view.setFloat32(8, -0); + set32BitFloatingPointItem(buffer, 8, -0); + assertEquals( + new Uint8Array(buffer), + new Uint8Array(expected), + ); + }); +}); + +describe("set32BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + set32BitIntegralItem(buffer, 4, -1n); + set32BitIntegralItem(buffer, 8, -1 >>> 0); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1]), + ); + }); +}); + +describe("set64BitFloatingPointItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(24); + const expected = new ArrayBuffer(24); + const view = new DataView(expected); + view.setFloat64(0, NaN); + set64BitFloatingPointItem(buffer, 0, NaN); + view.setFloat64(4, -Infinity); + set64BitFloatingPointItem(buffer, 4, -Infinity); + view.setFloat64(8, -0); + set64BitFloatingPointItem(buffer, 8, -0); + assertEquals( + new Uint8Array(buffer), + new Uint8Array(expected), + ); + }); +}); + +describe("set64BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + set64BitIntegralItem(buffer, 4, -1n); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1]), + ); + }); +}); + +describe("toArrayBuffer", () => { + it("[[Call]] returns the argument for array buffers", () => { + const buffer = new ArrayBuffer(); + assertStrictEquals(toArrayBuffer(buffer), buffer); + }); + + it("[[Call]] returns the buffer for data views", () => { + const src = Uint8Array.from([2, 3, 1]); + const buffer = toArrayBuffer(new DataView(src.buffer)); + assert(buffer instanceof ArrayBuffer); + assertEquals(new Uint8Array(buffer), src); + }); + + it("[[Call]] returns the buffer for typed arrays", () => { + const src = Uint8Array.from([2, 3, 1]); + const buffer = toArrayBuffer(src); + assert(buffer instanceof ArrayBuffer); + assertEquals(new Uint8Array(buffer), src); + }); + + it("[[Call]] throws for others", () => { + [ + undefined, + null, + true, + Symbol(), + 27, + 98n, + {}, + [], + () => {}, + new Proxy({}, {}), + "string", + ].forEach((value) => + assertThrows(() => { + toArrayBuffer(value); + }) + ); }); }); diff --git a/collection.js b/collection.js index 7319b73..3cfe938 100644 --- a/collection.js +++ b/collection.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ collection.js // ==================================================================== // -// Copyright © 2020–2022 Lady [@ Lady’s Computer]. +// Copyright © 2020–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -18,6 +18,8 @@ import { } from "./numeric.js"; import { sameValue, type } from "./value.js"; +const { prototype: arrayPrototype } = Array; + export const { /** Returns an array of the provided values. */ of: array, @@ -30,7 +32,7 @@ export const { } = Array; /** - * Returns -0 if the provided argument is "-0"; returns a number + * Returns −0 if the provided argument is "-0"; returns a number * representing the index if the provided argument is a canonical * numeric index string; otherwise, returns undefined. * @@ -49,34 +51,35 @@ export const canonicalNumericIndexString = ($) => { }; /** - * Returns the result of catenating the provided arraylikes, returning - * a new collection according to the algorithm of Array::concat. + * Returns the result of catenating the provided arraylikes into a new + * collection according to the algorithm of `Array::concat`. */ -export const catenate = makeCallable(Array.prototype.concat); +export const catenate = makeCallable(arrayPrototype.concat); /** * Copies the items in the provided object to a new location according - * to the algorithm of Array::copyWithin. + * to the algorithm of `Array::copyWithin`. */ -export const copyWithin = makeCallable(Array.prototype.copyWithin); +export const copyWithin = makeCallable(arrayPrototype.copyWithin); /** - * Fills the provided object with the provided value using the - * algorithm of Array::fill. + * Fills the provided object with the provided value according to the + * algorithm of `Array::fill`. */ -export const fill = makeCallable(Array.prototype.fill); +export const fill = makeCallable(arrayPrototype.fill); /** * Returns the result of filtering the provided object with the - * provided callback, using the algorithm of Array::filter. + * provided callback, according to the algorithm of `Array::filter`. */ -export const filter = makeCallable(Array.prototype.filter); +export const filter = makeCallable(arrayPrototype.filter); /** * Returns the first index in the provided object whose value satisfies - * the provided callback using the algorithm of Array::findIndex. + * the provided callback according to the algorithm of + * `Array::findIndex`. */ -export const findIndex = makeCallable(Array.prototype.findIndex); +export const findIndex = makeCallable(arrayPrototype.findIndex); /** * Returns the first indexed entry in the provided object whose value @@ -106,74 +109,77 @@ export const findIndexedEntry = ( /** * Returns the first indexed value in the provided object which - * satisfies the provided callback, using the algorithm of Array::find. + * satisfies the provided callback, according to the algorithm of + * `Array::find`. */ -export const findItem = makeCallable(Array.prototype.find); +export const findItem = makeCallable(arrayPrototype.find); /** * Returns the result of flatmapping the provided value with the - * provided callback using the algorithm of Array::flatMap. + * provided callback according to the algorithm of `Array::flatMap`. */ -export const flatmap = makeCallable(Array.prototype.flatMap); +export const flatmap = makeCallable(arrayPrototype.flatMap); /** - * Returns the result of flattening the provided object using the - * algorithm of Array::flat. + * Returns the result of flattening the provided object according to + * the algorithm of `Array::flat`. */ -export const flatten = makeCallable(Array.prototype.flat); +export const flatten = makeCallable(arrayPrototype.flat); /** * Returns the first index of the provided object with a value * equivalent to the provided value according to the algorithm of - * Array::indexOf. + * `Array::indexOf`. */ -export const getFirstIndex = makeCallable(Array.prototype.indexOf); +export const getFirstIndex = makeCallable(arrayPrototype.indexOf); /** - * Returns the item on the provided object at the provided index using - * the algorithm of Array::at. + * Returns the item on the provided object at the provided index + * according to the algorithm of `Array::at`. */ -export const getItem = makeCallable(Array.prototype.at); +export const getItem = makeCallable(arrayPrototype.at); /** * Returns the last index of the provided object with a value * equivalent to the provided value according to the algorithm of - * Array::lastIndexOf. + * `Array::lastIndexOf`. */ -export const getLastIndex = makeCallable(Array.prototype.lastIndexOf); +export const getLastIndex = makeCallable(arrayPrototype.lastIndexOf); /** * Returns whether every indexed value in the provided object satisfies - * the provided function, using the algorithm of Array::every. + * the provided function, according to the algorithm of `Array::every`. */ -export const hasEvery = makeCallable(Array.prototype.every); +export const hasEvery = makeCallable(arrayPrototype.every); /** * Returns whether the provided object has an indexed value which - * satisfies the provided function, using the algorithm of Array::some. + * satisfies the provided function, according to the algorithm of + * `Array::some`. */ -export const hasSome = makeCallable(Array.prototype.some); +export const hasSome = makeCallable(arrayPrototype.some); /** * Returns whether the provided object has an indexed value equivalent - * to the provided value according to the algorithm of Array::includes. + * to the provided value according to the algorithm of + * `Array::includes`. * - * > ☡ This algorithm treats missing values as `undefined` rather than - * > skipping them. + * ※ This algorithm treats missing values as `undefined` rather than + * skipping them. */ -export const includes = makeCallable(Array.prototype.includes); +export const includes = makeCallable(arrayPrototype.includes); /** * Returns an iterator over the indexed entries in the provided value - * according to the algorithm of Array::entries. + * according to the algorithm of `Array::entries`. */ -export const indexedEntries = makeCallable(Array.prototype.entries); +export const indexedEntries = makeCallable(arrayPrototype.entries); /** * Returns an iterator over the indices in the provided value according - * to the algorithm of Array::keys. + * to the algorithm of `Array::keys`. */ -export const indices = makeCallable(Array.prototype.keys); +export const indices = makeCallable(arrayPrototype.keys); /** Returns whether the provided value is an array index string. */ export const isArrayIndexString = ($) => { @@ -214,7 +220,7 @@ export const isArraylikeObject = ($) => { * - It requires the `length` property to be an integer index. * * - It requires the object to be concat‐spreadable, meaning it must - * either be an array or have `[Symbol.isConcatSpreadable]` be true. + * either be an array or have `.[Symbol.isConcatSpreadable]` be true. */ export const isCollection = ($) => { if (!(type($) === "object" && "length" in $)) { @@ -264,9 +270,9 @@ export const isIntegerIndexString = ($) => { /** * Returns an iterator over the items in the provided value according - * to the algorithm of Array::values. + * to the algorithm of `Array::values`. */ -export const items = makeCallable(Array.prototype.values); +export const items = makeCallable(arrayPrototype.values); /** * Returns the length of the provided arraylike object. @@ -280,49 +286,57 @@ export const lengthOfArraylike = ({ length }) => toLength(length); /** * Returns the result of mapping the provided value with the provided - * callback using the algorithm of Array::map. + * callback according to the algorithm of `Array::map`. */ -export const map = makeCallable(Array.prototype.map); +export const map = makeCallable(arrayPrototype.map); -/** Pops from the provided value using the algorithm of Array::pop. */ -export const pop = makeCallable(Array.prototype.pop); +/** + * Pops from the provided value according to the algorithm of + * `Array::pop`. + */ +export const pop = makeCallable(arrayPrototype.pop); /** - * Pushes onto the provided value using the algorithm of Array::push. + * Pushes onto the provided value according to the algorithm of + * `Array::push`. */ -export const push = makeCallable(Array.prototype.push); +export const push = makeCallable(arrayPrototype.push); /** * Returns the result of reducing the provided value with the provided - * callback, using the algorithm of Array::reduce. + * callback, according to the algorithm of `Array::reduce`. */ -export const reduce = makeCallable(Array.prototype.reduce); +export const reduce = makeCallable(arrayPrototype.reduce); /** - * Reverses the provided value using the algorithm of Array::reverse. + * Reverses the provided value according to the algorithm of + * `Array::reverse`. */ -export const reverse = makeCallable(Array.prototype.reverse); +export const reverse = makeCallable(arrayPrototype.reverse); -/** Shifts the provided value using the algorithm of Array::shift. */ -export const shift = makeCallable(Array.prototype.shift); +/** + * Shifts the provided value according to the algorithm of + * `Array::shift`. + */ +export const shift = makeCallable(arrayPrototype.shift); /** - * Returns a slice of the provided value using the algorithm of - * Array::slice. + * Returns a slice of the provided value according to the algorithm of + * `Array::slice`. */ -export const slice = makeCallable(Array.prototype.slice); +export const slice = makeCallable(arrayPrototype.slice); /** - * Sorts the provided value in‐place using the algorithm of - * Array::sort. + * Sorts the provided value in‐place according to the algorithm of + * `Array::sort`. */ -export const sort = makeCallable(Array.prototype.sort); +export const sort = makeCallable(arrayPrototype.sort); /** - * Splices into and out of the provided value using the algorithm of - * Array::splice. + * Splices into and out of the provided value according to the + * algorithm of `Array::splice`. */ -export const splice = makeCallable(Array.prototype.splice); +export const splice = makeCallable(arrayPrototype.splice); /** * Returns the result of converting the provided value to an array @@ -355,6 +369,7 @@ export const toLength = ($) => { }; /** - * Unshifts the provided value using the algorithm of Array::unshift. + * Unshifts the provided value according to the algorithm of + * `Array::unshift`. */ -export const unshift = makeCallable(Array.prototype.unshift); +export const unshift = makeCallable(arrayPrototype.unshift); diff --git a/function.js b/function.js index 46cab86..5f397a9 100644 --- a/function.js +++ b/function.js @@ -1,19 +1,21 @@ // ♓🌟 Piscēs ∷ function.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022‐2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . +import { ITERATOR } from "./value.js"; + export const { /** * Creates a bound function from the provided function using the * provided this value and arguments list. * - * ☡ As with call and construct, the arguments must be passed as an - * array. + * ☡ As with `call` and `construct`, the arguments must be passed as + * an array. */ bind, @@ -22,7 +24,7 @@ export const { * first argument as the `this` value and the remaining arguments * passed through. * - * ※ This is effectively an alias for Function.prototype.call.bind. + * ※ This is effectively an alias for `Function::call.bind`. */ makeCallable, } = (() => { @@ -40,13 +42,12 @@ export const { defineProperty: defineOwnProperty, getPrototypeOf: getPrototype, } = Object; - const { iterator: iteratorSymbol } = Symbol; - const { [iteratorSymbol]: arrayIterator } = Array.prototype; + const { [ITERATOR]: arrayIterator } = Array.prototype; const { next: arrayIteratorNext, - } = getPrototype([][iteratorSymbol]()); + } = getPrototype([][ITERATOR]()); const argumentIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: callBind( arrayIteratorNext, @@ -74,23 +75,25 @@ export const { }; })(); -/** - * Calls the provided function with the provided this value and - * arguments list. - * - * ☡ This is an alias for Reflect.apply—the arguments must be passed - * as an array. - */ -export const call = Reflect.apply; +export const { + /** + * Calls the provided function with the provided this value and + * arguments list. + * + * ☡ This is an alias for `Reflect.apply`—the arguments must be + * passed as an arraylike. + */ + apply: call, -/** - * Constructs the provided function with the provided arguments list - * and new target. - * - * ☡ This is an alias for Reflect.construct—the arguments must be - * passed as an array. - */ -export const construct = Reflect.construct; + /** + * Constructs the provided function with the provided arguments list + * and new target. + * + * ☡ This is an alias for `Reflect.construct`—the arguments must be + * passed as an arraylike. + */ + construct, +} = Reflect; /** * Returns the provided value. @@ -111,13 +114,17 @@ export const isCallable = ($) => typeof $ === "function"; export const isConstructor = ($) => { // The provided value is an object. try { + // Try constructing a new object with the provided value as its + // `new.target`. This will throw if the provided value is not a + // constructor. construct( function () {}, [], $, - ); // will throw if $ is not a constructor + ); return true; } catch { + // The provided value was not a constructor. return false; } }; diff --git a/iri.js b/iri.js index 47b1fb8..5dd9dd0 100644 --- a/iri.js +++ b/iri.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ iri.js // ==================================================================== // -// Copyright © 2020, 2022 Lady [@ Lady’s Computer]. +// Copyright © 2020, 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -20,6 +20,7 @@ import { stringStartsWith, substring, } from "./string.js"; +import { ITERATOR } from "./value.js"; const sub·delims = rawString`[!\$&'()*+,;=]`; const gen·delims = rawString`[:/?#\[\]@]`; @@ -198,24 +199,23 @@ export const { removeDotSegments, } = (() => { const TE = TextEncoder; - const { iterator: iteratorSymbol } = Symbol; const { toString: numberToString } = Number.prototype; const { encode: teEncode } = TE.prototype; - const { [iteratorSymbol]: arrayIterator } = Array.prototype; + const { [ITERATOR]: arrayIterator } = Array.prototype; const { next: arrayIteratorNext, - } = Object.getPrototypeOf([][iteratorSymbol]()); + } = Object.getPrototypeOf([][ITERATOR]()); const { next: generatorIteratorNext, } = Object.getPrototypeOf(function* () {}.prototype); - const { [iteratorSymbol]: stringIterator } = String.prototype; + const { [ITERATOR]: stringIterator } = String.prototype; const { next: stringIteratorNext, - } = Object.getPrototypeOf(""[iteratorSymbol]()); + } = Object.getPrototypeOf(""[ITERATOR]()); const iriCharacterIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind( stringIteratorNext, @@ -226,14 +226,14 @@ export const { }, }; const iriGeneratorIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind(generatorIteratorNext, this.generator(), []), }; }, }; const iriSegmentIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind( arrayIteratorNext, diff --git a/numeric.js b/numeric.js index 4a066f7..5a3d6ea 100644 --- a/numeric.js +++ b/numeric.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ numeric.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -21,49 +21,49 @@ export const { /** * ln(10). * - * ※ This is an alias for Math.LN10. + * ※ This is an alias for `Math.LN10`. */ LN10, /** * ln(2). * - * ※ This is an alias for Math.LN2. + * ※ This is an alias for `Math.LN2`. */ LN2, /** * log10(ℇ). * - * ※ This is an alias for Math.LOG10E. + * ※ This is an alias for `Math.LOG10E`. */ LOG10E: LOG10ℇ, /** * log2(ℇ). * - * ※ This is an alias for Math.LOG2E. + * ※ This is an alias for `Math.LOG2E`. */ LOG2E: LOG2ℇ, /** - * sqrt(.5). + * sqrt(½). * - * ※ This is an alias for Math.SQRT1_2. + * ※ This is an alias for `Math.SQRT1_2`. */ SQRT1_2: RECIPROCAL_SQRT2, /** * sqrt(2). * - * ※ This is an alias for Math.SQRT2. + * ※ This is an alias for `Math.SQRT2`. */ SQRT2, /** * Returns the arccos of the provided value. * - * ※ This is an alias for Math.acos. + * ※ This is an alias for `Math.acos`. * * ☡ This function does not allow big·int arguments. */ @@ -72,7 +72,7 @@ export const { /** * Returns the arccosh of the provided value. * - * ※ This is an alias for Math.acosh. + * ※ This is an alias for `Math.acosh`. * * ☡ This function does not allow big·int arguments. */ @@ -81,7 +81,7 @@ export const { /** * Returns the arcsin of the provided value. * - * ※ This is an alias for Math.asin. + * ※ This is an alias for `Math.asin`. * * ☡ This function does not allow big·int arguments. */ @@ -90,7 +90,7 @@ export const { /** * Returns the arcsinh of the provided value. * - * ※ This is an alias for Math.asinh. + * ※ This is an alias for `Math.asinh`. * * ☡ This function does not allow big·int arguments. */ @@ -99,7 +99,7 @@ export const { /** * Returns the arctan of the provided value. * - * ※ This is an alias for Math.atan. + * ※ This is an alias for `Math.atan`. * * ☡ This function does not allow big·int arguments. */ @@ -108,7 +108,7 @@ export const { /** * Returns the arctanh of the provided value. * - * ※ This is an alias for Math.atanh. + * ※ This is an alias for `Math.atanh`. * * ☡ This function does not allow big·int arguments. */ @@ -117,7 +117,7 @@ export const { /** * Returns the cube root of the provided value. * - * ※ This is an alias for Math.cbrt. + * ※ This is an alias for `Math.cbrt`. * * ☡ This function does not allow big·int arguments. */ @@ -126,7 +126,7 @@ export const { /** * Returns the ceiling of the provided value. * - * ※ This is an alias for Math.ceil. + * ※ This is an alias for `Math.ceil`. * * ☡ This function does not allow big·int arguments. */ @@ -135,7 +135,7 @@ export const { /** * Returns the cos of the provided value. * - * ※ This is an alias for Math.cos. + * ※ This is an alias for `Math.cos`. * * ☡ This function does not allow big·int arguments. */ @@ -144,7 +144,7 @@ export const { /** * Returns the cosh of the provided value. * - * ※ This is an alias for Math.cosh. + * ※ This is an alias for `Math.cosh`. * * ☡ This function does not allow big·int arguments. */ @@ -153,7 +153,7 @@ export const { /** * Returns the Euler number raised to the provided value. * - * ※ This is an alias for Math.exp. + * ※ This is an alias for `Math.exp`. * * ☡ This function does not allow big·int arguments. */ @@ -162,7 +162,7 @@ export const { /** * Returns the Euler number raised to the provided value, minus one. * - * ※ This is an alias for Math.expm1. + * ※ This is an alias for `Math.expm1`. * * ☡ This function does not allow big·int arguments. */ @@ -171,7 +171,7 @@ export const { /** * Returns the floor of the provided value. * - * ※ This is an alias for Math.floor. + * ※ This is an alias for `Math.floor`. * * ☡ This function does not allow big·int arguments. */ @@ -181,7 +181,7 @@ export const { * Returns the square root of the sum of the squares of the provided * arguments. * - * ※ This is an alias for Math.hypot. + * ※ This is an alias for `Math.hypot`. * * ☡ This function does not allow big·int arguments. */ @@ -190,7 +190,7 @@ export const { /** * Returns the ln of the provided value. * - * ※ This is an alias for Math.log. + * ※ This is an alias for `Math.log`. * * ☡ This function does not allow big·int arguments. */ @@ -199,7 +199,7 @@ export const { /** * Returns the log10 of the provided value. * - * ※ This is an alias for Math.log10. + * ※ This is an alias for `Math.log10`. * * ☡ This function does not allow big·int arguments. */ @@ -208,7 +208,7 @@ export const { /** * Returns the ln of one plus the provided value. * - * ※ This is an alias for Math.log1p. + * ※ This is an alias for `Math.log1p`. * * ☡ This function does not allow big·int arguments. */ @@ -217,7 +217,7 @@ export const { /** * Returns the log2 of the provided value. * - * ※ This is an alias for Math.log2. + * ※ This is an alias for `Math.log2`. * * ☡ This function does not allow big·int arguments. */ @@ -226,14 +226,14 @@ export const { /** * Returns a pseudo·random value in the range [0, 1). * - * ※ This is an alias for Math.random. + * ※ This is an alias for `Math.random`. */ random: rand, /** * Returns the round of the provided value. * - * ※ This is an alias for Math.round. + * ※ This is an alias for `Math.round`. * * ☡ This function does not allow big·int arguments. */ @@ -242,7 +242,7 @@ export const { /** * Returns the sinh of the provided value. * - * ※ This is an alias for Math.sinh. + * ※ This is an alias for `Math.sinh`. * * ☡ This function does not allow big·int arguments. */ @@ -251,7 +251,7 @@ export const { /** * Returns the square root of the provided value. * - * ※ This is an alias for Math.sqrt. + * ※ This is an alias for `Math.sqrt`. * * ☡ This function does not allow big·int arguments. */ @@ -260,7 +260,7 @@ export const { /** * Returns the tan of the provided value. * - * ※ This is an alias for Math.tan. + * ※ This is an alias for `Math.tan`. * * ☡ This function does not allow big·int arguments. */ @@ -269,7 +269,7 @@ export const { /** * Returns the tanh of the provided value. * - * ※ This is an alias for Math.tanh. + * ※ This is an alias for `Math.tanh`. * * ☡ This function does not allow big·int arguments. */ @@ -278,7 +278,7 @@ export const { /** * Returns the trunc of the provided value. * - * ※ This is an alias for Math.trunc. + * ※ This is an alias for `Math.trunc`. * * ☡ This function does not allow big·int arguments. */ @@ -287,14 +287,14 @@ export const { /** * The mathematical constant π. * - * ※ This is an alias for Math.PI. + * ※ This is an alias for `Math.PI`. */ PI: Π, /** * The Euler number. * - * ※ This is an alias for Math.E. + * ※ This is an alias for `Math.E`. */ E: ℇ, } = Math; @@ -303,84 +303,84 @@ export const { /** * The largest number value less than infinity. * - * ※ This is an alias for Number.MAX_VALUE. + * ※ This is an alias for `Number.MAX_VALUE`. */ MAX_VALUE: MAXIMUM_NUMBER, /** * 2**53 - 1. * - * ※ This is an alias for Number.MAX_SAFE_INTEGER. + * ※ This is an alias for `Number.MAX_SAFE_INTEGER`. */ MAX_SAFE_INTEGER: MAXIMUM_SAFE_INTEGRAL_NUMBER, /** * The smallest number value greater than negative infinity. * - * ※ This is an alias for Number.MIN_VALUE. + * ※ This is an alias for `Number.MIN_VALUE`. */ MIN_VALUE: MINIMUM_NUMBER, /** * -(2**53 - 1). * - * ※ This is an alias for Number.MIN_SAFE_INTEGER. + * ※ This is an alias for `Number.MIN_SAFE_INTEGER`. */ MIN_SAFE_INTEGER: MINIMUM_SAFE_INTEGRAL_NUMBER, /** * Negative infinity. * - * ※ This is an alias for Number.NEGATIVE_INFINITY. + * ※ This is an alias for `Number.NEGATIVE_INFINITY`. */ NEGATIVE_INFINITY, /** * Nan. * - * ※ This is an alias for Number.NaN. + * ※ This is an alias for `Number.NaN`. */ NaN: NAN, /** * Positive infinity. * - * ※ This is an alias for Number.POSITIVE_INFINITY. + * ※ This is an alias for `Number.POSITIVE_INFINITY`. */ POSITIVE_INFINITY, /** * The difference between 1 and the smallest number greater than 1. * - * ※ This is an alias for Number.EPSILON. + * ※ This is an alias for `Number.EPSILON`. */ EPSILON: Ε, /** * Returns whether the provided value is a finite number. * - * ※ This is an alias for Number.isFinite. + * ※ This is an alias for `Number.isFinite`. */ isFinite: isFiniteNumber, /** * Returns whether the provided value is an integral number. * - * ※ This is an alias for Number.isInteger. + * ※ This is an alias for `Number.isInteger`. */ isInteger: isIntegralNumber, /** * Returns whether the provided value is nan. * - * ※ This is an alias for Number.isNaN. + * ※ This is an alias for `Number.isNaN`. */ isNaN: isNan, /** * Returns whether the provided value is a safe integral number. * - * ※ This is an alias for Number.isSafeInteger. + * ※ This is an alias for `Number.isSafeInteger`. */ isSafeInteger: isSafeIntegralNumber, } = Number; @@ -394,7 +394,7 @@ export const NEGATIVE_ZERO = -0; /** * Returns the magnitude (absolute value) of the provided value. * - * ※ Unlike Math.abs, this function can take big·int arguments. + * ※ Unlike `Math.abs`, this function can take big·int arguments. */ export const abs = ($) => { const n = toNumeric($); @@ -415,17 +415,17 @@ export const { /** * Returns the arctangent of the dividend of the provided values. * - * ※ Unlike Math.atan2, this function can take big·int arguments. + * ※ Unlike `Math.atan2`, this function can take big·int arguments. * However, the result will always be a number. */ atan2, /** - * Returns the number of leading zeroes in the 32‐bit representation of - * the provided value. + * Returns the number of leading zeroes in the 32‐bit representation + * of the provided value. * - * ※ Unlike Math.clz32, this function accepts either number or big·int - * values. + * ※ Unlike `Math.clz32`, this function accepts either number or + * big·int values. */ clz32, @@ -433,7 +433,7 @@ export const { * Returns the 32‐bit float which best approximate the provided * value. * - * ※ Unlike Math.fround, this function can take big·int arguments. + * ※ Unlike `Math.fround`, this function can take big·int arguments. * However, the result will always be a number. */ toFloat32, @@ -444,7 +444,9 @@ export const { clz32: ($) => { const n = toNumeric($); return clz32( - typeof n === "bigint" ? toNumber(toUintN(32, n)) : n, + typeof n === "bigint" + ? toNumber(toUnsignedIntegralNumeric(32, n)) + : n, ); }, toFloat32: ($) => fround(toNumber($)), @@ -455,7 +457,7 @@ export const { * Returns the highest value of the provided arguments, or negative * infinity if no argument is provided. * - * ※ Unlike Math.max, this function accepts either number or big·int + * ※ Unlike `Math.max`, this function accepts either number or big·int * values. All values must be of the same type, or this function will * throw an error. * @@ -504,7 +506,7 @@ export const max = (...$s) => { * Returns the lowest value of the provided arguments, or positive * infinity if no argument is provided. * - * ※ Unlike Math.min, this function accepts either number or big·int + * ※ Unlike `Math.min`, this function accepts either number or big·int * values. All values must be of the same type, or this function will * throw an error. * @@ -556,17 +558,17 @@ export const min = (...$s) => { * signed) zero. * * For big·ints, the return value of this function is 0n if the - * provided value is 0n, -1n if the provided value is negative, and +1n + * provided value is 0n, −1n if the provided value is negative, and +1n * otherwise. * - * For numbers, the return value is nan, -0, or +0 if the provided - * value is nan, -0, or +0, respectively, and -1 if the provided value + * For numbers, the return value is nan, −0, or +0 if the provided + * value is nan, −0, or +0, respectively, and −1 if the provided value * is negative and +1 if the provided value is positive otherwise. Note - * that positive and negative infinity will return +1 and -1 + * that positive and negative infinity will return +1 and −1 * respectively. * - * ※ Unlike Math.sign, this function accepts either number or big·int - * values. + * ※ Unlike `Math.sign`, this function accepts either number or + * big·int values. */ export const sgn = ($) => { const n = toNumeric($); @@ -585,7 +587,7 @@ export const sgn = ($) => { * * ※ This method is safe to use with numbers. * - * ※ This is effectively an alias for BigInt. + * ※ This is effectively an alias for `BigInt`. */ export const { toBigInt } = (() => { const makeBigInt = BigInt; @@ -679,68 +681,6 @@ export const { }; })(); -export const { - /** - * Returns the result of converting the provided value to fit within - * the provided number of bits as a signed integer. - * - * ※ Unlike BigInt.asIntN, this function accepts both big·int and - * number values. - * - * ☡ The first argument, the number of bits, must be a number. - */ - toIntN, - - /** - * Returns the result of converting the provided value to fit within - * the provided number of bits as an unsigned integer. - * - * ※ Unlike BigInt.asUintN, this function accepts both big·int and - * number values. - * - * ☡ The first argument, the number of bits, must be a number. - */ - toUintN, -} = (() => { - const { asIntN, asUintN } = BigInt; - return { - toIntN: (n, $) => { - const prim = toPrimitive($); - if (typeof prim === "bigint") { - // The primitive value is a big·int. - return asIntN(n, prim); - } else { - // The primitive value is not a big·int. - const int = trunc(prim); - if (!isFiniteNumber(int) || int == 0) { - // The truncated value is zero or not finite. - return 0; - } else { - // The truncated value is finite. - return toNumber(asIntN(n, toBigInt(int))); - } - } - }, - toUintN: (n, $) => { - const prim = toPrimitive($); - if (typeof prim === "bigint") { - // The primitive value is a big·int. - return asUintN(n, prim); - } else { - // The primitive value is not a big·int. - const int = trunc(prim); - if (!isFiniteNumber(int) || int == 0) { - // The truncated value is zero or not finite. - return 0; - } else { - // The truncated value is finite. - return toNumber(asUintN(n, toBigInt(int))); - } - } - }, - }; -})(); - /** * Returns the result of converting the provided number to an integral * number. @@ -784,7 +724,7 @@ export const toIntegralNumberOrInfinity = ($) => { * * ※ This function is safe to use with big·ints. * - * ※ This is effectively a nonconstructible version of the Number + * ※ This is effectively a nonconstructible version of the `Number` * constructor. */ export const { toNumber } = (() => { @@ -803,3 +743,65 @@ export const toNumeric = ($) => { const primValue = toPrimitive($, "number"); return typeof primValue === "bigint" ? primValue : +primValue; }; + +export const { + /** + * Returns the result of converting the provided value to fit within + * the provided number of bits as a signed integer. + * + * ※ Unlike `BigInt.asIntN`, this function accepts both big·int and + * number values. + * + * ☡ The first argument, the number of bits, must be a number. + */ + toSignedIntegralNumeric, + + /** + * Returns the result of converting the provided value to fit within + * the provided number of bits as an unsigned integer. + * + * ※ Unlike `BigInt.asUintN`, this function accepts both big·int and + * number values. + * + * ☡ The first argument, the number of bits, must be a number. + */ + toUnsignedIntegralNumeric, +} = (() => { + const { asIntN, asUintN } = BigInt; + return { + toSignedIntegralNumeric: (n, $) => { + const prim = toPrimitive($); + if (typeof prim === "bigint") { + // The primitive value is a big·int. + return asIntN(n, prim); + } else { + // The primitive value is not a big·int. + const int = trunc(prim); + if (!isFiniteNumber(int) || int == 0) { + // The truncated value is zero or not finite. + return 0; + } else { + // The truncated value is finite. + return toNumber(asIntN(n, toBigInt(int))); + } + } + }, + toUnsignedIntegralNumeric: (n, $) => { + const prim = toPrimitive($); + if (typeof prim === "bigint") { + // The primitive value is a big·int. + return asUintN(n, prim); + } else { + // The primitive value is not a big·int. + const int = trunc(prim); + if (!isFiniteNumber(int) || int == 0) { + // The truncated value is zero or not finite. + return 0; + } else { + // The truncated value is finite. + return toNumber(asUintN(n, toBigInt(int))); + } + } + }, + }; +})(); diff --git a/numeric.test.js b/numeric.test.js index 27776cd..4d3d2cb 100644 --- a/numeric.test.js +++ b/numeric.test.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ numeric.test.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -28,10 +28,10 @@ import { toFloat32, toIntegralNumber, toIntegralNumberOrInfinity, - toIntN, toNumber, toNumeric, - toUintN, + toSignedIntegralNumeric, + toUnsignedIntegralNumeric, } from "./numeric.js"; describe("NEGATIVE_ZERO", () => { @@ -206,18 +206,18 @@ describe("toFloat32", () => { }); }); -describe("toIntN", () => { +describe("toSignedIntegralNumeric", () => { it("[[Call]] converts to an int·n", () => { - assertStrictEquals(toIntN(2, 7n), -1n); + assertStrictEquals(toSignedIntegralNumeric(2, 7n), -1n); }); it("[[Call]] works with numbers", () => { - assertStrictEquals(toIntN(2, 7), -1); + assertStrictEquals(toSignedIntegralNumeric(2, 7), -1); }); it("[[Call]] works with non‐integers", () => { - assertStrictEquals(toIntN(2, 7.21), -1); - assertStrictEquals(toIntN(2, Infinity), 0); + assertStrictEquals(toSignedIntegralNumeric(2, 7.21), -1); + assertStrictEquals(toSignedIntegralNumeric(2, Infinity), 0); }); }); @@ -306,17 +306,17 @@ describe("toNumeric", () => { }); }); -describe("toUintN", () => { +describe("toUnsignedIntegralNumeric", () => { it("[[Call]] converts to an int·n", () => { - assertStrictEquals(toUintN(2, 7n), 3n); + assertStrictEquals(toUnsignedIntegralNumeric(2, 7n), 3n); }); it("[[Call]] works with numbers", () => { - assertStrictEquals(toUintN(2, 7), 3); + assertStrictEquals(toUnsignedIntegralNumeric(2, 7), 3); }); it("[[Call]] works with non‐integers", () => { - assertStrictEquals(toUintN(2, 7.21), 3); - assertStrictEquals(toUintN(2, Infinity), 0); + assertStrictEquals(toUnsignedIntegralNumeric(2, 7.21), 3); + assertStrictEquals(toUnsignedIntegralNumeric(2, Infinity), 0); }); }); diff --git a/object.js b/object.js index 9986291..a403795 100644 --- a/object.js +++ b/object.js @@ -1,14 +1,14 @@ // ♓🌟 Piscēs ∷ object.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . import { bind, call } from "./function.js"; -import { toPrimitive, type } from "./value.js"; +import { ITERATOR, SPECIES, toPrimitive, type } from "./value.js"; /** * An object whose properties are lazy‐loaded from the methods on the @@ -28,12 +28,12 @@ import { toPrimitive, type } from "./value.js"; * Methods will be called with the resulting object as their this * value. * - * LazyLoader objects have the same prototype as the passed methods + * `LazyLoader` objects have the same prototype as the passed methods * object. */ export class LazyLoader extends null { /** - * Constructs a new LazyLoader object. + * Constructs a new `LazyLoader` object. * * ☡ This function throws if the provided value is not an object. */ @@ -96,7 +96,7 @@ export const { PropertyDescriptor } = (() => { * object. * * The resulting object is proxied to enforce types (for example, - * its `enumerable` property, if defined, will always be a + * its `.enumerable` property, if defined, will always be a * boolean). */ constructor(O) { @@ -395,7 +395,7 @@ export const { * descriptors on the enumerable own properties of the provided * additional objects. * - * ※ This differs from Object.defineProperties in that it can take + * ※ This differs from `Object.defineProperties` in that it can take * multiple source objects. */ defineOwnProperties, @@ -419,7 +419,7 @@ export const { * Defines an own property on the provided object on the provided * property key using the provided property descriptor. * - * ※ This is an alias for Object.defineProperty. + * ※ This is an alias for `Object.defineProperty`. */ defineProperty: defineOwnProperty, @@ -428,7 +428,7 @@ export const { * properties as nonconfigurable and (if data properties) * nonwritable, and returns the object. * - * ※ This is an alias for Object.freeze. + * ※ This is an alias for `Object.freeze`. */ freeze, @@ -437,7 +437,7 @@ export const { * provided property key on the provided object, or null if none * exists. * - * ※ This is an alias for Object.getOwnPropertyDescriptor. + * ※ This is an alias for `Object.getOwnPropertyDescriptor`. */ getOwnPropertyDescriptor, @@ -445,7 +445,7 @@ export const { * Returns the property descriptors for the own properties on the * provided object. * - * ※ This is an alias for Object.getOwnPropertyDescriptors. + * ※ This is an alias for `Object.getOwnPropertyDescriptors`. */ getOwnPropertyDescriptors, @@ -455,7 +455,7 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for Object.getOwnPropertyNames. + * ※ This is an alias for `Object.getOwnPropertyNames`. */ getOwnPropertyNames: getOwnPropertyStrings, @@ -465,14 +465,14 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for Object.getOwnPropertySymbols. + * ※ This is an alias for `Object.getOwnPropertySymbols`. */ getOwnPropertySymbols, /** * Returns the prototype of the provided object. * - * ※ This is an alias for Object.getPrototypeOf. + * ※ This is an alias for `Object.getPrototypeOf`. */ getPrototypeOf: getPrototype, @@ -480,36 +480,36 @@ export const { * Returns whether the provided object has an own property with the * provided property key. * - * ※ This is an alias for Object.hasOwn. + * ※ This is an alias for `Object.hasOwn`. */ hasOwn: hasOwnProperty, /** * Returns whether the provided object is extensible. * - * ※ This is an alias for Object.isExtensible. + * ※ This is an alias for `Object.isExtensible`. */ - isExtensible, + isExtensible: isExtensibleObject, /** * Returns whether the provided object is frozen. * - * ※ This is an alias for Object.isFrozen. + * ※ This is an alias for `Object.isFrozen`. */ - isFrozen, + isFrozen: isFrozenObject, /** * Returns whether the provided object is sealed. * - * ※ This is an alias for Object.isSealed. + * ※ This is an alias for `Object.isSealed`. */ - isSealed, + isSealed: isSealedObject, /** * Returns an array of key~value pairs for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for Object.entries. + * ※ This is an alias for `Object.entries`. */ entries: namedEntries, @@ -517,7 +517,7 @@ export const { * Returns an array of the enumerable, string‐valued property keys on * the provided object. * - * ※ This is an alias for Object.keys. + * ※ This is an alias for `Object.keys`. */ keys: namedKeys, @@ -525,7 +525,7 @@ export const { * Returns an array of property values for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for Object.values. + * ※ This is an alias for `Object.values`. */ values: namedValues, @@ -533,14 +533,14 @@ export const { * Returns a new object with the provided prototype and property * descriptors. * - * ※ This is an alias for Object.create. + * ※ This is an alias for `Object.create`. */ create: objectCreate, /** * Returns a new object with the provided property keys and values. * - * ※ This is an alias for Object.fromEntries. + * ※ This is an alias for `Object.fromEntries`. */ fromEntries: objectFromEntries, @@ -548,7 +548,7 @@ export const { * Marks the provided object as non·extensible, and returns the * object. * - * ※ This is an alias for Object.preventExtensions. + * ※ This is an alias for `Object.preventExtensions`. */ preventExtensions, @@ -556,7 +556,7 @@ export const { * Marks the provided object as non·extensible and marks all its * properties as nonconfigurable, and returns the object. * - * ※ This is an alias for Object.seal. + * ※ This is an alias for `Object.seal`. */ seal, @@ -564,7 +564,7 @@ export const { * Sets the values of the enumerable own properties of the provided * additional objects on the provided object. * - * ※ This is an alias for Object.assign. + * ※ This is an alias for `Object.assign`. */ assign: setPropertyValues, @@ -572,7 +572,7 @@ export const { * Sets the prototype of the provided object to the provided value * and returns the object. * - * ※ This is an alias for Object.setPrototypeOf. + * ※ This is an alias for `Object.setPrototypeOf`. */ setPrototypeOf: setPrototype, } = Object; @@ -582,7 +582,7 @@ export const { * Removes the provided property key from the provided object and * returns the object. * - * ※ This function differs from Reflect.deleteProperty and the + * ※ This function differs from `Reflect.deleteProperty` and the * `delete` operator in that it throws if the deletion is * unsuccessful. * @@ -593,7 +593,7 @@ export const { /** * Returns an array of property keys on the provided object. * - * ※ This is effectively an alias for Reflect.ownKeys, except that + * ※ This is effectively an alias for `Reflect.ownKeys`, except that * it does not require that the argument be an object. */ getOwnPropertyKeys, @@ -602,7 +602,7 @@ export const { * Returns the value of the provided property key on the provided * object. * - * ※ This is effectively an alias for Reflect.get, except that it + * ※ This is effectively an alias for `Reflect.get`, except that it * does not require that the argument be an object. */ getPropertyValue, @@ -611,7 +611,7 @@ export const { * Returns whether the provided property key exists on the provided * object. * - * ※ This is effectively an alias for Reflect.has, except that it + * ※ This is effectively an alias for `Reflect.has`, except that it * does not require that the argument be an object. * * ※ This includes properties present on the prototype chain. @@ -622,8 +622,8 @@ export const { * Sets the provided property key to the provided value on the * provided object and returns the object. * - * ※ This function differs from Reflect.set in that it throws if the - * setting is unsuccessful. + * ※ This function differs from `Reflect.set` in that it throws if + * the setting is unsuccessful. * * ☡ This function throws if the first argument is not an object. */ @@ -677,26 +677,22 @@ export const { * property with the same getter *and* setter. * * The prototype for the resulting object will be taken from the - * `prototype` property of the provided constructor, or the - * `prototype` of the `constructor` of the provided object if the + * `.prototype` property of the provided constructor, or the + * `.prototype` of the `.constructor` of the provided object if the * provided constructor is undefined. If the used constructor has a - * nonnullish `Symbol.species`, that will be used instead. If the + * nonnullish `.[Symbol.species]`, that will be used instead. If the * used constructor or species is nullish or does not have a - * `prototype` property, the prototype is set to null. + * `.prototype` property, the prototype is set to null. * * ※ The prototype of the provided object itself is ignored. */ frozenCopy, } = (() => { - const { - iterator: iteratorSymbol, - species: speciesSymbol, - } = Symbol; const { next: generatorIteratorNext, } = getPrototype(function* () {}.prototype); const propertyDescriptorEntryIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind(generatorIteratorNext, this.generator(), []), }; @@ -713,8 +709,8 @@ export const { // O is not null or undefined. // // (If not provided, the constructor will be the value of - // getting the `constructor` property of O.) - const species = constructor?.[speciesSymbol] ?? constructor; + // getting the `.constructor` property of O.) + const species = constructor?.[SPECIES] ?? constructor; return preventExtensions( objectCreate( species == null || !("prototype" in species) diff --git a/string.js b/string.js index eb6f6fb..e697a1f 100644 --- a/string.js +++ b/string.js @@ -15,24 +15,29 @@ import { objectCreate, setPrototype, } from "./object.js"; -import { type } from "./value.js"; +import { ITERATOR, TO_STRING_TAG } from "./value.js"; + +const RE = RegExp; +const { prototype: rePrototype } = RE; +const { prototype: arrayPrototype } = Array; +const { prototype: stringPrototype } = String; + +const { exec: reExec } = rePrototype; export const { /** - * A RegExp·like object which only matches entire strings, and may + * A `RegExp`like object which only matches entire strings, and may * have additional constraints specified. * * Matchers are callable objects and will return true if they are * called with a string that they match, and false otherwise. * Matchers will always return false if called with nonstrings, - * although other methods like `exec` coerce their arguments and may - * still return true. + * although other methods like `::exec` coerce their arguments and + * may still return true. */ Matcher, } = (() => { - const RE = RegExp; - const { prototype: rePrototype } = RE; - const { exec: reExec, toString: reToString } = rePrototype; + const { toString: reToString } = rePrototype; const getDotAll = Object.getOwnPropertyDescriptor(rePrototype, "dotAll").get; const getFlags = @@ -57,7 +62,7 @@ export const { #regExp; /** - * Constructs a new Matcher from the provided source. + * Constructs a new `Matcher` from the provided source. * * If the provided source is a regular expression, then it must * have the unicode flag set. Otherwise, it is interpreted as the @@ -71,9 +76,9 @@ export const { * A callable constraint on acceptable inputs may be provided as a * third argument. If provided, it will be called with three * arguments whenever a match appears successful: first, the string - * being matched, second, the match result, and third, the Matcher - * object itself. If the return value of this call is falsey, then - * the match will be considered a failure. + * being matched, second, the match result, and third, the + * `Matcher` object itself. If the return value of this call is + * falsey, then the match will be considered a failure. * * ☡ If the provided source regular expression uses nongreedy * quantifiers, it may not match the whole string even if a match @@ -88,7 +93,7 @@ export const { // The provided value is not a string. return false; } else { - // The provided value is a string. Set the `lastIndex` of + // The provided value is a string. Set the `.lastIndex` of // the regular expression to 0 and see if the first attempt // at a match matches the whole string and passes the // provided constraint (if present). @@ -142,13 +147,13 @@ export const { } } - /** Gets whether the dotAll flag is present on this Matcher. */ + /** Gets whether the dot‐all flag is present on this `Matcher`. */ get dotAll() { return call(getDotAll, this.#regExp, []); } /** - * Executes this Matcher on the provided value and returns the + * Executes this `Matcher` on the provided value and returns the * result if there is a match, or null otherwise. * * Matchers only match if they can match the entire value on the @@ -179,47 +184,53 @@ export const { } /** - * Gets the flags present on this Matcher. + * Gets the flags present on this `Matcher`. * - * ※ This needs to be defined because the internal RegExp object + * ※ This needs to be defined because the internal `RegExp` object * may have flags which are not yet recognized by ♓🌟 Piscēs. */ get flags() { return call(getFlags, this.#regExp, []); } - /** Gets whether the global flag is present on this Matcher. */ + /** Gets whether the global flag is present on this `Matcher`. */ get global() { return call(getGlobal, this.#regExp, []); } - /** Gets whether the hasIndices flag is present on this Matcher. */ + /** + * Gets whether the has‐indices flag is present on this `Matcher`. + */ get hasIndices() { return call(getHasIndices, this.#regExp, []); } - /** Gets whether the ignoreCase flag is present on this Matcher. */ + /** + * Gets whether the ignore‐case flag is present on this `Matcher`. + */ get ignoreCase() { return call(getIgnoreCase, this.#regExp, []); } - /** Gets whether the multiline flag is present on this Matcher. */ + /** + * Gets whether the multiline flag is present on this `Matcher`. + */ get multiline() { return call(getMultiline, this.#regExp, []); } - /** Gets the regular expression source for this Matcher. */ + /** Gets the regular expression source for this `Matcher`. */ get source() { return call(getSource, this.#regExp, []); } - /** Gets whether the sticky flag is present on this Matcher. */ + /** Gets whether the sticky flag is present on this `Matcher`. */ get sticky() { return call(getSticky, this.#regExp, []); } /** - * Gets whether the unicode flag is present on this Matcher. + * Gets whether the unicode flag is present on this `Matcher`. * * ※ This will always be true. */ @@ -264,7 +275,7 @@ export const { const { toLowerCase: stringToLowercase, toUpperCase: stringToUppercase, - } = String.prototype; + } = stringPrototype; return { asciiLowercase: ($) => stringReplaceAll( @@ -310,21 +321,17 @@ export const { */ scalarValueString, } = (() => { - const { - iterator: iteratorSymbol, - toStringTag: toStringTagSymbol, - } = Symbol; - const { [iteratorSymbol]: arrayIterator } = Array.prototype; + const { [ITERATOR]: arrayIterator } = arrayPrototype; const arrayIteratorPrototype = Object.getPrototypeOf( - [][iteratorSymbol](), + [][ITERATOR](), ); const { next: arrayIteratorNext } = arrayIteratorPrototype; const iteratorPrototype = Object.getPrototypeOf( arrayIteratorPrototype, ); - const { [iteratorSymbol]: stringIterator } = String.prototype; + const { [ITERATOR]: stringIterator } = stringPrototype; const stringIteratorPrototype = Object.getPrototypeOf( - ""[iteratorSymbol](), + ""[ITERATOR](), ); const { next: stringIteratorNext } = stringIteratorPrototype; @@ -413,7 +420,7 @@ export const { value: stringCodeValueIteratorNext, writable: true, }, - [toStringTagSymbol]: { + [TO_STRING_TAG]: { configurable: true, enumerable: false, value: "String Code Value Iterator", @@ -422,7 +429,7 @@ export const { }, ); const scalarValueIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind( stringCodeValueIteratorNext, @@ -460,16 +467,16 @@ export const { /** * Returns an iterator over the codepoints in the string representation * of the provided value according to the algorithm of - * String::[Symbol.iterator]. + * `String::[Symbol.iterator]`. */ export const characters = makeCallable( - String.prototype[Symbol.iterator], + stringPrototype[ITERATOR], ); /** * Returns the character at the provided position in the string * representation of the provided value according to the algorithm of - * String::codePointAt. + * `String::codePointAt`. */ export const getCharacter = ($, pos) => { const codepoint = getCodepoint($, pos); @@ -481,33 +488,33 @@ export const getCharacter = ($, pos) => { /** * Returns the code unit at the provided position in the string * representation of the provided value according to the algorithm of - * String::charAt. + * `String::charAt`. */ -export const getCodeUnit = makeCallable(String.prototype.charCodeAt); +export const getCodeUnit = makeCallable(stringPrototype.charCodeAt); /** * Returns the codepoint at the provided position in the string * representation of the provided value according to the algorithm of - * String::codePointAt. + * `String::codePointAt`. */ -export const getCodepoint = makeCallable(String.prototype.codePointAt); +export const getCodepoint = makeCallable(stringPrototype.codePointAt); /** * Returns the index of the first occurrence of the search string in * the string representation of the provided value according to the - * algorithm of String::indexOf. + * algorithm of `String::indexOf`. */ export const getFirstSubstringIndex = makeCallable( - String.prototype.indexOf, + stringPrototype.indexOf, ); /** * Returns the index of the last occurrence of the search string in the * string representation of the provided value according to the - * algorithm of String::lastIndexOf. + * algorithm of `String::lastIndexOf`. */ export const getLastSubstringIndex = makeCallable( - String.prototype.lastIndexOf, + stringPrototype.lastIndexOf, ); /** @@ -518,7 +525,7 @@ export const getLastSubstringIndex = makeCallable( * If a value is nullish, it will be stringified as the empty string. */ export const join = (() => { - const { join: arrayJoin } = Array.prototype; + const { join: arrayJoin } = arrayPrototype; const join = ($, separator = ",") => call(arrayJoin, [...$], [`${separator}`]); return join; @@ -529,21 +536,21 @@ export const { * Returns a string created from the raw value of the tagged template * literal. * - * ※ This is an alias for String.raw. + * ※ This is an alias for `String.raw`. */ raw: rawString, /** * Returns a string created from the provided code units. * - * ※ This is an alias for String.fromCharCode. + * ※ This is an alias for `String.fromCharCode`. */ fromCharCode: stringFromCodeUnits, /** * Returns a string created from the provided codepoints. * - * ※ This is an alias for String.fromCodePoint. + * ※ This is an alias for `String.fromCodePoint`. */ fromCodePoint: stringFromCodepoints, } = String; @@ -574,110 +581,110 @@ export const splitOnCommas = ($) => /** * Returns the result of catenating the string representations of the * provided values, returning a new string according to the algorithm - * of String::concat. + * of `String::concat`. */ -export const stringCatenate = makeCallable(String.prototype.concat); +export const stringCatenate = makeCallable(stringPrototype.concat); /** * Returns whether the string representation of the provided value ends * with the provided search string according to the algorithm of - * String::endsWith. + * `String::endsWith`. */ -export const stringEndsWith = makeCallable(String.prototype.endsWith); +export const stringEndsWith = makeCallable(stringPrototype.endsWith); /** * Returns whether the string representation of the provided value * contains the provided search string according to the algorithm of - * String::includes. + * `String::includes`. */ -export const stringIncludes = makeCallable(String.prototype.includes); +export const stringIncludes = makeCallable(stringPrototype.includes); /** * Returns the result of matching the string representation of the * provided value with the provided matcher according to the algorithm - * of String::match. + * of `String::match`. */ -export const stringMatch = makeCallable(String.prototype.match); +export const stringMatch = makeCallable(stringPrototype.match); /** * Returns the result of matching the string representation of the * provided value with the provided matcher according to the algorithm - * of String::matchAll. + * of `String::matchAll`. */ -export const stringMatchAll = makeCallable(String.prototype.matchAll); +export const stringMatchAll = makeCallable(stringPrototype.matchAll); /** * Returns the normalized form of the string representation of the - * provided value according to the algorithm of String::matchAll. + * provided value according to the algorithm of `String::matchAll`. */ export const stringNormalize = makeCallable( - String.prototype.normalize, + stringPrototype.normalize, ); /** * Returns the result of padding the end of the string representation * of the provided value padded until it is the desired length - * according to the algorithm of String::padEnd. + * according to the algorithm of `String::padEnd`. */ -export const stringPadEnd = makeCallable(String.prototype.padEnd); +export const stringPadEnd = makeCallable(stringPrototype.padEnd); /** * Returns the result of padding the start of the string representation * of the provided value padded until it is the desired length - * according to the algorithm of String::padStart. + * according to the algorithm of `String::padStart`. */ -export const stringPadStart = makeCallable(String.prototype.padStart); +export const stringPadStart = makeCallable(stringPrototype.padStart); /** * Returns the result of repeating the string representation of the * provided value the provided number of times according to the - * algorithm of String::repeat. + * algorithm of `String::repeat`. */ -export const stringRepeat = makeCallable(String.prototype.repeat); +export const stringRepeat = makeCallable(stringPrototype.repeat); /** * Returns the result of replacing the string representation of the * provided value with the provided replacement, using the provided - * matcher and according to the algorithm of String::replace. + * matcher and according to the algorithm of `String::replace`. */ -export const stringReplace = makeCallable(String.prototype.replace); +export const stringReplace = makeCallable(stringPrototype.replace); /** * Returns the result of replacing the string representation of the * provided value with the provided replacement, using the provided - * matcher and according to the algorithm of String::replaceAll. + * matcher and according to the algorithm of `String::replaceAll`. */ export const stringReplaceAll = makeCallable( - String.prototype.replaceAll, + stringPrototype.replaceAll, ); /** * Returns the result of searching the string representation of the * provided value using the provided matcher and according to the - * algorithm of String::search. + * algorithm of `String::search`. */ -export const stringSearch = makeCallable(String.prototype.search); +export const stringSearch = makeCallable(stringPrototype.search); /** * Returns a slice of the string representation of the provided value - * according to the algorithm of String::slice. + * according to the algorithm of `String::slice`. */ -export const stringSlice = makeCallable(String.prototype.slice); +export const stringSlice = makeCallable(stringPrototype.slice); /** * Returns the result of splitting of the string representation of the * provided value on the provided separator according to the algorithm - * of String::split. + * of `String::split`. */ -export const stringSplit = makeCallable(String.prototype.split); +export const stringSplit = makeCallable(stringPrototype.split); /** * Returns whether the string representation of the provided value * starts with the provided search string according to the algorithm of - * String::startsWith. + * `String::startsWith`. */ export const stringStartsWith = makeCallable( - String.prototype.startsWith, + stringPrototype.startsWith, ); /** @@ -686,7 +693,7 @@ export const stringStartsWith = makeCallable( * ☡ This function will throw if the provided object does not have a * `[[StringData]]` internal slot. */ -export const stringValue = makeCallable(String.prototype.valueOf); +export const stringValue = makeCallable(stringPrototype.valueOf); /** * Returns the result of stripping leading and trailing A·S·C·I·I @@ -706,17 +713,14 @@ export const stripAndCollapseASCIIWhitespace = ($) => * Returns the result of stripping leading and trailing A·S·C·I·I * whitespace from the string representation of the provided value. */ -export const stripLeadingAndTrailingASCIIWhitespace = (() => { - const { exec: reExec } = RegExp.prototype; - return ($) => - call(reExec, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1]; -})(); +export const stripLeadingAndTrailingASCIIWhitespace = ($) => + call(reExec, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1]; /** * Returns a substring of the string representation of the provided - * value according to the algorithm of String::substring. + * value according to the algorithm of `String::substring`. */ -export const substring = makeCallable(String.prototype.substring); +export const substring = makeCallable(stringPrototype.substring); /** * Returns the result of converting the provided value to a string. diff --git a/string.test.js b/string.test.js index 95c715e..e3d1096 100644 --- a/string.test.js +++ b/string.test.js @@ -10,7 +10,6 @@ import { assert, assertEquals, - assertSpyCall, assertSpyCalls, assertStrictEquals, assertThrows, @@ -101,6 +100,7 @@ describe("Matcher", () => { return "etaoin"; }, }); + assertEquals([...result], ["etaoin", "e"]); assertSpyCalls(constraint, 1); assertStrictEquals(constraint.calls[0].args[0], "etaoin"); assertEquals([...constraint.calls[0].args[1]], ["etaoin", "e"]); diff --git a/value.js b/value.js index 77cb6b4..3f0c6fa 100644 --- a/value.js +++ b/value.js @@ -1,13 +1,49 @@ // ♓🌟 Piscēs ∷ value.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022‐2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . -import { call } from "./function.js"; +export const { + /** The welknown `@@asyncIterator` symbol. */ + asyncIterator: ASYNC_ITERATOR, + + /** The welknown `@@hasInstance` symbol. */ + hasInstance: HAS_INSTANCE, + + /** The welknown `@@isConcatSpreadable` symbol. */ + isConcatSpreadable: IS_CONCAT_SPREADABLE, + + /** The welknown `@@iterator` symbol. */ + iterator: ITERATOR, + + /** The welknown `@@match` symbol. */ + match: MATCH, + + /** The welknown `@@matchAll` symbol. */ + matchAll: MATCH_ALL, + + /** The welknown `@@replace` symbol. */ + replace: REPLACE, + + /** The welknown `@@species` symbol. */ + species: SPECIES, + + /** The welknown `@@split` symbol. */ + split: SPLIT, + + /** The welknown `@@toPrimitive` symbol. */ + toPrimitive: TO_PRIMITIVE, + + /** The welknown `@@toStringTag` symbol. */ + toStringTag: TO_STRING_TAG, + + /** The welknown `@@unscopables` symbol. */ + unscopables: UNSCOPABLES, +} = Symbol; /** The null primitive. */ export const NULL = null; @@ -18,10 +54,10 @@ export const UNDEFINED = undefined; export const { /** * Returns the primitive value of the provided object per its - * `toString` and `valueOf` methods. + * `.toString` and `.valueOf` methods. * - * If the provided hint is "string", then `toString` takes - * precedence; otherwise, `valueOf` does. + * If the provided hint is "string", then `.toString` takes + * precedence; otherwise, `.valueOf` does. * * Throws an error if both of these methods are not callable or do * not return a primitive. @@ -34,13 +70,13 @@ export const { * * The provided preferred type, if specified, should be "string", * "number", or "default". If the provided input has a - * `[Symbol.toPrimitive]` method, this function will throw rather + * `.[Symbol.toPrimitive]` method, this function will throw rather * than calling that method with a preferred type other than one of * the above. */ toPrimitive, } = (() => { - const { toPrimitive: toPrimitiveSymbol } = Symbol; + const { apply: call } = Reflect; return { ordinaryToPrimitive: (O, hint) => { @@ -80,14 +116,14 @@ export const { ); } else if (type($) === "object") { // The provided value is an object. - const exoticToPrim = $[toPrimitiveSymbol] ?? undefined; + const exoticToPrim = $[TO_PRIMITIVE] ?? undefined; if (exoticToPrim !== undefined) { // The provided value has an exotic primitive conversion // method. if (typeof exoticToPrim !== "function") { // The method is not callable. throw new TypeError( - "Piscēs: `[Symbol.toPrimitive]` was neither nullish nor callable.", + "Piscēs: `.[Symbol.toPrimitive]` was neither nullish nor callable.", ); } else { // The method is callable. diff --git a/value.test.js b/value.test.js index 6729b85..df86778 100644 --- a/value.test.js +++ b/value.test.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ value.test.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -15,27 +15,114 @@ import { it, } from "./dev-deps.js"; import { + ASYNC_ITERATOR, + HAS_INSTANCE, + IS_CONCAT_SPREADABLE, + ITERATOR, + MATCH, + MATCH_ALL, NULL, ordinaryToPrimitive, + REPLACE, sameValue, sameValueZero, + SPECIES, + SPLIT, + TO_PRIMITIVE, + TO_STRING_TAG, toPrimitive, type, UNDEFINED, + UNSCOPABLES, } from "./value.js"; +describe("ASYNC_ITERATOR", () => { + it("[[Get]] is @@asyncIterator", () => { + assertStrictEquals(ASYNC_ITERATOR, Symbol.asyncIterator); + }); +}); + +describe("HAS_INSTANCE", () => { + it("[[Get]] is @@hasInstance", () => { + assertStrictEquals(HAS_INSTANCE, Symbol.hasInstance); + }); +}); + +describe("IS_CONCAT_SPREADABLE", () => { + it("[[Get]] is @@isConcatSpreadable", () => { + assertStrictEquals( + IS_CONCAT_SPREADABLE, + Symbol.isConcatSpreadable, + ); + }); +}); + +describe("ITERATOR", () => { + it("[[Get]] is @@iterator", () => { + assertStrictEquals(ITERATOR, Symbol.iterator); + }); +}); + +describe("MATCH", () => { + it("[[Get]] is @@match", () => { + assertStrictEquals(MATCH, Symbol.match); + }); +}); + +describe("MATCH_ALL", () => { + it("[[Get]] is @@matchAll", () => { + assertStrictEquals(MATCH_ALL, Symbol.matchAll); + }); +}); + describe("NULL", () => { it("[[Get]] is null", () => { assertStrictEquals(NULL, null); }); }); +describe("REPLACE", () => { + it("[[Get]] is @@replace", () => { + assertStrictEquals(REPLACE, Symbol.replace); + }); +}); + +describe("SPECIES", () => { + it("[[Get]] is @@species", () => { + assertStrictEquals(SPECIES, Symbol.species); + }); +}); + +describe("SPLIT", () => { + it("[[Get]] is @@split", () => { + assertStrictEquals(SPLIT, Symbol.split); + }); +}); + +describe("TO_PRIMITIVE", () => { + it("[[Get]] is @@toPrimitive", () => { + assertStrictEquals(TO_PRIMITIVE, Symbol.toPrimitive); + }); +}); + +describe("TO_STRING_TAG", () => { + it("[[Get]] is @@toStringTag", () => { + assertStrictEquals(TO_STRING_TAG, Symbol.toStringTag); + }); +}); + describe("UNDEFINED", () => { it("[[Get]] is undefined", () => { assertStrictEquals(UNDEFINED, void {}); }); }); +describe("UNSCOPABLES", () => { + it("[[Get]] is @@unscopables", () => { + assertStrictEquals(UNSCOPABLES, Symbol.unscopables); + }); +}); + describe("ordinaryToPrimitive", () => { it("[[Call]] prefers `valueOf` by default", () => { const obj = {