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
, makeCallable
} 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
= makeCallable(arrayPrototype
.concat
);
60 * Copies the items in the provided object to a new location according
61 * to the algorithm of `Array::copyWithin`.
63 export const copyWithin
= makeCallable(arrayPrototype
.copyWithin
);
66 * Fills the provided object with the provided value according to the
67 * algorithm of `Array::fill`.
69 export const fill
= makeCallable(arrayPrototype
.fill
);
72 * Returns the result of filtering the provided object with the
73 * provided callback, according to the algorithm of `Array::filter`.
75 export const filter
= makeCallable(arrayPrototype
.filter
);
78 * Returns the first index in the provided object whose value satisfies
79 * the provided callback according to the algorithm of
82 export const findIndex
= makeCallable(arrayPrototype
.findIndex
);
85 * Returns the first indexed entry in the provided object whose value
86 * satisfies the provided callback.
88 * If a third argument is supplied, it will be used as the this value
91 export const findIndexedEntry
= (
96 let result
= undefined;
97 findItem($, (kValue
, k
, O
) => {
98 if (call(callback
, thisArg
, [kValue
, k
, O
])) {
99 // The callback succeeded.
100 result
= [k
, kValue
];
103 // The callback failed.
111 * Returns the first indexed value in the provided object which
112 * satisfies the provided callback, according to the algorithm of
115 export const findItem
= makeCallable(arrayPrototype
.find
);
118 * Returns the result of flatmapping the provided value with the
119 * provided callback according to the algorithm of `Array::flatMap`.
121 export const flatmap
= makeCallable(arrayPrototype
.flatMap
);
124 * Returns the result of flattening the provided object according to
125 * the algorithm of `Array::flat`.
127 export const flatten
= makeCallable(arrayPrototype
.flat
);
130 * Returns the first index of the provided object with a value
131 * equivalent to the provided value according to the algorithm of
134 export const getFirstIndex
= makeCallable(arrayPrototype
.indexOf
);
137 * Returns the item on the provided object at the provided index
138 * according to the algorithm of `Array::at`.
140 export const getItem
= makeCallable(arrayPrototype
.at
);
143 * Returns the last index of the provided object with a value
144 * equivalent to the provided value according to the algorithm of
145 * `Array::lastIndexOf`.
147 export const getLastIndex
= makeCallable(arrayPrototype
.lastIndexOf
);
150 * Returns whether every indexed value in the provided object satisfies
151 * the provided function, according to the algorithm of `Array::every`.
153 export const hasEvery
= makeCallable(arrayPrototype
.every
);
156 * Returns whether the provided object has an indexed value which
157 * satisfies the provided function, according to the algorithm of
160 export const hasSome
= makeCallable(arrayPrototype
.some
);
163 * Returns whether the provided object has an indexed value equivalent
164 * to the provided value according to the algorithm of
167 * ※ This algorithm treats missing values as `undefined` rather than
170 export const includes
= makeCallable(arrayPrototype
.includes
);
173 * Returns an iterator over the indexed entries in the provided value
174 * according to the algorithm of `Array::entries`.
176 export const indexedEntries
= makeCallable(arrayPrototype
.entries
);
179 * Returns an iterator over the indices in the provided value according
180 * to the algorithm of `Array::keys`.
182 export const indices
= makeCallable(arrayPrototype
.keys
);
184 /** Returns whether the provided value is an array index string. */
185 export const isArrayIndexString
= ($) => {
186 const value
= canonicalNumericIndexString($);
187 if (value
!== undefined) {
188 // The provided value is a canonical numeric index string.
189 return sameValue(value
, 0) || value
> 0 && value
< -1 >>> 0 &&
190 value
=== toLength(value
);
192 // The provided value is not a canonical numeric index string.
197 /** Returns whether the provided value is arraylike. */
198 export const isArraylikeObject
= ($) => {
199 if (type($) !== "object") {
203 lengthOfArraylike($); // throws if not arraylike
212 * Returns whether the provided object is a collection.
214 * The definition of “collection” used by Piscēs is similar to
215 * Ecmascript’s definition of an arraylike object, but it differs in
218 * - It requires the provided value to be a proper object.
220 * - It requires the `length` property to be an integer index.
222 * - It requires the object to be concat‐spreadable, meaning it must
223 * either be an array or have `.[Symbol.isConcatSpreadable]` be true.
225 export const isCollection
= ($) => {
226 if (!(type($) === "object" && "length" in $)) {
227 // The provided value is not an object or does not have a `length`.
231 toIndex($.length
); // will throw if `length` is not an index
232 return isConcatSpreadable($);
240 * Returns whether the provided value is spreadable during array
243 * This is also used to determine which things should be treated as
246 export const isConcatSpreadable
= ($) => {
247 if (type($) !== "object") {
248 // The provided value is not an object.
251 // The provided value is an object.
252 const spreadable
= $[Symbol
.isConcatSpreadable
];
253 return spreadable
!== undefined ? !!spreadable
: isArray($);
257 /** Returns whether the provided value is an integer index string. */
258 export const isIntegerIndexString
= ($) => {
259 const value
= canonicalNumericIndexString($);
260 if (value
!== undefined && isIntegralNumber(value
)) {
261 // The provided value is a canonical numeric index string.
262 return sameValue(value
, 0) ||
263 value
> 0 && value
<= MAXIMUM_SAFE_INTEGRAL_NUMBER
&&
264 value
=== toLength(value
);
266 // The provided value is not a canonical numeric index string.
272 * Returns an iterator over the items in the provided value according
273 * to the algorithm of `Array::values`.
275 export const items
= makeCallable(arrayPrototype
.values
);
278 * Returns the length of the provided arraylike object.
280 * Will throw if the provided object is not arraylike.
282 * This can produce larger lengths than can actually be stored in
283 * arrays, because no such restrictions exist on arraylike methods.
285 export const lengthOfArraylike
= ({ length
}) => toLength(length
);
288 * Returns the result of mapping the provided value with the provided
289 * callback according to the algorithm of `Array::map`.
291 export const map
= makeCallable(arrayPrototype
.map
);
294 * Pops from the provided value according to the algorithm of
297 export const pop
= makeCallable(arrayPrototype
.pop
);
300 * Pushes onto the provided value according to the algorithm of
303 export const push
= makeCallable(arrayPrototype
.push
);
306 * Returns the result of reducing the provided value with the provided
307 * callback, according to the algorithm of `Array::reduce`.
309 export const reduce
= makeCallable(arrayPrototype
.reduce
);
312 * Reverses the provided value according to the algorithm of
315 export const reverse
= makeCallable(arrayPrototype
.reverse
);
318 * Shifts the provided value according to the algorithm of
321 export const shift
= makeCallable(arrayPrototype
.shift
);
324 * Returns a slice of the provided value according to the algorithm of
327 export const slice
= makeCallable(arrayPrototype
.slice
);
330 * Sorts the provided value in‐place according to the algorithm of
333 export const sort
= makeCallable(arrayPrototype
.sort
);
336 * Splices into and out of the provided value according to the
337 * algorithm of `Array::splice`.
339 export const splice
= makeCallable(arrayPrototype
.splice
);
342 * Returns the result of converting the provided value to an array
343 * index, or throws an error if it is out of range.
345 export const toIndex
= ($) => {
346 const integer
= floor($);
347 if (isNan(integer
) || integer
== 0) {
348 // The value is zero·like.
351 // The value is not zero·like.
352 const clamped
= toLength(integer
);
353 if (clamped
!== integer
) {
354 // Clamping the value changes it.
355 throw new RangeError(`Piscēs: Index out of range: ${$}.`);
357 // The value is within appropriate bounds.
363 /** Returns the result of converting the provided value to a length. */
364 export const toLength
= ($) => {
365 const len
= floor($);
366 return isNan(len
) || len
== 0
368 : max(min(len
, MAXIMUM_SAFE_INTEGRAL_NUMBER
), 0);
372 * Unshifts the provided value according to the algorithm of
375 export const unshift
= makeCallable(arrayPrototype
.unshift
);