X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/6f1ff895670d04034ef09faea4779923a85097fb..83f6aae0d1b8181dc2b0c6ccdba9f2fe2fdba3e6:/collection.js diff --git a/collection.js b/collection.js index 10172e6..3cfe938 100644 --- a/collection.js +++ b/collection.js @@ -1,25 +1,46 @@ // ♓🌟 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 // 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"; + +const { prototype: arrayPrototype } = Array; + +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 + * 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. * * There is no clamping of the numeric index, but note that numbers - * above 2^53 − 1 are not safe nor valid integer indices. + * 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,13 +50,161 @@ export const canonicalNumericIndexString = ($) => { } }; -/** Returns whether the provided value is an array index. */ -export const isArrayIndex = ($) => { +/** + * Returns the result of catenating the provided arraylikes into a new + * collection according to the algorithm of `Array::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`. + */ +export const copyWithin = makeCallable(arrayPrototype.copyWithin); + +/** + * Fills the provided object with the provided value according to the + * algorithm of `Array::fill`. + */ +export const fill = makeCallable(arrayPrototype.fill); + +/** + * Returns the result of filtering the provided object with the + * provided callback, according to the algorithm of `Array::filter`. + */ +export const filter = makeCallable(arrayPrototype.filter); + +/** + * Returns the first index in the provided object whose value satisfies + * the provided callback according to the algorithm of + * `Array::findIndex`. + */ +export const findIndex = makeCallable(arrayPrototype.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, according to the algorithm of + * `Array::find`. + */ +export const findItem = makeCallable(arrayPrototype.find); + +/** + * Returns the result of flatmapping the provided value with the + * provided callback according to the algorithm of `Array::flatMap`. + */ +export const flatmap = makeCallable(arrayPrototype.flatMap); + +/** + * Returns the result of flattening the provided object according to + * the algorithm of `Array::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`. + */ +export const getFirstIndex = makeCallable(arrayPrototype.indexOf); + +/** + * Returns the item on the provided object at the provided index + * according to the algorithm of `Array::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`. + */ +export const getLastIndex = makeCallable(arrayPrototype.lastIndexOf); + +/** + * Returns whether every indexed value in the provided object satisfies + * the provided function, according to the algorithm of `Array::every`. + */ +export const hasEvery = makeCallable(arrayPrototype.every); + +/** + * Returns whether the provided object has an indexed value which + * satisfies the provided function, according to the algorithm of + * `Array::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`. + * + * ※ This algorithm treats missing values as `undefined` rather than + * skipping them. + */ +export const includes = makeCallable(arrayPrototype.includes); + +/** + * Returns an iterator over the indexed entries in the provided value + * according to the algorithm of `Array::entries`. + */ +export const indexedEntries = makeCallable(arrayPrototype.entries); + +/** + * Returns an iterator over the indices in the provided value according + * to the algorithm of `Array::keys`. + */ +export const indices = makeCallable(arrayPrototype.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; + } } }; @@ -44,18 +213,17 @@ export const isArrayIndex = ($) => { * * The definition of “collection” used by Piscēs is similar to * Ecmascript’s definition of an arraylike object, but it differs in - * a few ways :— + * a few ways :— * * - 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. + * 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 +244,107 @@ 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(arrayPrototype.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 according to the algorithm of `Array::map`. + */ +export const map = makeCallable(arrayPrototype.map); + +/** + * Pops from the provided value according to the algorithm of + * `Array::pop`. + */ +export const pop = makeCallable(arrayPrototype.pop); + +/** + * Pushes onto the provided value according to the algorithm of + * `Array::push`. + */ +export const push = makeCallable(arrayPrototype.push); + +/** + * Returns the result of reducing the provided value with the provided + * callback, according to the algorithm of `Array::reduce`. + */ +export const reduce = makeCallable(arrayPrototype.reduce); + +/** + * Reverses the provided value according to the algorithm of + * `Array::reverse`. + */ +export const reverse = makeCallable(arrayPrototype.reverse); + +/** + * Shifts the provided value according to the algorithm of + * `Array::shift`. + */ +export const shift = makeCallable(arrayPrototype.shift); + +/** + * Returns a slice of the provided value according to the algorithm of + * `Array::slice`. + */ +export const slice = makeCallable(arrayPrototype.slice); + +/** + * Sorts the provided value in‐place according to the algorithm of + * `Array::sort`. + */ +export const sort = makeCallable(arrayPrototype.sort); /** - * Converts the provided value to an array index, or throws an error if - * it is out of range. + * Splices into and out of the provided value according to the + * algorithm of `Array::splice`. + */ +export const splice = makeCallable(arrayPrototype.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 +360,16 @@ 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 according to the algorithm of + * `Array::unshift`. + */ +export const unshift = makeCallable(arrayPrototype.unshift);