1 // ♓🌟 Piscēs ∷ collection.js
2 // ====================================================================
4 // Copyright © 2020–2022 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 { MAX_SAFE_INTEGER
} from "./numeric.js";
11 import { isObject
, sameValue
} from "./object.js";
14 * Returns -0 if the provided argument is "-0"; returns a number
15 * representing the index if the provided argument is a canonical
16 * numeric index string; otherwise, returns undefined.
18 * There is no clamping of the numeric index, but note that numbers
19 * above 2^53 − 1 are not safe nor valid integer indices.
21 export const canonicalNumericIndexString
= ($) => {
22 if (typeof $ != "string") {
24 } else if ($ === "-0") {
28 return $ === `${n}` ? n
: undefined;
32 /** Returns whether the provided value is an array index. */
33 export const isArrayIndex
= ($) => {
34 const value
= canonicalNumericIndexString($);
35 if (value
!== undefined) {
36 return sameValue(value
, 0) || value
> 0 && value
< -1 >>> 0;
43 * Returns whether the provided object is a collection.
45 * The definition of “collection” used by Piscēs is similar to
46 * Ecmascript’s definition of an arraylike object, but it differs in
49 * - It requires the provided value to be a proper object.
51 * - It requires the `length` property to be an integer corresponding
52 * to an integer index.
54 * - It requires the object to be concat‐spreadable, meaning it must
55 * either be an array or have `[Symbol.isConcatSpreadable]` be true.
57 export const isCollection
= ($) => {
58 if (!(isObject($) && "length" in $)) {
59 // The provided value is not an object or does not have a `length`.
63 toIndex($.length
); // will throw if `length` is not an index
64 return isConcatSpreadable($);
72 * Returns whether the provided value is spreadable during array
75 * This is also used to determine which things should be treated as
78 export const isConcatSpreadable
= ($) => {
80 // The provided value is not an object.
83 // The provided value is an object.
84 return !!($[Symbol
.isConcatSpreadable
] ?? Array
.isArray($));
88 /** Returns whether the provided value is an integer index. */
89 export const isIntegerIndex
= ($) => {
90 const value
= canonicalNumericIndexString($);
91 if (value
!== undefined) {
92 return sameValue(value
, 0) ||
93 value
> 0 && value
<= MAX_SAFE_INTEGER
;
100 * Returns the length of the provided arraylike object.
102 * Will throw if the provided object is not arraylike.
104 * This produces larger lengths than can actually be stored in arrays,
105 * because no such restrictions exist on arraylike methods. Use
106 * `isIndex` to determine if a value is an actual array index.
108 export const lengthOfArrayLike
= ({ length
}) => {
109 return toLength(length
);
113 * Converts the provided value to an array index, or throws an error if
114 * it is out of range.
116 export const toIndex
= ($) => {
117 const integer
= Math
.floor($);
118 if (isNaN(integer
) || integer
== 0) {
119 // The value is zero·like.
122 // The value is not zero·like.
123 const clamped
= toLength(integer
);
124 if (clamped
!== integer
) {
125 // Clamping the value changes it.
126 throw new RangeError(`Piscēs: Index out of range: ${$}.`);
128 // The value is within appropriate bounds.
134 /** Converts the provided value to a length. */
135 export const toLength
= ($) => {
136 const len
= Math
.floor($);
137 return isNaN(len
) || len
== 0
139 : Math
.max(Math
.min(len
, MAX_SAFE_INTEGER
), 0);