1 // ♓🌟 Piscēs ∷ object.js
2 // ====================================================================
4 // Copyright © 2022–2023 Lady [@ Lady’s Computer].
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/>.
15 } from "./function.js";
16 import { ITERATOR
, SPECIES
, toPrimitive
, type
} from "./value.js";
19 * An object whose properties are lazy‐loaded from the methods on the
20 * own properties of the provided object.
22 * This is useful when you are looking to reference properties on
23 * objects which, due to module dependency graphs, cannot be guaranteed
24 * to have been initialized yet.
26 * The resulting properties will have the same attributes (regarding
27 * configurability, enumerability, and writability) as the
28 * corresponding properties on the methods object. If a property is
29 * marked as writable, the method will never be called if it is set
30 * before it is gotten. By necessity, the resulting properties are all
31 * configurable before they are accessed for the first time.
33 * Methods will be called with the resulting object as their this
36 * `LazyLoader` objects have the same prototype as the passed methods
39 export class LazyLoader
extends null {
41 * Constructs a new `LazyLoader` object.
43 * ☡ This function throws if the provided value is not an object.
45 constructor(loadMethods
) {
46 if (type(loadMethods
) !== "object") {
47 // The provided value is not an object; throw an error.
49 `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`,
52 // The provided value is an object; process it and build the
54 const result
= objectCreate(getPrototype(loadMethods
));
55 const methodKeys
= getOwnPropertyKeys(loadMethods
);
56 for (let index
= 0; index
< methodKeys
.length
; ++index
) {
57 // Iterate over the property keys of the provided object and
58 // define getters and setters appropriately on the result.
59 const methodKey
= methodKeys
[index
];
60 const { configurable
, enumerable
, writable
} =
61 getOwnPropertyDescriptor(loadMethods
, methodKey
);
62 defineOwnProperty(result
, methodKey
, {
65 get: defineOwnProperty(
67 const value
= call(loadMethods
[methodKey
], result
, []);
68 defineOwnProperty(result
, methodKey
, {
77 { value
: toFunctionName(methodKey
, "get") },
82 defineOwnProperty(result
, methodKey
, {
89 { value
: toFunctionName(methodKey
, "set") },
100 * A property descriptor object.
102 * Actually constructing a property descriptor object using this class
103 * is only necessary if you need strict guarantees about the types of
104 * its properties; the resulting object is proxied to ensure the types
105 * match what one would expect from composing FromPropertyDescriptor
106 * and ToPropertyDescriptor in the Ecmascript specification.
108 * Otherwise, the instance properties and methods are generic.
110 export const { PropertyDescriptor
} = (() => {
111 class PropertyDescriptor
extends null {
113 * Constructs a new property descriptor object from the provided
116 * The resulting object is proxied to enforce types (for example,
117 * its `.enumerable` property, if defined, will always be a
121 if (type(O
) !== "object") {
122 // The provided value is not an object.
124 `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
127 // The provided value is an object.
128 const desc
= objectCreate(propertyDescriptorPrototype
);
129 if ("enumerable" in O
) {
130 // An enumerable property is specified.
131 desc
.enumerable
= !!O
.enumerable
;
133 // An enumerable property is not specified.
136 if ("configurable" in O
) {
137 // A configurable property is specified.
138 desc
.configurable
= !!O
.configurable
;
140 // A configurable property is not specified.
144 // A value property is specified.
145 desc
.value
= O
.value
;
147 // A value property is not specified.
150 if ("writable" in O
) {
151 // A writable property is specified.
152 desc
.writable
= !!O
.writable
;
154 // A writable property is not specified.
158 // A get property is specified.
159 const getter
= O
.get;
160 if (getter
!== undefined && typeof getter
!== "function") {
161 // The getter is not callable.
162 throw new TypeError("Piscēs: Getters must be callable.");
164 // The getter is callable.
168 // A get property is not specified.
172 // A set property is specified.
173 const setter
= O
.set;
174 if (setter
!== undefined && typeof setter
!== "function") {
175 // The setter is not callable.
176 throw new TypeError("Piscēs: Setters must be callable.");
178 // The setter is callable.
182 // A set property is not specified.
186 ("get" in desc
|| "set" in desc
) &&
187 ("value" in desc
|| "writable" in desc
)
189 // Both accessor and data attributes have been defined.
191 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
194 // The property descriptor is valid.
195 return new Proxy(desc
, propertyDescriptorProxyHandler
);
201 * Completes this property descriptor by setting missing values to
204 * This method modifies this object and returns undefined.
207 if (this !== undefined && !("get" in this || "set" in this)) {
208 // This is a generic or data descriptor.
209 if (!("value" in this)) {
210 // `value` is not defined on this.
211 this.value
= undefined;
213 // `value` is already defined on this.
216 if (!("writable" in this)) {
217 // `writable` is not defined on this.
218 this.writable
= false;
220 // `writable` is already defined on this.
224 // This is not a generic or data descriptor.
225 if (!("get" in this)) {
226 // `get` is not defined on this.
227 this.get = undefined;
229 // `get` is already defined on this.
232 if (!("set" in this)) {
233 // `set` is not defined on this.
234 this.set = undefined;
236 // `set` is already defined on this.
240 if (!("enumerable" in this)) {
241 // `enumerable` is not defined on this.
242 this.enumerable
= false;
244 // `enumerable` is already defined on this.
247 if (!("configurable" in this)) {
248 // `configurable` is not defined on this.
249 this.configurable
= false;
251 // `configurable` is already defined on this.
256 /** Gets whether this is an accessor descrtiptor. */
257 get isAccessorDescriptor() {
258 return this !== undefined && ("get" in this || "set" in this);
261 /** Gets whether this is a data descrtiptor. */
262 get isDataDescriptor() {
263 return this !== undefined &&
264 ("value" in this || "writable" in this);
267 /** Gets whether this is a fully‐populated property descriptor. */
268 get isFullyPopulated() {
269 return this !== undefined &&
270 ("value" in this && "writable" in this ||
271 "get" in this && "set" in this) &&
272 "enumerable" in this && "configurable" in this;
276 * Gets whether this is a generic (not accessor or data)
279 get isGenericDescriptor() {
280 return this !== undefined &&
281 !("get" in this || "set" in this || "value" in this ||
286 const coercePropertyDescriptorValue
= (P
, V
) => {
295 if (V
!== undefined && typeof V
!== "function") {
297 "Piscēs: Getters must be callable.",
303 if (V
!== undefined && typeof V
!== "function") {
305 "Piscēs: Setters must be callable.",
316 prototype: propertyDescriptorPrototype
,
317 } = PropertyDescriptor
;
319 const propertyDescriptorProxyHandler
= Object
.assign(
322 defineProperty(O
, P
, Desc
) {
324 P
=== "configurable" || P
=== "enumerable" ||
325 P
=== "writable" || P
=== "value" ||
326 P
=== "get" || P
=== "set"
328 // P is a property descriptor attribute.
329 const desc
= new PropertyDescriptor(Desc
);
330 if ("get" in desc
|| "set" in desc
) {
331 // Desc is an accessor property descriptor.
333 "Piscēs: Property descriptor attributes must be data properties.",
335 } else if ("value" in desc
|| !(P
in O
)) {
336 // Desc has a value or P does not already exist on O.
337 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
339 // Desc is not an accessor property descriptor and has no
343 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
344 "get" in O
|| "set" in O
;
345 const isDataDescriptor
= "value" === P
|| "writable" === P
||
346 "value" in O
|| "writable" in O
;
347 if (isAccessorDescriptor
&& isDataDescriptor
) {
348 // Both accessor and data attributes will be present on O
351 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
354 // P can be safely defined on O.
355 return defineOwnProperty(O
, P
, desc
);
358 // P is not a property descriptor attribute.
359 return defineOwnProperty(O
, P
, Desc
);
362 set(O
, P
, V
, Receiver
) {
364 P
=== "configurable" || P
=== "enumerable" ||
365 P
=== "writable" || P
=== "value" ||
366 P
=== "get" || P
=== "set"
368 // P is a property descriptor attribute.
369 const newValue
= coercePropertyDescriptorValue(P
, V
);
370 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
371 "get" in O
|| "set" in O
;
372 const isDataDescriptor
= "value" === P
|| "writable" === P
||
373 "value" in O
|| "writable" in O
;
374 if (isAccessorDescriptor
&& isDataDescriptor
) {
375 // Both accessor and data attributes will be present on O
378 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
381 // P can be safely defined on O.
383 // ☡ Receiver will be the *proxied* object, so passing it
384 // through to setPropertyValue here would produce an
387 // ☡ This has implications on objects with a proxied
388 // PropertyDescriptor in their prototype.
389 return setPropertyValue(O
, P
, newValue
, O
);
392 return setPropertyValue(O
, P
, V
, Receiver
);
395 setPrototypeOf(O
, V
) {
396 if (V
!== propertyDescriptorPrototype
) {
397 // V is not the property descriptor prototype.
400 // V is the property descriptor prototype.
401 return setPrototype(O
, V
);
407 return { PropertyDescriptor
};
411 * Defines an own property on the provided object on the provided
412 * property key using the provided property descriptor.
414 * ※ This is effectively an alias for `Object.defineProperty`.
416 export const defineOwnProperty
= createArrowFunction(
417 Object
.defineProperty
,
418 { name
: "defineOwnProperty" },
423 * Defines own properties on the provided object using the
424 * descriptors on the enumerable own properties of the provided
425 * additional objects.
427 * ※ This differs from `Object.defineProperties` in that it can take
428 * multiple source objects.
433 * Returns a new frozen shallow copy of the enumerable own properties
434 * of the provided object, according to the following rules :—
436 * - For data properties, create a nonconfigurable, nonwritable
437 * property with the same value.
439 * - For accessor properties, create a nonconfigurable accessor
440 * property with the same getter *and* setter.
442 * The prototype for the resulting object will be taken from the
443 * `.prototype` property of the provided constructor, or the
444 * `.prototype` of the `.constructor` of the provided object if the
445 * provided constructor is undefined. If the used constructor has a
446 * nonnullish `.[Symbol.species]`, that will be used instead. If the
447 * used constructor or species is nullish or does not have a
448 * `.prototype` property, the prototype is set to null.
450 * ※ The prototype of the provided object itself is ignored.
455 * Returns whether the provided object is frozen.
457 * ※ This function returns false for nonobjects.
459 * ※ This is effectively an alias for `!Object.isFrozen`.
464 * Returns whether the provided object is sealed.
466 * ※ This function returns false for nonobjects.
468 * ※ This is effectively an alias for `!Object.isSealed`.
473 * Sets the prototype of the provided object to the provided value
474 * and returns the object.
476 * ※ This is effectively an alias for `Object.setPrototypeOf`.
481 * Returns the provided value converted to an object.
483 * Existing objects are returned with no modification.
485 * ☡ This function throws if its argument is null or undefined.
489 const createObject
= Object
;
498 const { [ITERATOR
]: arrayIterator
} = Array
.prototype;
499 const { next
: arrayIteratorNext
} = getPrototypeOf([][ITERATOR
]());
501 next
: generatorIteratorNext
,
502 } = getPrototypeOf(function* () {}.prototype);
503 const splatIterablePrototype
= {
508 call(arrayIterator
, this.args
, []),
514 const splatIterable
= ($) =>
515 create(splatIterablePrototype
, { args
: { value
: $ } });
516 const propertyDescriptorEntryIterablePrototype
= {
519 next
: bind(generatorIteratorNext
, this.generator(), []),
523 const propertyDescriptorEntryIterable
= ($) =>
524 create(propertyDescriptorEntryIterablePrototype
, {
525 generator
: { value
: $ },
529 defineOwnProperties
: (O
, ...sources
) => {
530 for (const source
of splatIterable(sources
)) {
531 defineProperties(O
, source
);
535 frozenCopy
: (O
, constructor = O
?.constructor) => {
537 // O is null or undefined.
539 "Piscēs: Cannot copy properties of null or undefined.",
542 // O is not null or undefined.
544 // (If not provided, the constructor will be the value of
545 // getting the `.constructor` property of O.)
546 const species
= constructor?.[SPECIES
] ?? constructor;
547 return preventExtensions(
549 species
== null || !("prototype" in species
)
553 propertyDescriptorEntryIterable(function* () {
554 const ownPropertyKeys
= getOwnPropertyKeys(O
);
557 i
< ownPropertyKeys
.length
;
560 const P
= ownPropertyKeys
[i
];
561 const Desc
= getOwnPropertyDescriptor(O
, P
);
562 if (Desc
.enumerable
) {
563 // P is an enumerable property.
566 "get" in Desc
|| "set" in Desc
581 // P is not an enumerable property.
591 isUnfrozenObject
: (O
) => !isFrozen(O
),
592 isUnsealedObject
: (O
) => !isSealed(O
),
593 setPrototype
: (O
, proto
) => {
594 const obj
= toObject(O
);
596 // The provided value is an object; set its prototype normally.
597 return setPrototypeOf(O
, proto
);
599 // The provided value is not an object; attempt to set the
600 // prototype on a coerced version with extensions prevented,
601 // then return the provided value.
603 // This will throw if the given prototype does not match the
604 // existing one on the coerced object.
605 setPrototypeOf(preventExtensions(obj
), proto
);
611 // The provided value is nullish; this is an error.
613 `Piscēs: Cannot convert ${$} into an object.`,
616 // The provided value is not nullish; coerce it to an object.
617 return createObject($);
625 * Removes the provided property key from the provided object and
626 * returns the object.
628 * ※ This function differs from `Reflect.deleteProperty` and the
629 * `delete` operator in that it throws if the deletion is
632 * ☡ This function throws if the first argument is not an object.
637 * Returns an array of property keys on the provided object.
639 * ※ This is effectively an alias for `Reflect.ownKeys`, except that
640 * it does not require that the argument be an object.
645 * Returns the value of the provided property key on the provided
648 * ※ This is effectively an alias for `Reflect.get`, except that it
649 * does not require that the argument be an object.
654 * Returns whether the provided property key exists on the provided
657 * ※ This is effectively an alias for `Reflect.has`, except that it
658 * does not require that the argument be an object.
660 * ※ This includes properties present on the prototype chain.
665 * Sets the provided property key to the provided value on the
666 * provided object and returns the object.
668 * ※ This function differs from `Reflect.set` in that it throws if
669 * the setting is unsuccessful.
671 * ☡ This function throws if the first argument is not an object.
675 const { deleteProperty
, get, has
, ownKeys
, set } = Reflect
;
678 deleteOwnProperty
: (O
, P
) => {
679 if (type(O
) !== "object") {
681 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
683 } else if (!deleteProperty(O
, P
)) {
685 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
691 getOwnPropertyKeys
: (O
) => ownKeys(toObject(O
)),
692 getPropertyValue
: (O
, P
, Receiver
= O
) =>
693 get(toObject(O
), P
, Receiver
),
694 hasProperty
: (O
, P
) => has(toObject(O
), P
),
695 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
696 if (type(O
) !== "object") {
698 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
700 } else if (!set(O
, P
, V
, Receiver
)) {
702 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
712 * Marks the provided object as non·extensible and marks all its
713 * properties as nonconfigurable and (if data properties) nonwritable,
714 * and returns the object.
716 * ※ This is effectively an alias for `Object.freeze`.
718 export const freeze
= createArrowFunction(Object
.freeze
);
721 * Returns the function on the provided value at the provided property
724 * ☡ This function throws if the provided property key does not have an
725 * associated value which is callable.
727 export const getMethod
= (V
, P
) => {
728 const func
= getPropertyValue(V
, P
);
731 } else if (typeof func
!== "function") {
732 throw new TypeError(`Piscēs: Method not callable: ${P}`);
739 * Returns the property descriptor for the own property with the
740 * provided property key on the provided object, or null if none
743 * ※ This is effectively an alias for
744 * `Object.getOwnPropertyDescriptor`.
746 export const getOwnPropertyDescriptor
= createArrowFunction(
747 Object
.getOwnPropertyDescriptor
,
751 * Returns the property descriptors for the own properties on the
754 * ※ This is effectively an alias for
755 * `Object.getOwnPropertyDescriptors`.
757 export const getOwnPropertyDescriptors
= createArrowFunction(
758 Object
.getOwnPropertyDescriptors
,
762 * Returns an array of string‐valued own property keys on the
765 * ☡ This includes both enumerable and non·enumerable properties.
767 * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
769 export const getOwnPropertyStrings
= createArrowFunction(
770 Object
.getOwnPropertyNames
,
771 { name
: "getOwnPropertyStrings" },
775 * Returns an array of symbol‐valued own property keys on the
778 * ☡ This includes both enumerable and non·enumerable properties.
780 * ※ This is effectively an alias for
781 * `Object.getOwnPropertySymbols`.
783 export const getOwnPropertySymbols
= createArrowFunction(
784 Object
.getOwnPropertySymbols
,
788 * Returns the prototype of the provided object.
790 * ※ This is effectively an alias for `Object.getPrototypeOf`.
792 export const getPrototype
= createArrowFunction(
793 Object
.getPrototypeOf
,
794 { name
: "getPrototype" },
798 * Returns whether the provided object has an own property with the
799 * provided property key.
801 * ※ This is effectively an alias for `Object.hasOwn`.
803 export const hasOwnProperty
= createArrowFunction(Object
.hasOwn
, {
804 name
: "hasOwnProperty",
808 * Returns whether the provided object is extensible.
810 * ※ This function returns false for nonobjects.
812 * ※ This is effectively an alias for `Object.isExtensible`.
814 export const isExtensibleObject
= createArrowFunction(
816 { name
: "isExtensibleObject" },
820 * Returns an array of key~value pairs for the enumerable,
821 * string‐valued property keys on the provided object.
823 * ※ This is effectively an alias for `Object.entries`.
825 export const namedEntries
= createArrowFunction(Object
.entries
, {
826 name
: "namedEntries",
830 * Returns an array of the enumerable, string‐valued property keys on
831 * the provided object.
833 * ※ This is effectively an alias for `Object.keys`.
835 export const namedKeys
= createArrowFunction(Object
.keys
, {
840 * Returns an array of property values for the enumerable,
841 * string‐valued property keys on the provided object.
843 * ※ This is effectively an alias for `Object.values`.
845 export const namedValues
= createArrowFunction(Object
.values
, {
850 * Returns a new object with the provided prototype and property
853 * ※ This is effectively an alias for `Object.create`.
855 export const objectCreate
= createArrowFunction(Object
.create
, {
856 name
: "objectCreate",
860 * Returns a new object with the provided property keys and values.
862 * ※ This is effectively an alias for `Object.fromEntries`.
864 export const objectFromEntries
= createArrowFunction(
866 { name
: "objectFromEntries" },
870 * Marks the provided object as non·extensible, and returns the
873 * ※ This is effectively an alias for `Object.preventExtensions`.
875 export const preventExtensions
= createArrowFunction(
876 Object
.preventExtensions
,
880 * Marks the provided object as non·extensible and marks all its
881 * properties as nonconfigurable, and returns the object.
883 * ※ This is effectively an alias for `Object.seal`.
885 export const seal
= createArrowFunction(Object
.seal
);
888 * Sets the values of the enumerable own properties of the provided
889 * additional objects on the provided object.
891 * ※ This is effectively an alias for `Object.assign`.
893 export const setPropertyValues
= createArrowFunction(Object
.assign
, {
894 name
: "setPropertyValues",
898 * Returns the property key (symbol or string) corresponding to the
901 export const toPropertyKey
= ($) => {
902 const key
= toPrimitive($, "string");
903 return typeof key
=== "symbol" ? key
: `${key}`;