]> Lady’s Gitweb - Pisces/blob - collection.js
Improve object.js a·p·i
[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, createCallableFunction } 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 = createCallableFunction(
58 arrayPrototype.concat,
59 "catenate",
60 );
61
62 /**
63 * Copies the items in the provided object to a new location according
64 * to the algorithm of `Array::copyWithin`.
65 */
66 export const copyWithin = createCallableFunction(
67 arrayPrototype.copyWithin,
68 );
69
70 /**
71 * Fills the provided object with the provided value according to the
72 * algorithm of `Array::fill`.
73 */
74 export const fill = createCallableFunction(arrayPrototype.fill);
75
76 /**
77 * Returns the result of filtering the provided object with the
78 * provided callback, according to the algorithm of `Array::filter`.
79 */
80 export const filter = createCallableFunction(arrayPrototype.filter);
81
82 /**
83 * Returns the first index in the provided object whose value satisfies
84 * the provided callback according to the algorithm of
85 * `Array::findIndex`.
86 */
87 export const findIndex = createCallableFunction(
88 arrayPrototype.findIndex,
89 );
90
91 /**
92 * Returns the first indexed entry in the provided object whose value
93 * satisfies the provided callback.
94 *
95 * If a third argument is supplied, it will be used as the this value
96 * of the callback.
97 */
98 export const findIndexedEntry = (
99 $,
100 callback,
101 thisArg = undefined,
102 ) => {
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];
108 return true;
109 } else {
110 // The callback failed.
111 return false;
112 }
113 });
114 return result;
115 };
116
117 /**
118 * Returns the first indexed value in the provided object which
119 * satisfies the provided callback, according to the algorithm of
120 * `Array::find`.
121 */
122 export const findItem = createCallableFunction(
123 arrayPrototype.find,
124 "findItem",
125 );
126
127 /**
128 * Returns the result of flatmapping the provided value with the
129 * provided callback according to the algorithm of `Array::flatMap`.
130 */
131 export const flatmap = createCallableFunction(
132 arrayPrototype.flatMap,
133 "flatmap",
134 );
135
136 /**
137 * Returns the result of flattening the provided object according to
138 * the algorithm of `Array::flat`.
139 */
140 export const flatten = createCallableFunction(
141 arrayPrototype.flat,
142 "flatten",
143 );
144
145 /**
146 * Returns the first index of the provided object with a value
147 * equivalent to the provided value according to the algorithm of
148 * `Array::indexOf`.
149 */
150 export const getFirstIndex = createCallableFunction(
151 arrayPrototype.indexOf,
152 "getFirstIndex",
153 );
154
155 /**
156 * Returns the item on the provided object at the provided index
157 * according to the algorithm of `Array::at`.
158 */
159 export const getItem = createCallableFunction(
160 arrayPrototype.at,
161 "getItem",
162 );
163
164 /**
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`.
168 */
169 export const getLastIndex = createCallableFunction(
170 arrayPrototype.lastIndexOf,
171 "getLastIndex",
172 );
173
174 /**
175 * Returns whether every indexed value in the provided object satisfies
176 * the provided function, according to the algorithm of `Array::every`.
177 */
178 export const hasEvery = createCallableFunction(
179 arrayPrototype.every,
180 "hasEvery",
181 );
182
183 /**
184 * Returns whether the provided object has an indexed value which
185 * satisfies the provided function, according to the algorithm of
186 * `Array::some`.
187 */
188 export const hasSome = createCallableFunction(
189 arrayPrototype.some,
190 "hasSome",
191 );
192
193 /**
194 * Returns whether the provided object has an indexed value equivalent
195 * to the provided value according to the algorithm of
196 * `Array::includes`.
197 *
198 * ※ This algorithm treats missing values as `undefined` rather than
199 * skipping them.
200 */
201 export const includes = createCallableFunction(
202 arrayPrototype.includes,
203 );
204
205 /**
206 * Returns an iterator over the indexed entries in the provided value
207 * according to the algorithm of `Array::entries`.
208 */
209 export const indexedEntries = createCallableFunction(
210 arrayPrototype.entries,
211 "indexedEntries",
212 );
213
214 /**
215 * Returns an iterator over the indices in the provided value according
216 * to the algorithm of `Array::keys`.
217 */
218 export const indices = createCallableFunction(
219 arrayPrototype.keys,
220 "indices",
221 );
222
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);
230 } else {
231 // The provided value is not a canonical numeric index string.
232 return false;
233 }
234 };
235
236 /** Returns whether the provided value is arraylike. */
237 export const isArraylikeObject = ($) => {
238 if (type($) !== "object") {
239 return false;
240 } else {
241 try {
242 lengthOfArraylike($); // throws if not arraylike
243 return true;
244 } catch {
245 return false;
246 }
247 }
248 };
249
250 /**
251 * Returns whether the provided object is a collection.
252 *
253 * The definition of “collection” used by Piscēs is similar to
254 * Ecmascript’s definition of an arraylike object, but it differs in
255 * a few ways :—
256 *
257 * - It requires the provided value to be a proper object.
258 *
259 * - It requires the `length` property to be an integer index.
260 *
261 * - It requires the object to be concat‐spreadable, meaning it must
262 * either be an array or have `.[Symbol.isConcatSpreadable]` be true.
263 */
264 export const isCollection = ($) => {
265 if (!(type($) === "object" && "length" in $)) {
266 // The provided value is not an object or does not have a `length`.
267 return false;
268 } else {
269 try {
270 toIndex($.length); // will throw if `length` is not an index
271 return isConcatSpreadable($);
272 } catch {
273 return false;
274 }
275 }
276 };
277
278 /**
279 * Returns whether the provided value is spreadable during array
280 * concatenation.
281 *
282 * This is also used to determine which things should be treated as
283 * collections.
284 */
285 export const isConcatSpreadable = ($) => {
286 if (type($) !== "object") {
287 // The provided value is not an object.
288 return false;
289 } else {
290 // The provided value is an object.
291 const spreadable = $[Symbol.isConcatSpreadable];
292 return spreadable !== undefined ? !!spreadable : isArray($);
293 }
294 };
295
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);
304 } else {
305 // The provided value is not a canonical numeric index string.
306 return false;
307 }
308 };
309
310 /**
311 * Returns an iterator over the items in the provided value according
312 * to the algorithm of `Array::values`.
313 */
314 export const items = createCallableFunction(
315 arrayPrototype.values,
316 "items",
317 );
318
319 /**
320 * Returns the length of the provided arraylike object.
321 *
322 * Will throw if the provided object is not arraylike.
323 *
324 * This can produce larger lengths than can actually be stored in
325 * arrays, because no such restrictions exist on arraylike methods.
326 */
327 export const lengthOfArraylike = ({ length }) => toLength(length);
328
329 /**
330 * Returns the result of mapping the provided value with the provided
331 * callback according to the algorithm of `Array::map`.
332 */
333 export const map = createCallableFunction(arrayPrototype.map);
334
335 /**
336 * Pops from the provided value according to the algorithm of
337 * `Array::pop`.
338 */
339 export const pop = createCallableFunction(arrayPrototype.pop);
340
341 /**
342 * Pushes onto the provided value according to the algorithm of
343 * `Array::push`.
344 */
345 export const push = createCallableFunction(arrayPrototype.push);
346
347 /**
348 * Returns the result of reducing the provided value with the provided
349 * callback, according to the algorithm of `Array::reduce`.
350 */
351 export const reduce = createCallableFunction(arrayPrototype.reduce);
352
353 /**
354 * Reverses the provided value according to the algorithm of
355 * `Array::reverse`.
356 */
357 export const reverse = createCallableFunction(arrayPrototype.reverse);
358
359 /**
360 * Shifts the provided value according to the algorithm of
361 * `Array::shift`.
362 */
363 export const shift = createCallableFunction(arrayPrototype.shift);
364
365 /**
366 * Returns a slice of the provided value according to the algorithm of
367 * `Array::slice`.
368 */
369 export const slice = createCallableFunction(arrayPrototype.slice);
370
371 /**
372 * Sorts the provided value in‐place according to the algorithm of
373 * `Array::sort`.
374 */
375 export const sort = createCallableFunction(arrayPrototype.sort);
376
377 /**
378 * Splices into and out of the provided value according to the
379 * algorithm of `Array::splice`.
380 */
381 export const splice = createCallableFunction(arrayPrototype.splice);
382
383 /**
384 * Returns the result of converting the provided value to an array
385 * index, or throws an error if it is out of range.
386 */
387 export const toIndex = ($) => {
388 const integer = floor($);
389 if (isNan(integer) || integer == 0) {
390 // The value is zero·like.
391 return 0;
392 } else {
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: ${$}.`);
398 } else {
399 // The value is within appropriate bounds.
400 return integer;
401 }
402 }
403 };
404
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
409 ? 0
410 : max(min(len, MAXIMUM_SAFE_INTEGRAL_NUMBER), 0);
411 };
412
413 /**
414 * Unshifts the provided value according to the algorithm of
415 * `Array::unshift`.
416 */
417 export const unshift = createCallableFunction(arrayPrototype.unshift);
This page took 0.116913 seconds and 5 git commands to generate.