X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/6f1ff895670d04034ef09faea4779923a85097fb..a63ba5294bd5cc847a192f6737bc386bfe7187f8:/collection.js diff --git a/collection.js b/collection.js index 10172e6..38b048c 100644 --- a/collection.js +++ b/collection.js @@ -7,8 +7,27 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . -import { MAX_SAFE_INTEGER } from "./numeric.js"; -import { isObject, sameValue } from "./object.js"; +import { call, makeCallable } from "./function.js"; +import { + floor, + isIntegralNumber, + isNan, + max, + MAXIMUM_SAFE_INTEGRAL_NUMBER, + min, +} from "./numeric.js"; +import { sameValue, type } from "./value.js"; + +export const { + /** Returns an array of the provided values. */ + of: array, + + /** Returns whether the provided value is an array. */ + isArray, + + /** Returns an array created from the provided arraylike. */ + from: toArray, +} = Array; /** * Returns -0 if the provided argument is "-0"; returns a number @@ -19,7 +38,7 @@ import { isObject, sameValue } from "./object.js"; * above 2^53 − 1 are not safe nor valid integer indices. */ export const canonicalNumericIndexString = ($) => { - if (typeof $ != "string") { + if (typeof $ !== "string") { return undefined; } else if ($ === "-0") { return -0; @@ -29,16 +48,160 @@ export const canonicalNumericIndexString = ($) => { } }; -/** Returns whether the provided value is an array index. */ -export const isArrayIndex = ($) => { +/** + * Returns the result of catenating the provided arraylikes, returning + * a new collection according to the algorithm of Array::concat. + */ +export const catenate = makeCallable(Array.prototype.concat); + +/** + * Copies the items in the provided object to a new location according + * to the algorithm of Array::copyWithin. + */ +export const copyWithin = makeCallable(Array.prototype.copyWithin); + +/** + * Fills the provided object with the provided value using the + * algorithm of Array::fill. + */ +export const fill = makeCallable(Array.prototype.fill); + +/** + * Returns the result of filtering the provided object with the + * provided callback, using the algorithm of Array::filter. + */ +export const filter = makeCallable(Array.prototype.filter); + +/** + * Returns the first index in the provided object whose value satisfies + * the provided callback using the algorithm of Array::findIndex. + */ +export const findIndex = makeCallable(Array.prototype.findIndex); + +/** + * Returns the first indexed entry in the provided object whose value + * satisfies the provided callback. + * + * If a third argument is supplied, it will be used as the this value + * of the callback. + */ +export const findIndexedEntry = ( + $, + callback, + thisArg = undefined, +) => { + let result = undefined; + findItem($, (kValue, k, O) => { + if (call(callback, thisArg, [kValue, k, O])) { + // The callback succeeded. + result = [k, kValue]; + return true; + } else { + // The callback failed. + return false; + } + }); + return result; +}; + +/** + * Returns the first indexed value in the provided object which + * satisfies the provided callback, using the algorithm of Array::find. + */ +export const findItem = makeCallable(Array.prototype.find); + +/** + * Returns the result of flatmapping the provided value with the + * provided callback using the algorithm of Array::flatMap. + */ +export const flatmap = makeCallable(Array.prototype.flatMap); + +/** + * Returns the result of flattening the provided object using the + * algorithm of Array::flat. + */ +export const flatten = makeCallable(Array.prototype.flat); + +/** + * Returns the first index of the provided object with a value + * equivalent to the provided value according to the algorithm of + * Array::indexOf. + */ +export const getFirstIndex = makeCallable(Array.prototype.indexOf); + +/** + * Returns the item on the provided object at the provided index using + * the algorithm of Array::at. + */ +export const getItem = makeCallable(Array.prototype.at); + +/** + * Returns the last index of the provided object with a value + * equivalent to the provided value according to the algorithm of + * Array::lastIndexOf. + */ +export const getLastIndex = makeCallable(Array.prototype.lastIndexOf); + +/** + * Returns whether every indexed value in the provided object satisfies + * the provided function, using the algorithm of Array::every. + */ +export const hasEvery = makeCallable(Array.prototype.every); + +/** + * Returns whether the provided object has an indexed value which + * satisfies the provided function, using the algorithm of Array::some. + */ +export const hasSome = makeCallable(Array.prototype.some); + +/** + * Returns whether the provided object has an indexed value equivalent + * to the provided value according to the algorithm of Array::includes. + * + * > ☡ This algorithm treats missing values as `undefined` rather than + * > skipping them. + */ +export const includes = makeCallable(Array.prototype.includes); + +/** + * Returns an iterator over the indexed entries in the provided value + * according to the algorithm of Array::entries. + */ +export const indexedEntries = makeCallable(Array.prototype.entries); + +/** + * Returns an iterator over the indices in the provided value according + * to the algorithm of Array::keys. + */ +export const indices = makeCallable(Array.prototype.keys); + +/** Returns whether the provided value is an array index string. */ +export const isArrayIndexString = ($) => { const value = canonicalNumericIndexString($); if (value !== undefined) { - return sameValue(value, 0) || value > 0 && value < -1 >>> 0; + // The provided value is a canonical numeric index string. + return sameValue(value, 0) || value > 0 && value < -1 >>> 0 && + value === toLength(value); } else { + // The provided value is not a canonical numeric index string. return false; } }; +/** Returns whether the provided value is arraylike. */ +export const isArraylikeObject = ($) => { + if (type($) !== "object") { + return false; + } else { + try { + lengthOfArraylike($); // throws if not arraylike + return true; + } catch { + return false; + } + } +}; + /** * Returns whether the provided object is a collection. * @@ -48,14 +211,13 @@ export const isArrayIndex = ($) => { * * - It requires the provided value to be a proper object. * - * - It requires the `length` property to be an integer corresponding - * to an integer index. + * - 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. */ export const isCollection = ($) => { - if (!(isObject($) && "length" in $)) { + if (!(type($) === "object" && "length" in $)) { // The provided value is not an object or does not have a `length`. return false; } else { @@ -76,46 +238,99 @@ export const isCollection = ($) => { * collections. */ export const isConcatSpreadable = ($) => { - if (!isObject($)) { + if (type($) !== "object") { // The provided value is not an object. return false; } else { // The provided value is an object. - return !!($[Symbol.isConcatSpreadable] ?? Array.isArray($)); + const spreadable = $[Symbol.isConcatSpreadable]; + return spreadable !== undefined ? !!spreadable : isArray($); } }; -/** Returns whether the provided value is an integer index. */ -export const isIntegerIndex = ($) => { +/** Returns whether the provided value is an integer index string. */ +export const isIntegerIndexString = ($) => { const value = canonicalNumericIndexString($); - if (value !== undefined) { + if (value !== undefined && isIntegralNumber(value)) { + // The provided value is a canonical numeric index string. return sameValue(value, 0) || - value > 0 && value <= MAX_SAFE_INTEGER; + value > 0 && value <= MAXIMUM_SAFE_INTEGRAL_NUMBER && + value === toLength(value); } else { + // The provided value is not a canonical numeric index string. return false; } }; +/** + * Returns an iterator over the items in the provided value according + * to the algorithm of Array::values. + */ +export const items = makeCallable(Array.prototype.values); + /** * Returns the length of the provided arraylike object. * * Will throw if the provided object is not arraylike. * - * This produces larger lengths than can actually be stored in arrays, - * because no such restrictions exist on arraylike methods. Use - * `isIndex` to determine if a value is an actual array index. + * This can produce larger lengths than can actually be stored in + * arrays, because no such restrictions exist on arraylike methods. */ -export const lengthOfArrayLike = ({ length }) => { - return toLength(length); -}; +export const lengthOfArraylike = ({ length }) => toLength(length); + +/** + * Returns the result of mapping the provided value with the provided + * callback using the algorithm of Array::map. + */ +export const map = makeCallable(Array.prototype.map); + +/** Pops from the provided value using the algorithm of Array::pop. */ +export const pop = makeCallable(Array.prototype.pop); + +/** + * Pushes onto the provided value using the algorithm of Array::push. + */ +export const push = makeCallable(Array.prototype.push); + +/** + * Returns the result of reducing the provided value with the provided + * callback, using the algorithm of Array::reduce. + */ +export const reduce = makeCallable(Array.prototype.reduce); + +/** + * Reverses the provided value using the algorithm of Array::reverse. + */ +export const reverse = makeCallable(Array.prototype.reverse); + +/** Shifts the provided value using the algorithm of Array::shift. */ +export const shift = makeCallable(Array.prototype.shift); /** - * Converts the provided value to an array index, or throws an error if - * it is out of range. + * Returns a slice of the provided value using the algorithm of + * Array::slice. + */ +export const slice = makeCallable(Array.prototype.slice); + +/** + * Sorts the provided value in‐place using the algorithm of + * Array::sort. + */ +export const sort = makeCallable(Array.prototype.sort); + +/** + * Splices into and out of the provided value using the algorithm of + * Array::splice. + */ +export const splice = makeCallable(Array.prototype.splice); + +/** + * Returns the result of converting the provided value to an array + * index, or throws an error if it is out of range. */ export const toIndex = ($) => { - const integer = Math.floor($); - if (isNaN(integer) || integer == 0) { + const integer = floor($); + if (isNan(integer) || integer == 0) { // The value is zero·like. return 0; } else { @@ -131,10 +346,15 @@ export const toIndex = ($) => { } }; -/** Converts the provided value to a length. */ +/** Returns the result of converting the provided value to a length. */ export const toLength = ($) => { - const len = Math.floor($); - return isNaN(len) || len == 0 + const len = floor($); + return isNan(len) || len == 0 ? 0 - : Math.max(Math.min(len, MAX_SAFE_INTEGER), 0); + : max(min(len, MAXIMUM_SAFE_INTEGRAL_NUMBER), 0); }; + +/** + * Unshifts the provided value using the algorithm of Array::unshift. + */ +export const unshift = makeCallable(Array.prototype.unshift);