]> Lady’s Gitweb - Pisces/blob - collection.js
Add denseProxy, various collection.js improvements
[Pisces] / collection.js
1 // SPDX-FileCopyrightText: 2020, 2021, 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: MPL-2.0
3 /**
4 * ⁌ ♓🧩 Piscēs ∷ collection.js
5 *
6 * Copyright © 2020–2023, 2025 Lady [@ Ladys Computer].
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
11 */
12
13 import {
14 call,
15 createArrowFunction,
16 createCallableFunction,
17 createProxyConstructor,
18 isCallable,
19 maybe,
20 } from "./function.js";
21 import {
22 defineOwnDataProperty,
23 defineOwnProperty,
24 getMethod,
25 hasOwnProperty,
26 isConcatSpreadableObject,
27 lengthOfArraylike,
28 objectCreate,
29 setPropertyValues,
30 toObject,
31 toPropertyDescriptorRecord,
32 } from "./object.js";
33 import {
34 canonicalNumericIndexString,
35 ITERATOR,
36 MAXIMUM_SAFE_INTEGRAL_NUMBER,
37 sameValue,
38 toFunctionName,
39 toIndex,
40 toLength,
41 type,
42 UNDEFINED,
43 } from "./value.js";
44
45 const PISCĒS = "♓🧩 Piscēs";
46
47 /** Returns an array of the provided values. */
48 export const array = createArrowFunction(
49 Array.of,
50 { name: "array" },
51 );
52
53 export const {
54 /**
55 * Returns the result of catenating the provided concat spreadable
56 * values into a new collection according to the algorithm of
57 * `Array::concat´.
58 *
59 * ※ If no arguments are given, this function returns an empty
60 * array. This is different behaviour than if an explicit undefined
61 * first argument is given, in which case the resulting array will
62 * have undefined as its first item.
63 *
64 * ※ Unlike `Array::concat´, this function ignores
65 * `.constructor[Symbol.species]´ and always returns an array.
66 */
67 concatSpreadableCatenate,
68 } = (() => {
69 const { concat } = Array.prototype;
70 return {
71 concatSpreadableCatenate: Object.defineProperties(
72 (...args) => call(concat, [], args),
73 {
74 name: { value: "concatSpreadableCatenate" },
75 length: { value: 2 },
76 },
77 ),
78 };
79 })();
80
81 /**
82 * Copies the items in the provided object to a new location according
83 * to the algorithm of `Array::copyWithin´.
84 */
85 export const copyWithin = createCallableFunction(
86 Array.prototype.copyWithin,
87 );
88
89 export const {
90 /**
91 * Returns a proxy of the provided arraylike value for which every
92 * integer index less than its length will appear to be present.
93 *
94 * ※ The returned proxy will have the original object as its
95 * prototype and will update with changes to the original.
96 *
97 * ※ The returned proxy only reflects ⹐own⹑ properties on the
98 * underlying object; if an index is set on the prototype chain, but
99 * not as an own property on the underlying object, it will appear as
100 * undefined on the proxy. Like·wise, if the length is not an own
101 * property, it will appear to be zero.
102 *
103 * ※ Both data values and accessors are supported for indices,
104 * provided they are defined directly on the underlying object.
105 *
106 * ※ A proxy can be made non·extensible if the `.length´ of the
107 * underlying object is read·only (i·e, not defined using a getter)
108 * and nonconfigurable.
109 *
110 * ※ Integer indices on the returned proxy, as well as `.length´,
111 * are read·only and start out formally (but not manually)
112 * configurable. They can only be made nonconfigurable if the
113 * underlying value is guaranteed not to change. As a change in
114 * `.length´ deletes any integer indices larger than the `.length´,
115 * the `.length´ of the underlying object must be fixed for any
116 * integer index to be made nonconfigurable.
117 *
118 * ※ When iterating, it is probably faster to just make a copy of
119 * the original value, for example :—
120 *
121 * | `setPropertyValues(´
122 * | ` fill(setPropertyValue([], "length", original.length)),´
123 * | ` original,´
124 * | `);´
125 *
126 * This function is rather intended for the use·case where both the
127 * proxy and the underlying array are longlived, and the latter may
128 * change unexpectedly after the formers creation.
129 */
130 denseProxy,
131
132 /**
133 * Returns whether the provided value is a dense proxy (created with
134 * `denseProxy´).
135 */
136 isDenseProxy,
137 } = (() => {
138 const {
139 deleteProperty: reflectDeleteProperty,
140 defineProperty: reflectDefineProperty,
141 getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
142 getPrototypeOf: reflectGetPrototypeOf,
143 has: reflectHas,
144 isExtensible: reflectIsExtensible,
145 ownKeys: reflectOwnKeys,
146 preventExtensions: reflectPreventExtensions,
147 setPrototypeOf: reflectSetPrototypeOf,
148 } = Reflect;
149 const isReadOnlyNonconfigurable = (Desc) =>
150 Desc !== UNDEFINED
151 && !(Desc.configurable || "get" in Desc
152 || "writable" in Desc && Desc.writable);
153
154 const denseProxyHandler = Object.assign(Object.create(null), {
155 defineProperty(O, P, Desc) {
156 const k = P === "length"
157 ? UNDEFINED
158 : canonicalNumericIndexString(P);
159 if (
160 P === "length"
161 || k !== UNDEFINED
162 && (sameValue(k, 0) || k > 0 && k === toLength(k))
163 ) {
164 // The provided property is either `"length"´ or an integer
165 // index.
166 const desc = maybe(Desc, toPropertyDescriptorRecord);
167 if (
168 desc.set !== UNDEFINED
169 || "writable" in desc && desc.writable
170 ) {
171 // The provided descriptor defines a setter or is attempting
172 // to make the property writable; this is not permitted.
173 return false;
174 } else {
175 // The provided descriptor does not define a setter.
176 const Q = reflectGetPrototypeOf(O);
177 const current = maybe(
178 reflectGetOwnPropertyDescriptor(O, P),
179 toPropertyDescriptorRecord,
180 );
181 const lenDesc = maybe(
182 reflectGetOwnPropertyDescriptor(Q, "length"),
183 toPropertyDescriptorRecord,
184 );
185 const currentlyConfigurable = current?.configurable ?? true;
186 const willChangeConfigurable = "configurable" in desc
187 && (desc.configurable !== currentlyConfigurable);
188 if (
189 willChangeConfigurable
190 && (!currentlyConfigurable
191 || !isReadOnlyNonconfigurable(lenDesc))
192 ) {
193 // The provided descriptor either aims to make a property
194 // nonconfigurable when the underlying length is not
195 // readonly or else aims to make a property configurable
196 // when it currently isn¦t; neither is permitted.
197 return false;
198 } else if (P === "length") {
199 // The provided descriptor is attempting to modify the
200 // length.
201 //
202 // It is known at this point that either the property
203 // descriptor is not trying to make the length
204 // nonconfigurable, or else the underlying length is
205 // readonly.
206 const len = !currentlyConfigurable
207 ? current.value
208 : lenDesc === UNDEFINED
209 ? 0 // force zero if not an own property
210 : !("value" in desc)
211 ? null // not needed yet
212 : lengthOfArraylike(Q);
213 if (
214 "get" in desc
215 || "enumerable" in desc && desc.enumerable
216 || "value" in desc && desc.value !== len
217 ) {
218 // The provided descriptor is attempting to create a
219 // getter for the length, change the enumerability of the
220 // length, or change the value of the length; these are
221 // not permitted.
222 return false;
223 } else if (!willChangeConfigurable) {
224 // The provided descriptor is not attempting to change
225 // the value of the length or its configurablility; this
226 // succeeds with no effect.
227 return true;
228 } else {
229 // The provided descriptor is attempting to make a
230 // read·only, configurable length nonconfigurable.
231 return reflectDefineProperty(
232 O,
233 P,
234 setPropertyValues(objectCreate(null), {
235 configurable: false,
236 enumerable: false,
237 value: len ?? lengthOfArraylike(Q),
238 writable: false,
239 }),
240 );
241 }
242 } else {
243 // The provided property is an integral canonical numeric
244 // index string.
245 const len = lenDesc === UNDEFINED
246 ? 0
247 : lengthOfArraylike(Q);
248 if (k < len) {
249 // The provided property is smaller than the length; this
250 // property can potentially be modified.
251 const kDesc = maybe(
252 reflectGetOwnPropertyDescriptor(Q, P),
253 toPropertyDescriptorRecord,
254 );
255 const kGet = current?.get; // use current getter
256 const kValue = // use underlying value
257 kDesc === UNDEFINED
258 ? UNDEFINED
259 : kDesc.value ?? call(kDesc.get, Q, []);
260 if (
261 "get" in desc && desc.get !== kGet
262 || "enumerable" in desc && !desc.enumerable
263 || "value" in desc && desc.value !== kValue
264 ) {
265 // The provided descriptor is attempting to change the
266 // value or enumerability of the property away from
267 // their current values; this is not permitted.
268 return false;
269 } else if (!willChangeConfigurable) {
270 // The provided descriptor is not attempting to change
271 // the configurability of the property; in this case,
272 // no actual change is being requested.
273 return true;
274 } else {
275 // The provided descriptor is attempting to make the
276 // property nonconfigurable, but it is currently
277 // configurable (and maybe not even present on the
278 // proxy target); this is only permissible if the value
279 // of the underlying property is known not to be able
280 // to change.
281 //
282 // Providing the value is okay if the underlying
283 // property is readonly, but if the underlying property
284 // is a getter, then the value must not be provided
285 // (since the resulting property will be defined with a
286 // brand new getter).
287 //
288 // At this point, it is known that the provided
289 // descriptor does not provide a getter, because
290 // getters are only supported on index properties which
291 // are already nonconfigurable.
292 //
293 // At this point, we have already confirmed that the
294 // length of the underlying object is immutable.
295 const dynamic = kDesc !== UNDEFINED
296 && !("writable" in kDesc);
297 const readonly =
298 kDesc === UNDEFINED && !reflectIsExtensible(Q)
299 || kDesc !== UNDEFINED && !kDesc.configurable && (
300 dynamic || !kDesc.writable
301 );
302 const noChange = !dynamic
303 || dynamic && !("value" in desc);
304 return readonly && noChange && reflectDefineProperty(
305 O,
306 P,
307 setPropertyValues(
308 objectCreate(null),
309 kDesc !== UNDEFINED && "get" in kDesc
310 ? {
311 configurable: false,
312 enumerable: true,
313 get: defineOwnProperty(
314 () => call(kDesc.get, Q, []),
315 "name",
316 defineOwnDataProperty(
317 objectCreate(null),
318 "value",
319 toFunctionName(P, "get"),
320 ),
321 ),
322 set: UNDEFINED,
323 }
324 : {
325 configurable: false,
326 enumerable: true,
327 value: kValue,
328 writable: false,
329 },
330 ),
331 );
332 }
333 } else {
334 // The provided property is not smaller than the length;
335 // this is not permitted.
336 return false;
337 }
338 }
339 }
340 } else {
341 // The provided property is not `"length"´ or an integer index.
342 return reflectDefineProperty(O, P, Desc);
343 }
344 },
345 deleteProperty(O, P) {
346 const k = P === "length"
347 ? UNDEFINED
348 : canonicalNumericIndexString(P);
349 if (
350 P === "length"
351 || k !== UNDEFINED
352 && (sameValue(k, 0) || k > 0 && k === toLength(k))
353 ) {
354 // The property is an integer index or `"length"´.
355 if (!reflectIsExtensible(O) || P === "length") {
356 // The proxied object is not extensible or the provided
357 // property is `"length"´; this is not permitted.
358 return false;
359 } else {
360 // The provided property is an integer index; it can only
361 // be deleted if it is greater than the length (in which
362 // case, it is not present in the first place).
363 const Q = reflectGetPrototypeOf(O);
364 const len = hasOwnProperty(Q, "length")
365 ? lengthOfArraylike(Q)
366 : 0;
367 return k < len ? false : true;
368 }
369 } else {
370 // The provided property is not `"length"´ or an integer index.
371 return reflectDeleteProperty(O, P);
372 }
373 },
374 getOwnPropertyDescriptor(O, P) {
375 const k = P === "length"
376 ? UNDEFINED
377 : canonicalNumericIndexString(P);
378 if (
379 P === "length"
380 || k !== UNDEFINED
381 && (sameValue(k, 0) || k > 0 && k === toLength(k))
382 ) {
383 // The property is an integer index or `"length"´.
384 const Q = reflectGetPrototypeOf(O);
385 const current = maybe(
386 reflectGetOwnPropertyDescriptor(O, P),
387 toPropertyDescriptorRecord,
388 );
389 if (current !== UNDEFINED && !current.configurable) {
390 // The property is defined and nonconfigurable on the object.
391 //
392 // Return its descriptor.
393 return current;
394 } else if (P === "length") {
395 // The property is `"length"´.
396 //
397 // Return the length of the underlying object.
398 return setPropertyValues(objectCreate(null), {
399 configurable: true,
400 enumerable: false,
401 value: hasOwnProperty(Q, "length")
402 ? lengthOfArraylike(Q)
403 : 0,
404 writable: false,
405 });
406 } else {
407 // The property is an integer index.
408 //
409 // Return a data descriptor with its value or undefined as
410 // appropriate.
411 const len = hasOwnProperty(Q, "length")
412 ? lengthOfArraylike(Q)
413 : 0;
414 if (k < len) {
415 // The property is an integer index less than the length.
416 //
417 // Provide the current value of the own property.
418 const kDesc = maybe(
419 reflectGetOwnPropertyDescriptor(Q, P),
420 toPropertyDescriptorRecord,
421 );
422 return setPropertyValues(objectCreate(null), {
423 configurable: true,
424 enumerable: true,
425 value: !kDesc
426 ? UNDEFINED
427 : "get" in kDesc
428 ? call(kDesc.get, Q, [])
429 : kDesc.value,
430 writable: false,
431 });
432 } else {
433 // The property is an integer index, but not less than the
434 // length.
435 //
436 // Return undefined.
437 return UNDEFINED;
438 }
439 }
440 } else {
441 // The provided property is not `"length"´ or an integer index.
442 return reflectGetOwnPropertyDescriptor(O, P);
443 }
444 },
445 has(O, P) {
446 const k = P === "length"
447 ? UNDEFINED
448 : canonicalNumericIndexString(P);
449 if (P === "length") {
450 // The provided property is `"length"´; this is always present.
451 return true;
452 } else if (
453 k !== UNDEFINED
454 && (sameValue(k, 0) || k > 0 && k === toLength(k))
455 ) {
456 // The provided property is an integer index.
457 //
458 // Return whether it is less than the length.
459 const Q = reflectGetPrototypeOf(O);
460 const len = hasOwnProperty(Q, "length")
461 ? lengthOfArraylike(Q)
462 : 0;
463 return k < len ? true : reflectHas(O, P);
464 } else {
465 // The provided property is not `"length"´ or an integer index.
466 return reflectHas(O, P);
467 }
468 },
469 ownKeys(O) {
470 const keys = reflectOwnKeys(O);
471 const Q = reflectGetPrototypeOf(O);
472 const len = hasOwnProperty(Q, "length")
473 ? lengthOfArraylike(Q)
474 : 0;
475 const result = [];
476 let i;
477 let hasHitLength = false;
478 for (i = 0; i < len && i < -1 >>> 0; ++i) {
479 // Iterate over those array indices which are less than the
480 // length of the underlying object and collect them in the
481 // result.
482 //
483 // Array indices are handled specially by the Ecmascript
484 // specification. Other integer indices may also be present
485 // (if they are too big to be array indices but still smaller
486 // than the length), but these are added later with all of the
487 // other keys.
488 defineOwnDataProperty(result, i, `${i}`);
489 }
490 for (let j = 0; j < keys.length; ++j) {
491 // Iterate over the own keys of the object and collect them in
492 // the result if necessary.
493 const P = keys[j];
494 const k = P === "length"
495 ? UNDEFINED
496 : canonicalNumericIndexString(P);
497 const isIntegerIndex = k !== UNDEFINED
498 && (sameValue(k, 0) || k > 0 && k === toLength(k));
499 if (!hasHitLength && (!isIntegerIndex || k >= -1 >>> 0)) {
500 // The current key is the first key which is not an array
501 // index; add `"length"´ to the result, as well as any
502 // integer indices which are not array indices.
503 //
504 // This may never occur, in which case these properties are
505 // added after the end of the loop.
506 //
507 // `"length"´ is added first as it is conceptually the first
508 // property on the object.
509 defineOwnDataProperty(result, result.length, "length");
510 for (; i < len; ++i) {
511 // Iterate over those remaining integer indices which are
512 // less than the length of the underlying object and
513 // collect them in the result.
514 defineOwnDataProperty(result, result.length, `${i}`);
515 }
516 hasHitLength = true;
517 } else {
518 // The current key is not the first key which is not an array
519 // index.
520 /* do nothing */
521 }
522 if (P === "length" || isIntegerIndex && k < len) {
523 // The current key is either `"length"´ or an integer index
524 // less than the length; it has already been collected.
525 /* do nothing */
526 } else {
527 // The current key has not yet been collected into the
528 // result; add it.
529 defineOwnDataProperty(result, result.length, P);
530 }
531 }
532 if (!hasHitLength) {
533 // All of the collected keys were array indices; `"length"´ and
534 // any outstanding integer indices still need to be collected.
535 defineOwnDataProperty(result, result.length, "length");
536 for (; i < len; ++i) {
537 // Iterate over those remaining integer indices which are
538 // less than the length of the underlying object and collect
539 // them in the result.
540 defineOwnDataProperty(result, result.length, `${i}`);
541 }
542 } else {
543 // There was at least one key collected which was not an array
544 // index.
545 /* do nothing */
546 }
547 return result;
548 },
549 preventExtensions(O) {
550 if (!reflectIsExtensible(O)) {
551 // The object is already not extensible; this is an automatic
552 // success.
553 return true;
554 } else {
555 // The object is currently extensible; see if it can be made
556 // non·extensible and attempt to do so.
557 const Q = reflectGetPrototypeOf(O);
558 const lenDesc = maybe(
559 reflectGetOwnPropertyDescriptor(Q, "length"),
560 toPropertyDescriptorRecord,
561 );
562 if (!isReadOnlyNonconfigurable(lenDesc)) {
563 // The underlying length is not read·only; the object cannot
564 // be made non·extensible because the indices may change.
565 return false;
566 } else {
567 // The underlying length is read·only; define the needed
568 // indices on the object and then prevent extensions.
569 const len = lengthOfArraylike(Q); // definitely exists
570 for (let k = 0; k < len; ++k) {
571 // Iterate over each index and define a placeholder for it.
572 reflectDefineProperty(
573 O,
574 k,
575 setPropertyValues(objectCreate(null), {
576 configurable: true,
577 enumerable: true,
578 value: UNDEFINED,
579 writable: false,
580 }),
581 );
582 }
583 return reflectPreventExtensions(O);
584 }
585 }
586 },
587 setPrototypeOf(O, V) {
588 const Q = reflectGetPrototypeOf(O);
589 return Q === V ? reflectSetPrototypeOf(O, V) : false;
590 },
591 });
592
593 const DenseProxy = createProxyConstructor(
594 denseProxyHandler,
595 function Dense($) {
596 return objectCreate(toObject($)); // throws if nullish
597 },
598 );
599
600 return {
601 denseProxy: Object.defineProperty(
602 ($) => new DenseProxy($),
603 "name",
604 { value: "denseProxy" },
605 ),
606 isDenseProxy: DenseProxy.isDenseProxy,
607 };
608 })();
609
610 /**
611 * Fills the provided object with the provided value according to the
612 * algorithm of `Array::fill´.
613 */
614 export const fill = createCallableFunction(Array.prototype.fill);
615
616 /**
617 * Returns the result of filtering the provided object with the
618 * provided callback, according to the algorithm of `Array::filter´.
619 *
620 * ※ Unlike `Array::filter´, this function ignores
621 * `.constructor[Symbol.species]´ and always returns an array.
622 */
623 export const filter = ($, callbackFn, thisArg = UNDEFINED) => {
624 const O = toObject($);
625 const len = lengthOfArraylike(O);
626 if (!isCallable(callbackFn)) {
627 throw new TypeError(
628 `${PISCĒS}: Filter callback must be callable.`,
629 );
630 } else {
631 const A = [];
632 for (let k = 0, to = 0; k < len; ++k) {
633 if (k in O) {
634 const kValue = O[k];
635 if (call(callbackFn, thisArg, [kValue, k, O])) {
636 defineOwnDataProperty(A, to++, kValue);
637 } else {
638 /* do nothing */
639 }
640 } else {
641 /* do nothing */
642 }
643 }
644 return A;
645 }
646 };
647
648 /**
649 * Returns the first index in the provided object whose value satisfies
650 * the provided callback.
651 *
652 * ※ This function differs from `Array::findIndex´ in that it returns
653 * undefined, not −1, if no match is found, and indices which aren¦t
654 * present are skipped, not treated as having values of undefined.
655 */
656 export const findFirstIndex = ($, callback, thisArg = UNDEFINED) =>
657 findFirstIndexedEntry($, callback, thisArg)?.[0];
658
659 export const {
660 /**
661 * Returns the first indexed entry in the provided object whose value
662 * satisfies the provided callback.
663 *
664 * If a third argument is supplied, it will be used as the this value
665 * of the callback.
666 *
667 * ※ Unlike the builtin Ecmascript array searching methods, this
668 * function does not treat indices which are not present on a sparse
669 * array as though they were undefined.
670 */
671 findFirstIndexedEntry,
672
673 /**
674 * Returns the last indexed entry in the provided object whose value
675 * satisfies the provided callback.
676 *
677 * If a third argument is supplied, it will be used as the this value
678 * of the callback.
679 *
680 * ※ Unlike the builtin Ecmascript array searching methods, this
681 * function does not treat indices which are not present on a sparse
682 * array as though they were undefined.
683 */
684 findLastIndexedEntry,
685 } = (() => {
686 const findViaPredicate = ($, direction, predicate, thisArg) => {
687 const O = toObject($);
688 const len = lengthOfArraylike(O);
689 if (!isCallable(predicate)) {
690 // The provided predicate is not callable; throw an error.
691 throw new TypeError(
692 `${PISCĒS}: Find predicate must be callable.`,
693 );
694 } else {
695 // The provided predicate is callable; do the search.
696 const ascending = direction === "ascending";
697 for (
698 let k = ascending ? 0 : len - 1;
699 ascending ? k < len : k >= 0;
700 ascending ? ++k : --k
701 ) {
702 // Iterate over each possible index between 0 and the length of
703 // the provided arraylike.
704 if (!(k in O)) {
705 // The current index is not present in the provided value.
706 /* do nothing */
707 } else {
708 // The current index is present in the provided value; test
709 // to see if it satisfies the predicate.
710 const kValue = O[k];
711 if (call(predicate, thisArg, [kValue, k, O])) {
712 // The value at the current index satisfies the predicate;
713 // return the entry.
714 return [k, kValue];
715 } else {
716 // The value at the current index does not satisfy the
717 // predicate.
718 /* do nothing */
719 }
720 }
721 }
722 return UNDEFINED;
723 }
724 };
725
726 return {
727 findFirstIndexedEntry: ($, predicate, thisArg = UNDEFINED) =>
728 findViaPredicate($, "ascending", predicate, thisArg),
729 findLastIndexedEntry: ($, predicate, thisArg = UNDEFINED) =>
730 findViaPredicate($, "descending", predicate, thisArg),
731 };
732 })();
733
734 /**
735 * Returns the first indexed value in the provided object which
736 * satisfies the provided callback.
737 *
738 * ※ Unlike `Array::find´, this function does not treat indices which
739 * are not present on a sparse array as though they were undefined.
740 */
741 export const findFirstItem = ($, callback, thisArg = UNDEFINED) =>
742 findFirstIndexedEntry($, callback, thisArg)?.[1];
743
744 /**
745 * Returns the last index in the provided object whose value satisfies
746 * the provided callback.
747 *
748 * ※ This function differs from `Array::findLastIndex´ in that it
749 * returns undefined, not −1, if no match is found, and indices which
750 * aren¦t present are skipped, not treated as having values of
751 * undefined.
752 */
753 export const findLastIndex = ($, callback, thisArg = UNDEFINED) =>
754 findLastIndexedEntry($, callback, thisArg)?.[0];
755
756 /**
757 * Returns the last indexed value in the provided object which
758 * satisfies the provided callback.
759 *
760 * ※ Unlike `Array::findLast´, this function does not treat indices
761 * which are not present on a sparse array as though they were
762 * undefined.
763 */
764 export const findLastItem = ($, callback, thisArg = UNDEFINED) =>
765 findLastIndexedEntry($, callback, thisArg)?.[1];
766
767 /**
768 * Returns the result of flatmapping the provided value with the
769 * provided callback according to the algorithm of `Array::flatMap´.
770 *
771 * ※ Flattening always produces a dense array.
772 */
773 export const flatmap = createCallableFunction(
774 Array.prototype.flatMap,
775 { name: "flatmap" },
776 );
777
778 /**
779 * Returns the result of flattening the provided object according to
780 * the algorithm of `Array::flat´.
781 *
782 * ※ Flattening always produces a dense array.
783 */
784 export const flatten = createCallableFunction(
785 Array.prototype.flat,
786 { name: "flatten" },
787 );
788
789 /**
790 * Returns the first index of the provided object with a value
791 * equivalent to the provided value according to the algorithm of
792 * `Array::indexOf´.
793 */
794 export const getFirstIndex = createCallableFunction(
795 Array.prototype.indexOf,
796 { name: "getFirstIndex" },
797 );
798
799 /**
800 * Returns the item on the provided object at the provided index
801 * according to the algorithm of `Array::at´.
802 */
803 export const getItem = createCallableFunction(
804 Array.prototype.at,
805 { name: "getItem" },
806 );
807
808 /**
809 * Returns the last index of the provided object with a value
810 * equivalent to the provided value according to the algorithm of
811 * `Array::lastIndexOf´.
812 */
813 export const getLastIndex = createCallableFunction(
814 Array.prototype.lastIndexOf,
815 { name: "getLastIndex" },
816 );
817
818 /**
819 * Returns whether every indexed value in the provided object satisfies
820 * the provided function, according to the algorithm of `Array::every´.
821 */
822 export const hasEvery = createCallableFunction(
823 Array.prototype.every,
824 { name: "hasEvery" },
825 );
826
827 /**
828 * Returns whether the provided object has an indexed value which
829 * satisfies the provided function, according to the algorithm of
830 * `Array::some´.
831 */
832 export const hasSome = createCallableFunction(
833 Array.prototype.some,
834 { name: "hasSome" },
835 );
836
837 /**
838 * Returns whether the provided object has an indexed value equivalent
839 * to the provided value according to the algorithm of
840 * `Array::includes´.
841 *
842 * ※ This algorithm treats missing values as undefined rather than
843 * skipping them.
844 */
845 export const includes = createCallableFunction(
846 Array.prototype.includes,
847 );
848
849 /**
850 * Returns an iterator over the indexed entries in the provided value
851 * according to the algorithm of `Array::entries´.
852 */
853 export const indexedEntries = createCallableFunction(
854 Array.prototype.entries,
855 { name: "indexedEntries" },
856 );
857
858 /**
859 * Returns an iterator over the indices in the provided value according
860 * to the algorithm of `Array::keys´.
861 */
862 export const indices = createCallableFunction(
863 Array.prototype.keys,
864 { name: "indices" },
865 );
866
867 export const isArray = createArrowFunction(Array.isArray);
868
869 /**
870 * Returns whether the provided object is a collection.
871 *
872 * The definition of “collection” used by Piscēs is similar to
873 * Ecmascripts definition of an arraylike object, but it differs in
874 * a few ways :—
875 *
876 * - It requires the provided value to be a proper object.
877 *
878 * - It requires the `length´ property to be an integer index.
879 *
880 * - It requires the object to be concat‐spreadable, meaning it must
881 * either be an array or have `.[Symbol.isConcatSpreadable]´ be true.
882 */
883 export const isCollection = ($) => {
884 if (!(type($) === "object" && "length" in $)) {
885 // The provided value is not an object or does not have a `length´.
886 return false;
887 } else {
888 try {
889 toIndex($.length); // will throw if `length´ is not an index
890 return isConcatSpreadableObject($);
891 } catch {
892 return false;
893 }
894 }
895 };
896
897 /**
898 * Returns an iterator over the items in the provided value according
899 * to the algorithm of `Array::values´.
900 */
901 export const items = createCallableFunction(
902 Array.prototype.values,
903 { name: "items" },
904 );
905
906 /**
907 * Returns the result of mapping the provided value with the provided
908 * callback according to the algorithm of `Array::map´.
909 */
910 export const map = createCallableFunction(Array.prototype.map);
911
912 /**
913 * Pops from the provided value according to the algorithm of
914 * `Array::pop´.
915 */
916 export const pop = createCallableFunction(Array.prototype.pop);
917
918 /**
919 * Pushes onto the provided value according to the algorithm of
920 * `Array::push´.
921 */
922 export const push = createCallableFunction(Array.prototype.push);
923
924 /**
925 * Returns the result of reducing the provided value with the provided
926 * callback, according to the algorithm of `Array::reduce´.
927 */
928 export const reduce = createCallableFunction(Array.prototype.reduce);
929
930 /**
931 * Reverses the provided value according to the algorithm of
932 * `Array::reverse´.
933 */
934 export const reverse = createCallableFunction(Array.prototype.reverse);
935
936 /**
937 * Shifts the provided value according to the algorithm of
938 * `Array::shift´.
939 */
940 export const shift = createCallableFunction(Array.prototype.shift);
941
942 /**
943 * Returns a slice of the provided value according to the algorithm of
944 * `Array::slice´.
945 */
946 export const slice = createCallableFunction(Array.prototype.slice);
947
948 /**
949 * Sorts the provided value in‐place according to the algorithm of
950 * `Array::sort´.
951 */
952 export const sort = createCallableFunction(Array.prototype.sort);
953
954 /**
955 * Splices into and out of the provided value according to the
956 * algorithm of `Array::splice´.
957 */
958 export const splice = createCallableFunction(Array.prototype.splice);
959
960 export const {
961 /**
962 * Returns a potentially‐sparse array created from the provided
963 * arraylike or iterable.
964 *
965 * ※ This function differs from `Array.from´ in that it does not
966 * support subclassing and, in the case of a provided arraylike
967 * value, does not set properties on the result which are not present
968 * in the provided value. This can result in sparse arrays.
969 *
970 * ※ An iterator result which lacks a `value´ property also results
971 * in the corresponding index being missing in the resulting array.
972 */
973 toArray,
974
975 /**
976 * Returns a dense array created from the provided arraylike or
977 * iterable.
978 *
979 * ※ This function differs from `Array.from´ in that it does not
980 * support subclassing.
981 *
982 * ※ If indices are not present in a provided arraylike value, they
983 * will be treated exactly as tho they were present and set to
984 * undefined.
985 */
986 toDenseArray,
987 } = (() => {
988 const makeArray = Array;
989
990 const arrayFrom = (items, mapFn, thisArg, allowSparse = false) => {
991 // This function re·implements the behaviour of `Array.from´, plus
992 // support for sparse arrays and minus the support for subclassing.
993 //
994 // It is actually only needed in the former case, as subclassing
995 // support can more easily be removed by wrapping it in an arrow
996 // function or binding it to `Array´ or undefined.
997 if (mapFn !== UNDEFINED && !isCallable(mapFn)) {
998 // A mapping function was provided but is not callable; this is
999 // an error.
1000 throw new TypeError(`${PISCĒS}: Map function not callable.`);
1001 } else {
1002 // Attempt to get an iterator method for the provided items;
1003 // further behaviour depends on whether this is successful.
1004 const iteratorMethod = getMethod(items, ITERATOR);
1005 if (iteratorMethod !== UNDEFINED) {
1006 // An iterator method was found; attempt to create an array
1007 // from its items.
1008 const A = [];
1009 const iterator = call(items, iteratorMethod, []);
1010 if (type(iterator) !== "object") {
1011 // The iterator method did not produce an object; this is an
1012 // error.
1013 throw new TypeError(
1014 `${PISCĒS}: Iterators must be objects, but got: ${iterator}.`,
1015 );
1016 } else {
1017 // The iterator method produced an iterator object; collect
1018 // its values into the array.
1019 const nextMethod = iterator.next;
1020 for (let k = 0; true; ++k) {
1021 // Loop until the iterator is exhausted.
1022 if (k >= MAXIMUM_SAFE_INTEGRAL_NUMBER) {
1023 // The current index has exceeded the maximum index
1024 // allowable for arrays; close the iterator, then throw
1025 // an error.
1026 try {
1027 // Attempt to close the iterator.
1028 iterator.return();
1029 } catch {
1030 // Ignore any errors while closing the iterator.
1031 /* do nothing */
1032 }
1033 throw new TypeError(`${PISCĒS}: Index out of range.`);
1034 } else {
1035 // The current index is a valid index; get the next value
1036 // from the iterator and assign it, if one exists.
1037 const result = call(nextMethod, iterator, []);
1038 if (type(result) !== "object") {
1039 // The next method did not produce an object; this is
1040 // an error.
1041 throw new TypeError(
1042 `${PISCĒS}: Iterator results must be objects, but got: ${result}.`,
1043 );
1044 } else {
1045 // The next method produced an object; process it.
1046 const { done } = result;
1047 if (done) {
1048 // The iterator has exhausted itself; confirm the
1049 // length of the resulting array and return it.
1050 A.length = k;
1051 return A;
1052 } else {
1053 const present = "value" in result;
1054 // The iterator has not exhausted itself; add its
1055 // value to the array.
1056 if (allowSparse && !present) {
1057 // The iterator has no value and creating sparse
1058 // arrays is allowed.
1059 /* do nothing */
1060 } else {
1061 // The iterator has a value or sparse arrays are
1062 // disallowed.
1063 const nextValue = present
1064 ? result.value
1065 : UNDEFINED;
1066 try {
1067 // Try to assign the value in the result array,
1068 // mapping if necessary.
1069 defineOwnDataProperty(
1070 A,
1071 k,
1072 mapFn !== UNDEFINED
1073 ? call(mapFn, thisArg, [nextValue, k])
1074 : nextValue,
1075 );
1076 } catch (error) {
1077 // There was an error when mapping or assigning
1078 // the value; close the iterator before
1079 // rethrowing the error.
1080 try {
1081 // Attempt to close the iterator.
1082 iterator.return();
1083 } catch {
1084 // Ignore any errors while closing the
1085 // iterator.
1086 /* do nothing */
1087 }
1088 throw error;
1089 }
1090 }
1091 }
1092 }
1093 }
1094 }
1095 }
1096 } else {
1097 // No iterator method was found; treat the provided items as an
1098 // arraylike object.
1099 const arraylike = toObject(items);
1100 const len = lengthOfArraylike(arraylike);
1101 const A = makeArray(len);
1102 for (let k = 0; k < len; ++k) {
1103 // Iterate over the values in the arraylike object and assign
1104 // them to the result array as necessary.
1105 const present = k in arraylike;
1106 if (allowSparse && !present) {
1107 // The current index is not present in the arraylike object
1108 // and sparse arrays are allowed.
1109 /* do nothing */
1110 } else {
1111 // The current index is present in the arraylike object or
1112 // sparse arrays are not allowed; assign the value to the
1113 // appropriate index in the result array, mapping if
1114 // necessary.
1115 const nextValue = present ? arraylike[k] : UNDEFINED;
1116 defineOwnDataProperty(
1117 A,
1118 k,
1119 mapFn !== UNDEFINED
1120 ? call(mapFn, thisArg, [nextValue, k])
1121 : nextValue,
1122 );
1123 }
1124 }
1125 A.length = len;
1126 return A;
1127 }
1128 }
1129 };
1130
1131 return {
1132 toArray: (items, mapFn = UNDEFINED, thisArg = UNDEFINED) =>
1133 arrayFrom(items, mapFn, thisArg, true),
1134 toDenseArray: (items, mapFn = UNDEFINED, thisArg = UNDEFINED) =>
1135 arrayFrom(items, mapFn, thisArg, false),
1136 };
1137 })();
1138
1139 /**
1140 * Unshifts the provided value according to the algorithm of
1141 * `Array::unshift´.
1142 */
1143 export const unshift = createCallableFunction(Array.prototype.unshift);
This page took 0.340696 seconds and 5 git commands to generate.