X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/7c3a2eda590637af9463e5a59ab798f802d274a9..83f6aae0d1b8181dc2b0c6ccdba9f2fe2fdba3e6:/object.js diff --git a/object.js b/object.js index 578f84f..a403795 100644 --- a/object.js +++ b/object.js @@ -1,14 +1,82 @@ // ♓🌟 Piscēs ∷ object.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . import { bind, call } from "./function.js"; -import { toPrimitive, type } from "./value.js"; +import { ITERATOR, SPECIES, toPrimitive, type } from "./value.js"; + +/** + * An object whose properties are lazy‐loaded from the methods on the + * own properties of the provided object. + * + * This is useful when you are looking to reference properties on + * objects which, due to module dependency graphs, cannot be guaranteed + * to have been initialized yet. + * + * The resulting properties will have the same attributes (regarding + * configurability, enumerability, and writability) as the + * corresponding properties on the methods object. If a property is + * marked as writable, the method will never be called if it is set + * before it is gotten. By necessity, the resulting properties are all + * configurable before they are accessed for the first time. + * + * Methods will be called with the resulting object as their this + * value. + * + * `LazyLoader` objects have the same prototype as the passed methods + * object. + */ +export class LazyLoader extends null { + /** + * Constructs a new `LazyLoader` object. + * + * ☡ This function throws if the provided value is not an object. + */ + constructor(loadMethods) { + if (type(loadMethods) !== "object") { + throw new TypeError( + `Piscēs: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`, + ); + } else { + const result = objectCreate(getPrototype(loadMethods)); + const methodKeys = getOwnPropertyKeys(loadMethods); + for (let index = 0; index < methodKeys.length; ++index) { + const methodKey = methodKeys[index]; + const { configurable, enumerable, writable } = + getOwnPropertyDescriptor(loadMethods, methodKey); + defineOwnProperty(result, methodKey, { + configurable: true, + enumerable, + get: () => { + const value = call(loadMethods[methodKey], result, []); + defineOwnProperty(result, methodKey, { + configurable, + enumerable, + value, + writable, + }); + return value; + }, + set: writable + ? ($) => + defineOwnProperty(result, methodKey, { + configurable, + enumerable, + value: $, + writable, + }) + : void {}, + }); + } + return result; + } + } +} /** * A property descriptor object. @@ -28,15 +96,14 @@ export const { PropertyDescriptor } = (() => { * object. * * The resulting object is proxied to enforce types (for example, - * its `enumerable` property, if defined, will always be a + * its `.enumerable` property, if defined, will always be a * boolean). */ - //deno-lint-ignore constructor-super constructor(O) { if (type(O) !== "object") { // The provided value is not an object. throw new TypeError( - "Piscēs: Cannot convert primitive to property descriptor.", + `Piscēs: Cannot convert primitive to property descriptor: ${O}.`, ); } else { // The provided value is an object. @@ -328,7 +395,7 @@ export const { * descriptors on the enumerable own properties of the provided * additional objects. * - * ※ This differs from Object.defineProperties in that it can take + * ※ This differs from `Object.defineProperties` in that it can take * multiple source objects. */ defineOwnProperties, @@ -352,7 +419,7 @@ export const { * Defines an own property on the provided object on the provided * property key using the provided property descriptor. * - * ※ This is an alias for Object.defineProperty. + * ※ This is an alias for `Object.defineProperty`. */ defineProperty: defineOwnProperty, @@ -361,7 +428,7 @@ export const { * properties as nonconfigurable and (if data properties) * nonwritable, and returns the object. * - * ※ This is an alias for Object.freeze. + * ※ This is an alias for `Object.freeze`. */ freeze, @@ -370,7 +437,7 @@ export const { * provided property key on the provided object, or null if none * exists. * - * ※ This is an alias for Object.getOwnPropertyDescriptor. + * ※ This is an alias for `Object.getOwnPropertyDescriptor`. */ getOwnPropertyDescriptor, @@ -378,7 +445,7 @@ export const { * Returns the property descriptors for the own properties on the * provided object. * - * ※ This is an alias for Object.getOwnPropertyDescriptors. + * ※ This is an alias for `Object.getOwnPropertyDescriptors`. */ getOwnPropertyDescriptors, @@ -388,7 +455,7 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for Object.getOwnPropertyNames. + * ※ This is an alias for `Object.getOwnPropertyNames`. */ getOwnPropertyNames: getOwnPropertyStrings, @@ -398,14 +465,14 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for Object.getOwnPropertySymbols. + * ※ This is an alias for `Object.getOwnPropertySymbols`. */ getOwnPropertySymbols, /** * Returns the prototype of the provided object. * - * ※ This is an alias for Object.getPrototypeOf. + * ※ This is an alias for `Object.getPrototypeOf`. */ getPrototypeOf: getPrototype, @@ -413,36 +480,36 @@ export const { * Returns whether the provided object has an own property with the * provided property key. * - * ※ This is an alias for Object.hasOwn. + * ※ This is an alias for `Object.hasOwn`. */ hasOwn: hasOwnProperty, /** * Returns whether the provided object is extensible. * - * ※ This is an alias for Object.isExtensible. + * ※ This is an alias for `Object.isExtensible`. */ - isExtensible, + isExtensible: isExtensibleObject, /** * Returns whether the provided object is frozen. * - * ※ This is an alias for Object.isFrozen. + * ※ This is an alias for `Object.isFrozen`. */ - isFrozen, + isFrozen: isFrozenObject, /** * Returns whether the provided object is sealed. * - * ※ This is an alias for Object.isSealed. + * ※ This is an alias for `Object.isSealed`. */ - isSealed, + isSealed: isSealedObject, /** * Returns an array of key~value pairs for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for Object.entries. + * ※ This is an alias for `Object.entries`. */ entries: namedEntries, @@ -450,7 +517,7 @@ export const { * Returns an array of the enumerable, string‐valued property keys on * the provided object. * - * ※ This is an alias for Object.keys. + * ※ This is an alias for `Object.keys`. */ keys: namedKeys, @@ -458,7 +525,7 @@ export const { * Returns an array of property values for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for Object.values. + * ※ This is an alias for `Object.values`. */ values: namedValues, @@ -466,14 +533,14 @@ export const { * Returns a new object with the provided prototype and property * descriptors. * - * ※ This is an alias for Object.create. + * ※ This is an alias for `Object.create`. */ create: objectCreate, /** * Returns a new object with the provided property keys and values. * - * ※ This is an alias for Object.fromEntries. + * ※ This is an alias for `Object.fromEntries`. */ fromEntries: objectFromEntries, @@ -481,7 +548,7 @@ export const { * Marks the provided object as non·extensible, and returns the * object. * - * ※ This is an alias for Object.preventExtensions. + * ※ This is an alias for `Object.preventExtensions`. */ preventExtensions, @@ -489,7 +556,7 @@ export const { * Marks the provided object as non·extensible and marks all its * properties as nonconfigurable, and returns the object. * - * ※ This is an alias for Object.seal. + * ※ This is an alias for `Object.seal`. */ seal, @@ -497,7 +564,7 @@ export const { * Sets the values of the enumerable own properties of the provided * additional objects on the provided object. * - * ※ This is an alias for Object.assign. + * ※ This is an alias for `Object.assign`. */ assign: setPropertyValues, @@ -505,7 +572,7 @@ export const { * Sets the prototype of the provided object to the provided value * and returns the object. * - * ※ This is an alias for Object.setPrototypeOf. + * ※ This is an alias for `Object.setPrototypeOf`. */ setPrototypeOf: setPrototype, } = Object; @@ -515,26 +582,62 @@ export const { * Removes the provided property key from the provided object and * returns the object. * - * ☡ This function differs from Reflect.deleteProperty and the + * ※ This function differs from `Reflect.deleteProperty` and the * `delete` operator in that it throws if the deletion is * unsuccessful. + * + * ☡ This function throws if the first argument is not an object. */ deleteOwnProperty, + /** + * Returns an array of property keys on the provided object. + * + * ※ This is effectively an alias for `Reflect.ownKeys`, except that + * it does not require that the argument be an object. + */ + getOwnPropertyKeys, + + /** + * Returns the value of the provided property key on the provided + * object. + * + * ※ This is effectively an alias for `Reflect.get`, except that it + * does not require that the argument be an object. + */ + getPropertyValue, + + /** + * Returns whether the provided property key exists on the provided + * object. + * + * ※ This is effectively an alias for `Reflect.has`, except that it + * does not require that the argument be an object. + * + * ※ This includes properties present on the prototype chain. + */ + hasProperty, + /** * Sets the provided property key to the provided value on the * provided object and returns the object. * - * ※ This function differs from Reflect.set in that it throws if the - * setting is unsuccessful. + * ※ This function differs from `Reflect.set` in that it throws if + * the setting is unsuccessful. + * + * ☡ This function throws if the first argument is not an object. */ setPropertyValue, } = (() => { - const { deleteProperty, set } = Reflect; + const { deleteProperty, get, has, ownKeys, set } = Reflect; return { deleteOwnProperty: (O, P) => { - if (!deleteProperty(O, P)) { + if (type(O) !== "object") { + throw new TypeError( + `Piscēs: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!deleteProperty(O, P)) { throw new TypeError( `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`, ); @@ -542,8 +645,16 @@ export const { return O; } }, + getOwnPropertyKeys: (O) => ownKeys(toObject(O)), + getPropertyValue: (O, P, Receiver = O) => + get(toObject(O), P, Receiver), + hasProperty: (O, P) => has(toObject(O), P), setPropertyValue: (O, P, V, Receiver = O) => { - if (!set(O, P, V, Receiver)) { + if (type(O) !== "object") { + throw new TypeError( + `Piscēs: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!set(O, P, V, Receiver)) { throw new TypeError( `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`, ); @@ -557,7 +668,7 @@ export const { export const { /** * Returns a new frozen shallow copy of the enumerable own properties - * of the provided object, according to the following rules :— + * of the provided object, according to the following rules :— * * - For data properties, create a nonconfigurable, nonwritable * property with the same value. @@ -566,24 +677,22 @@ export const { * property with the same getter *and* setter. * * The prototype for the resulting object will be taken from the - * `prototype` property of the provided constructor, or the - * `prototype` of the `constructor` of the provided object if the + * `.prototype` property of the provided constructor, or the + * `.prototype` of the `.constructor` of the provided object if the * provided constructor is undefined. If the used constructor has a - * nonnullish `Symbol.species`, that will be used instead. If the + * nonnullish `.[Symbol.species]`, that will be used instead. If the * used constructor or species is nullish or does not have a - * `prototype` property, the prototype is set to null. + * `.prototype` property, the prototype is set to null. + * + * ※ The prototype of the provided object itself is ignored. */ frozenCopy, } = (() => { - const { - iterator: iteratorSymbol, - species: speciesSymbol, - } = Symbol; const { next: generatorIteratorNext, } = getPrototype(function* () {}.prototype); const propertyDescriptorEntryIterablePrototype = { - [iteratorSymbol]() { + [ITERATOR]() { return { next: bind(generatorIteratorNext, this.generator(), []), }; @@ -600,8 +709,8 @@ export const { // O is not null or undefined. // // (If not provided, the constructor will be the value of - // getting the `constructor` property of O.) - const species = constructor?.[speciesSymbol] ?? constructor; + // getting the `.constructor` property of O.) + const species = constructor?.[SPECIES] ?? constructor; return preventExtensions( objectCreate( species == null || !("prototype" in species) @@ -656,46 +765,44 @@ export const { }; })(); -export const { - /** - * Returns an array of property keys on the provided object. - * - * ※ This is an alias for Reflect.ownKeys. - */ - ownKeys: getOwnPropertyKeys, - - /** - * Returns the value of the provided property key on the provided - * object. - * - * ※ This is an alias for Reflect.get. - */ - get: getPropertyValue, - - /** - * Returns whether the provided property key exists on the provided - * object. - * - * ※ This is an alias for Reflect.has. - * - * ※ This includes properties present on the prototype chain. - */ - has: hasProperty, -} = Reflect; +/** + * Returns the function on the provided value at the provided property + * key. + * + * ☡ This function throws if the provided property key does not have an + * associated value which is callable. + */ +export const getMethod = (V, P) => { + const func = getPropertyValue(V, P); + if (func == null) { + return undefined; + } else if (typeof func !== "function") { + throw new TypeError(`Piscēs: Method not callable: ${P}`); + } else { + return func; + } +}; /** * Returns the provided value converted to an object. * - * Null and undefined are converted to a new, empty object. Other - * primitives are wrapped. Existing objects are returned with no - * modification. + * Existing objects are returned with no modification. * - * ※ This is effectively a nonconstructible version of the Object - * constructor. + * ☡ This function throws if its argument is null or undefined. */ export const { toObject } = (() => { const makeObject = Object; - return { toObject: ($) => makeObject($) }; + return { + toObject: ($) => { + if ($ == null) { + throw new TypeError( + `Piscēs: Cannot convert ${$} into an object.`, + ); + } else { + return makeObject($); + } + }, + }; })(); /**