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