1 // ♓🌟 Piscēs ∷ collection.js
2 // ====================================================================
4 // Copyright © 2020–2023 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
10 import { call
, createCallableFunction
} from "./function.js";
16 MAXIMUM_SAFE_INTEGRAL_NUMBER
,
18 } from "./numeric.js";
19 import { sameValue
, type
} from "./value.js";
21 const { prototype: arrayPrototype
} = Array
;
24 /** Returns an array of the provided values. */
27 /** Returns whether the provided value is an array. */
30 /** Returns an array created from the provided arraylike. */
35 * Returns −0 if the provided argument is "-0"; returns a number
36 * representing the index if the provided argument is a canonical
37 * numeric index string; otherwise, returns undefined.
39 * There is no clamping of the numeric index, but note that numbers
40 * above 2^53 − 1 are not safe nor valid integer indices.
42 export const canonicalNumericIndexString
= ($) => {
43 if (typeof $ !== "string") {
45 } else if ($ === "-0") {
49 return $ === `${n}` ? n
: undefined;
54 * Returns the result of catenating the provided arraylikes into a new
55 * collection according to the algorithm of `Array::concat`.
57 export const catenate
= createCallableFunction(
58 arrayPrototype
.concat
,
63 * Copies the items in the provided object to a new location according
64 * to the algorithm of `Array::copyWithin`.
66 export const copyWithin
= createCallableFunction(
67 arrayPrototype
.copyWithin
,
71 * Fills the provided object with the provided value according to the
72 * algorithm of `Array::fill`.
74 export const fill
= createCallableFunction(arrayPrototype
.fill
);
77 * Returns the result of filtering the provided object with the
78 * provided callback, according to the algorithm of `Array::filter`.
80 export const filter
= createCallableFunction(arrayPrototype
.filter
);
83 * Returns the first index in the provided object whose value satisfies
84 * the provided callback according to the algorithm of
87 export const findIndex
= createCallableFunction(
88 arrayPrototype
.findIndex
,
92 * Returns the first indexed entry in the provided object whose value
93 * satisfies the provided callback.
95 * If a third argument is supplied, it will be used as the this value
98 export const findIndexedEntry
= (
103 let result
= undefined;
104 findItem($, (kValue
, k
, O
) => {
105 if (call(callback
, thisArg
, [kValue
, k
, O
])) {
106 // The callback succeeded.
107 result
= [k
, kValue
];
110 // The callback failed.
118 * Returns the first indexed value in the provided object which
119 * satisfies the provided callback, according to the algorithm of
122 export const findItem
= createCallableFunction(
128 * Returns the result of flatmapping the provided value with the
129 * provided callback according to the algorithm of `Array::flatMap`.
131 export const flatmap
= createCallableFunction(
132 arrayPrototype
.flatMap
,
137 * Returns the result of flattening the provided object according to
138 * the algorithm of `Array::flat`.
140 export const flatten
= createCallableFunction(
146 * Returns the first index of the provided object with a value
147 * equivalent to the provided value according to the algorithm of
150 export const getFirstIndex
= createCallableFunction(
151 arrayPrototype
.indexOf
,
156 * Returns the item on the provided object at the provided index
157 * according to the algorithm of `Array::at`.
159 export const getItem
= createCallableFunction(
165 * Returns the last index of the provided object with a value
166 * equivalent to the provided value according to the algorithm of
167 * `Array::lastIndexOf`.
169 export const getLastIndex
= createCallableFunction(
170 arrayPrototype
.lastIndexOf
,
175 * Returns whether every indexed value in the provided object satisfies
176 * the provided function, according to the algorithm of `Array::every`.
178 export const hasEvery
= createCallableFunction(
179 arrayPrototype
.every
,
184 * Returns whether the provided object has an indexed value which
185 * satisfies the provided function, according to the algorithm of
188 export const hasSome
= createCallableFunction(
194 * Returns whether the provided object has an indexed value equivalent
195 * to the provided value according to the algorithm of
198 * ※ This algorithm treats missing values as `undefined` rather than
201 export const includes
= createCallableFunction(
202 arrayPrototype
.includes
,
206 * Returns an iterator over the indexed entries in the provided value
207 * according to the algorithm of `Array::entries`.
209 export const indexedEntries
= createCallableFunction(
210 arrayPrototype
.entries
,
215 * Returns an iterator over the indices in the provided value according
216 * to the algorithm of `Array::keys`.
218 export const indices
= createCallableFunction(
223 /** Returns whether the provided value is an array index string. */
224 export const isArrayIndexString
= ($) => {
225 const value
= canonicalNumericIndexString($);
226 if (value
!== undefined) {
227 // The provided value is a canonical numeric index string.
228 return sameValue(value
, 0) || value
> 0 && value
< -1 >>> 0 &&
229 value
=== toLength(value
);
231 // The provided value is not a canonical numeric index string.
236 /** Returns whether the provided value is arraylike. */
237 export const isArraylikeObject
= ($) => {
238 if (type($) !== "object") {
242 lengthOfArraylike($); // throws if not arraylike
251 * Returns whether the provided object is a collection.
253 * The definition of “collection” used by Piscēs is similar to
254 * Ecmascript’s definition of an arraylike object, but it differs in
257 * - It requires the provided value to be a proper object.
259 * - It requires the `length` property to be an integer index.
261 * - It requires the object to be concat‐spreadable, meaning it must
262 * either be an array or have `.[Symbol.isConcatSpreadable]` be true.
264 export const isCollection
= ($) => {
265 if (!(type($) === "object" && "length" in $)) {
266 // The provided value is not an object or does not have a `length`.
270 toIndex($.length
); // will throw if `length` is not an index
271 return isConcatSpreadable($);
279 * Returns whether the provided value is spreadable during array
282 * This is also used to determine which things should be treated as
285 export const isConcatSpreadable
= ($) => {
286 if (type($) !== "object") {
287 // The provided value is not an object.
290 // The provided value is an object.
291 const spreadable
= $[Symbol
.isConcatSpreadable
];
292 return spreadable
!== undefined ? !!spreadable
: isArray($);
296 /** Returns whether the provided value is an integer index string. */
297 export const isIntegerIndexString
= ($) => {
298 const value
= canonicalNumericIndexString($);
299 if (value
!== undefined && isIntegralNumber(value
)) {
300 // The provided value is a canonical numeric index string.
301 return sameValue(value
, 0) ||
302 value
> 0 && value
<= MAXIMUM_SAFE_INTEGRAL_NUMBER
&&
303 value
=== toLength(value
);
305 // The provided value is not a canonical numeric index string.
311 * Returns an iterator over the items in the provided value according
312 * to the algorithm of `Array::values`.
314 export const items
= createCallableFunction(
315 arrayPrototype
.values
,
320 * Returns the length of the provided arraylike object.
322 * Will throw if the provided object is not arraylike.
324 * This can produce larger lengths than can actually be stored in
325 * arrays, because no such restrictions exist on arraylike methods.
327 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
330 * Returns the result of mapping the provided value with the provided
331 * callback according to the algorithm of `Array::map`.
333 export const map
= createCallableFunction(arrayPrototype
.map
);
336 * Pops from the provided value according to the algorithm of
339 export const pop
= createCallableFunction(arrayPrototype
.pop
);
342 * Pushes onto the provided value according to the algorithm of
345 export const push
= createCallableFunction(arrayPrototype
.push
);
348 * Returns the result of reducing the provided value with the provided
349 * callback, according to the algorithm of `Array::reduce`.
351 export const reduce
= createCallableFunction(arrayPrototype
.reduce
);
354 * Reverses the provided value according to the algorithm of
357 export const reverse
= createCallableFunction(arrayPrototype
.reverse
);
360 * Shifts the provided value according to the algorithm of
363 export const shift
= createCallableFunction(arrayPrototype
.shift
);
366 * Returns a slice of the provided value according to the algorithm of
369 export const slice
= createCallableFunction(arrayPrototype
.slice
);
372 * Sorts the provided value in‐place according to the algorithm of
375 export const sort
= createCallableFunction(arrayPrototype
.sort
);
378 * Splices into and out of the provided value according to the
379 * algorithm of `Array::splice`.
381 export const splice
= createCallableFunction(arrayPrototype
.splice
);
384 * Returns the result of converting the provided value to an array
385 * index, or throws an error if it is out of range.
387 export const toIndex
= ($) => {
388 const integer
= floor($);
389 if (isNan(integer
) || integer
== 0) {
390 // The value is zero·like.
393 // The value is not zero·like.
394 const clamped
= toLength(integer
);
395 if (clamped
!== integer
) {
396 // Clamping the value changes it.
397 throw new RangeError(`Piscēs: Index out of range: ${$}.`);
399 // The value is within appropriate bounds.
405 /** Returns the result of converting the provided value to a length. */
406 export const toLength
= ($) => {
407 const len
= floor($);
408 return isNan(len
) || len
== 0
410 : max(min(len
, MAXIMUM_SAFE_INTEGRAL_NUMBER
), 0);
414 * Unshifts the provided value according to the algorithm of
417 export const unshift
= createCallableFunction(arrayPrototype
.unshift
);