]> Lady’s Gitweb - Pisces/blob - collection.js
10172e643b29fbe5b0dc741f531fc28d1f9f4875
[Pisces] / collection.js
1 // ♓🌟 Piscēs ∷ collection.js
2 // ====================================================================
3 //
4 // Copyright © 2020–2022 Lady [@ Lady’s Computer].
5 //
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/>.
9
10 import { MAX_SAFE_INTEGER } from "./numeric.js";
11 import { isObject, sameValue } from "./object.js";
12
13 /**
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.
17 *
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.
20 */
21 export const canonicalNumericIndexString = ($) => {
22 if (typeof $ != "string") {
23 return undefined;
24 } else if ($ === "-0") {
25 return -0;
26 } else {
27 const n = +$;
28 return $ === `${n}` ? n : undefined;
29 }
30 };
31
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;
37 } else {
38 return false;
39 }
40 };
41
42 /**
43 * Returns whether the provided object is a collection.
44 *
45 * The definition of “collection” used by Piscēs is similar to
46 * Ecmascript’s definition of an arraylike object, but it differs in
47 * a few ways :—
48 *
49 * - It requires the provided value to be a proper object.
50 *
51 * - It requires the `length` property to be an integer corresponding
52 * to an integer index.
53 *
54 * - It requires the object to be concat‐spreadable, meaning it must
55 * either be an array or have `[Symbol.isConcatSpreadable]` be true.
56 */
57 export const isCollection = ($) => {
58 if (!(isObject($) && "length" in $)) {
59 // The provided value is not an object or does not have a `length`.
60 return false;
61 } else {
62 try {
63 toIndex($.length); // will throw if `length` is not an index
64 return isConcatSpreadable($);
65 } catch {
66 return false;
67 }
68 }
69 };
70
71 /**
72 * Returns whether the provided value is spreadable during array
73 * concatenation.
74 *
75 * This is also used to determine which things should be treated as
76 * collections.
77 */
78 export const isConcatSpreadable = ($) => {
79 if (!isObject($)) {
80 // The provided value is not an object.
81 return false;
82 } else {
83 // The provided value is an object.
84 return !!($[Symbol.isConcatSpreadable] ?? Array.isArray($));
85 }
86 };
87
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;
94 } else {
95 return false;
96 }
97 };
98
99 /**
100 * Returns the length of the provided arraylike object.
101 *
102 * Will throw if the provided object is not arraylike.
103 *
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.
107 */
108 export const lengthOfArrayLike = ({ length }) => {
109 return toLength(length);
110 };
111
112 /**
113 * Converts the provided value to an array index, or throws an error if
114 * it is out of range.
115 */
116 export const toIndex = ($) => {
117 const integer = Math.floor($);
118 if (isNaN(integer) || integer == 0) {
119 // The value is zero·like.
120 return 0;
121 } else {
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: ${$}.`);
127 } else {
128 // The value is within appropriate bounds.
129 return integer;
130 }
131 }
132 };
133
134 /** Converts the provided value to a length. */
135 export const toLength = ($) => {
136 const len = Math.floor($);
137 return isNaN(len) || len == 0
138 ? 0
139 : Math.max(Math.min(len, MAX_SAFE_INTEGER), 0);
140 };
This page took 0.080667 seconds and 3 git commands to generate.