]> Lady’s Gitweb - Pisces/blob - collection.js
3cfe9385acfbd44423d777c104ad590c8a4d6704
[Pisces] / collection.js
1 // ♓🌟 Piscēs ∷ collection.js
2 // ====================================================================
3 //
4 // Copyright © 2020–2023 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 { call, makeCallable } from "./function.js";
11 import {
12 floor,
13 isIntegralNumber,
14 isNan,
15 max,
16 MAXIMUM_SAFE_INTEGRAL_NUMBER,
17 min,
18 } from "./numeric.js";
19 import { sameValue, type } from "./value.js";
20
21 const { prototype: arrayPrototype } = Array;
22
23 export const {
24 /** Returns an array of the provided values. */
25 of: array,
26
27 /** Returns whether the provided value is an array. */
28 isArray,
29
30 /** Returns an array created from the provided arraylike. */
31 from: toArray,
32 } = Array;
33
34 /**
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.
38 *
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.
41 */
42 export const canonicalNumericIndexString = ($) => {
43 if (typeof $ !== "string") {
44 return undefined;
45 } else if ($ === "-0") {
46 return -0;
47 } else {
48 const n = +$;
49 return $ === `${n}` ? n : undefined;
50 }
51 };
52
53 /**
54 * Returns the result of catenating the provided arraylikes into a new
55 * collection according to the algorithm of `Array::concat`.
56 */
57 export const catenate = makeCallable(arrayPrototype.concat);
58
59 /**
60 * Copies the items in the provided object to a new location according
61 * to the algorithm of `Array::copyWithin`.
62 */
63 export const copyWithin = makeCallable(arrayPrototype.copyWithin);
64
65 /**
66 * Fills the provided object with the provided value according to the
67 * algorithm of `Array::fill`.
68 */
69 export const fill = makeCallable(arrayPrototype.fill);
70
71 /**
72 * Returns the result of filtering the provided object with the
73 * provided callback, according to the algorithm of `Array::filter`.
74 */
75 export const filter = makeCallable(arrayPrototype.filter);
76
77 /**
78 * Returns the first index in the provided object whose value satisfies
79 * the provided callback according to the algorithm of
80 * `Array::findIndex`.
81 */
82 export const findIndex = makeCallable(arrayPrototype.findIndex);
83
84 /**
85 * Returns the first indexed entry in the provided object whose value
86 * satisfies the provided callback.
87 *
88 * If a third argument is supplied, it will be used as the this value
89 * of the callback.
90 */
91 export const findIndexedEntry = (
92 $,
93 callback,
94 thisArg = undefined,
95 ) => {
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];
101 return true;
102 } else {
103 // The callback failed.
104 return false;
105 }
106 });
107 return result;
108 };
109
110 /**
111 * Returns the first indexed value in the provided object which
112 * satisfies the provided callback, according to the algorithm of
113 * `Array::find`.
114 */
115 export const findItem = makeCallable(arrayPrototype.find);
116
117 /**
118 * Returns the result of flatmapping the provided value with the
119 * provided callback according to the algorithm of `Array::flatMap`.
120 */
121 export const flatmap = makeCallable(arrayPrototype.flatMap);
122
123 /**
124 * Returns the result of flattening the provided object according to
125 * the algorithm of `Array::flat`.
126 */
127 export const flatten = makeCallable(arrayPrototype.flat);
128
129 /**
130 * Returns the first index of the provided object with a value
131 * equivalent to the provided value according to the algorithm of
132 * `Array::indexOf`.
133 */
134 export const getFirstIndex = makeCallable(arrayPrototype.indexOf);
135
136 /**
137 * Returns the item on the provided object at the provided index
138 * according to the algorithm of `Array::at`.
139 */
140 export const getItem = makeCallable(arrayPrototype.at);
141
142 /**
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`.
146 */
147 export const getLastIndex = makeCallable(arrayPrototype.lastIndexOf);
148
149 /**
150 * Returns whether every indexed value in the provided object satisfies
151 * the provided function, according to the algorithm of `Array::every`.
152 */
153 export const hasEvery = makeCallable(arrayPrototype.every);
154
155 /**
156 * Returns whether the provided object has an indexed value which
157 * satisfies the provided function, according to the algorithm of
158 * `Array::some`.
159 */
160 export const hasSome = makeCallable(arrayPrototype.some);
161
162 /**
163 * Returns whether the provided object has an indexed value equivalent
164 * to the provided value according to the algorithm of
165 * `Array::includes`.
166 *
167 * ※ This algorithm treats missing values as `undefined` rather than
168 * skipping them.
169 */
170 export const includes = makeCallable(arrayPrototype.includes);
171
172 /**
173 * Returns an iterator over the indexed entries in the provided value
174 * according to the algorithm of `Array::entries`.
175 */
176 export const indexedEntries = makeCallable(arrayPrototype.entries);
177
178 /**
179 * Returns an iterator over the indices in the provided value according
180 * to the algorithm of `Array::keys`.
181 */
182 export const indices = makeCallable(arrayPrototype.keys);
183
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);
191 } else {
192 // The provided value is not a canonical numeric index string.
193 return false;
194 }
195 };
196
197 /** Returns whether the provided value is arraylike. */
198 export const isArraylikeObject = ($) => {
199 if (type($) !== "object") {
200 return false;
201 } else {
202 try {
203 lengthOfArraylike($); // throws if not arraylike
204 return true;
205 } catch {
206 return false;
207 }
208 }
209 };
210
211 /**
212 * Returns whether the provided object is a collection.
213 *
214 * The definition of “collection” used by Piscēs is similar to
215 * Ecmascript’s definition of an arraylike object, but it differs in
216 * a few ways :—
217 *
218 * - It requires the provided value to be a proper object.
219 *
220 * - It requires the `length` property to be an integer index.
221 *
222 * - It requires the object to be concat‐spreadable, meaning it must
223 * either be an array or have `.[Symbol.isConcatSpreadable]` be true.
224 */
225 export const isCollection = ($) => {
226 if (!(type($) === "object" && "length" in $)) {
227 // The provided value is not an object or does not have a `length`.
228 return false;
229 } else {
230 try {
231 toIndex($.length); // will throw if `length` is not an index
232 return isConcatSpreadable($);
233 } catch {
234 return false;
235 }
236 }
237 };
238
239 /**
240 * Returns whether the provided value is spreadable during array
241 * concatenation.
242 *
243 * This is also used to determine which things should be treated as
244 * collections.
245 */
246 export const isConcatSpreadable = ($) => {
247 if (type($) !== "object") {
248 // The provided value is not an object.
249 return false;
250 } else {
251 // The provided value is an object.
252 const spreadable = $[Symbol.isConcatSpreadable];
253 return spreadable !== undefined ? !!spreadable : isArray($);
254 }
255 };
256
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);
265 } else {
266 // The provided value is not a canonical numeric index string.
267 return false;
268 }
269 };
270
271 /**
272 * Returns an iterator over the items in the provided value according
273 * to the algorithm of `Array::values`.
274 */
275 export const items = makeCallable(arrayPrototype.values);
276
277 /**
278 * Returns the length of the provided arraylike object.
279 *
280 * Will throw if the provided object is not arraylike.
281 *
282 * This can produce larger lengths than can actually be stored in
283 * arrays, because no such restrictions exist on arraylike methods.
284 */
285 export const lengthOfArraylike = ({ length }) => toLength(length);
286
287 /**
288 * Returns the result of mapping the provided value with the provided
289 * callback according to the algorithm of `Array::map`.
290 */
291 export const map = makeCallable(arrayPrototype.map);
292
293 /**
294 * Pops from the provided value according to the algorithm of
295 * `Array::pop`.
296 */
297 export const pop = makeCallable(arrayPrototype.pop);
298
299 /**
300 * Pushes onto the provided value according to the algorithm of
301 * `Array::push`.
302 */
303 export const push = makeCallable(arrayPrototype.push);
304
305 /**
306 * Returns the result of reducing the provided value with the provided
307 * callback, according to the algorithm of `Array::reduce`.
308 */
309 export const reduce = makeCallable(arrayPrototype.reduce);
310
311 /**
312 * Reverses the provided value according to the algorithm of
313 * `Array::reverse`.
314 */
315 export const reverse = makeCallable(arrayPrototype.reverse);
316
317 /**
318 * Shifts the provided value according to the algorithm of
319 * `Array::shift`.
320 */
321 export const shift = makeCallable(arrayPrototype.shift);
322
323 /**
324 * Returns a slice of the provided value according to the algorithm of
325 * `Array::slice`.
326 */
327 export const slice = makeCallable(arrayPrototype.slice);
328
329 /**
330 * Sorts the provided value in‐place according to the algorithm of
331 * `Array::sort`.
332 */
333 export const sort = makeCallable(arrayPrototype.sort);
334
335 /**
336 * Splices into and out of the provided value according to the
337 * algorithm of `Array::splice`.
338 */
339 export const splice = makeCallable(arrayPrototype.splice);
340
341 /**
342 * Returns the result of converting the provided value to an array
343 * index, or throws an error if it is out of range.
344 */
345 export const toIndex = ($) => {
346 const integer = floor($);
347 if (isNan(integer) || integer == 0) {
348 // The value is zero·like.
349 return 0;
350 } else {
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: ${$}.`);
356 } else {
357 // The value is within appropriate bounds.
358 return integer;
359 }
360 }
361 };
362
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
367 ? 0
368 : max(min(len, MAXIMUM_SAFE_INTEGRAL_NUMBER), 0);
369 };
370
371 /**
372 * Unshifts the provided value according to the algorithm of
373 * `Array::unshift`.
374 */
375 export const unshift = makeCallable(arrayPrototype.unshift);
This page took 0.199439 seconds and 3 git commands to generate.