X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/6f1ff895670d04034ef09faea4779923a85097fb..e1cb83c479df2a3e4a5e918867a135ff9dde8121:/object.js diff --git a/object.js b/object.js index c0a9081..d382b31 100644 --- a/object.js +++ b/object.js @@ -1,102 +1,752 @@ -// ♓🌟 Piscēs ∷ object.js -// ==================================================================== -// -// Copyright © 2022 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 . +// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady +// SPDX-License-Identifier: MPL-2.0 +/** + * ⁌ ♓🧩 Piscēs ∷ object.js + * + * Copyright © 2022–2023, 2025 Lady [@ Ladys 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 { call } from "./function.js"; +import { + IS_CONCAT_SPREADABLE, + isAccessorDescriptor, + isDataDescriptor, + SPECIES, + toFunctionName, + toLength, + type, + UNDEFINED, +} from "./value.js"; -export const { - assign: assignProperties, - defineProperty: defineOwnProperty, - defineProperties: defineOwnProperties, - freeze, - getOwnPropertyDescriptor, - getOwnPropertyDescriptors, - getOwnPropertyNames: getOwnPropertyStrings, - getOwnPropertySymbols, - getPrototypeOf: getPrototype, - hasOwn: hasOwnProperty, +const PISCĒS = "♓🧩 Piscēs"; + +const createArray = Array; +const { isArray } = Array; +const object = Object; +const { + assign, + create, + defineProperty, + defineProperties, + entries, + freeze: objectFreeze, + fromEntries, + getOwnPropertyDescriptor: objectGetOwnPropertyDescriptor, + getOwnPropertyNames, + getOwnPropertySymbols: objectGetOwnPropertySymbols, + getPrototypeOf, + hasOwn, isExtensible, isFrozen, isSealed, - entries: namedEntries, - keys: namedKeys, - values: namedValues, - create: objectCreate, - fromEntries: objectFromEntries, - preventExtensions, - is: sameValue, - seal, - setPrototypeOf: setPrototype, + keys, + preventExtensions: objectPreventExtensions, + seal: objectSeal, + setPrototypeOf, + values, } = Object; - -export const { - delete: deleteOwnProperty, - keys: getOwnPropertyKeys, - get: getPropertyValue, - has: hasProperty, - set: setPropertyValue, +const { + apply: call, + deleteProperty, + defineProperty: reflectDefineProperty, + get, + getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor, + has, + ownKeys, + set, + setPrototypeOf: reflectSetPrototypeOf, } = Reflect; /** - * A property descriptor object. - * - * Actually constructing a property descriptor object using this class - * is only necessary if you need strict guarantees about the types of - * its properties; the resulting object is proxied to ensure the types - * match what one would expect from composing FromPropertyDescriptor - * and ToPropertyDescriptor in the Ecmascript specification. - * - * Otherwise, the instance properties and methods are generic. - */ -export const PropertyDescriptor = (() => { - class PropertyDescriptor extends null { - /** - * Constructs a new property descriptor object from the provided - * object. - * - * The resulting object is proxied to enforce types (for example, - * its `enumerable` property, if defined, will always be a - * boolean). - */ - //deno-lint-ignore constructor-super - constructor(Obj) { - if (!isObject(Obj)) { - // The provided value is not an object. + * 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") { + // The provided value is not an object; this is an error. + throw new TypeError( + `${PISCĒS}: Cannot construct LazyLoader: Provided argument is not an object: ${loadMethods}.`, + ); + } else { + // The provided value is an object. + // + // Process it and build the result. + const result = create(getPrototypeOf(loadMethods)); + const methodKeys = ownKeys(loadMethods); + for (let index = 0; index < methodKeys.length; ++index) { + // Iterate over the property keys of the provided object and + // define getters and setters appropriately on the result. + const methodKey = methodKeys[index]; + const { configurable, enumerable, writable } = + getOwnPropertyDescriptor(loadMethods, methodKey); + defineProperty( + result, + methodKey, + assign(create(null), { + configurable: true, + enumerable, + get: defineProperty( + () => { + const value = call(loadMethods[methodKey], result, []); + defineProperty( + result, + methodKey, + assign(create(null), { + configurable, + enumerable, + value, + writable, + }), + ); + return value; + }, + "name", + assign(create(null), { + value: toFunctionName(methodKey, "get"), + }), + ), + set: writable + ? defineProperty( + ($) => + defineProperty( + result, + methodKey, + assign(create(null), { + configurable, + enumerable, + value: $, + writable, + }), + ), + "name", + assign(create(null), { + value: toFunctionName(methodKey, "set"), + }), + ) + : UNDEFINED, + }), + ); + } + return result; + } + } +} + +/** + * Defines an own enumerable data property on the provided object with + * the provided property key and value. + */ +export const defineOwnDataProperty = (O, P, V) => + defineProperty( + O, + P, + assign(create(null), { + configurable: true, + enumerable: true, + value: V, + writable: true, + }), + ); + +/** + * Defines an own nonenumerable data property on the provided object + * with the provided property key and value. + */ +export const defineOwnNonenumerableDataProperty = (O, P, V) => + defineProperty( + O, + P, + assign(create(null), { + configurable: true, + enumerable: false, + value: V, + writable: true, + }), + ); + +/** + * Defines an own property on the provided object on the provided + * property key using the provided property descriptor. + * + * ※ This is effectively an alias for `Object.defineProperty´. + */ +export const defineOwnProperty = (O, P, Desc) => + defineProperty(O, P, Desc); + +/** + * Defines own properties on the provided object using the descriptors + * on the enumerable own properties of the provided additional objects. + * + * ※ This function differs from `Object.defineProperties´ in that it + * can take multiple source objects. + */ +export const defineOwnProperties = (O, ...sources) => { + const { length } = sources; + for (let k = 0; k < length; ++k) { + // Iterate over each source and define the appropriate properties + // on the provided object. + defineProperties(O, sources[k]); + } + return O; +}; + +/** + * Removes the provided property key from the provided object and + * returns the object. + * + * ※ 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. + */ +export const deleteOwnProperty = (O, P) => { + if (type(O) !== "object") { + // The provided value is not an object; this is an error. + throw new TypeError( + `${PISCĒS}: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!deleteProperty(O, P)) { + // The provided property could not be deleted on the provided + // value; this is an error. + throw new TypeError( + `${PISCĒS}: Tried to delete property from object but [[Delete]] returned false: ${P}`, + ); + } else { + // The provided property was successfully deleted. + // + // Return the provided value. + return O; + } +}; + +/** + * Marks the provided object as non·extensible and marks all its + * properties as nonconfigurable and (if data properties) nonwritable, + * and returns the object. + * + * ※ This is effectively an alias for `Object.freeze´. + */ +export const freeze = (O) => objectFreeze(O); + +/** + * Returns a new frozen shallow copy of the enumerable own properties + * of the provided object, according to the following rules :— + * + * - For data properties, create a nonconfigurable, nonwritable + * property with the same value. + * + * - For accessor properties, create a nonconfigurable accessor + * 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 + * provided constructor is undefined. If the used constructor has a + * 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. + * + * ※ The prototype of the provided object itself is ignored. + */ +export const frozenCopy = (O, constructor = O?.constructor) => { + if (O == null) { + // O is null or undefined. + throw new TypeError( + `${PISCĒS}: Cannot copy properties of null or undefined.`, + ); + } else { + // 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?.[SPECIES] ?? constructor; + const copy = create( + species == null || !("prototype" in species) + ? null + : species.prototype, + ); + const keys = ownKeys(O); + for (let k = 0; k < keys.length; ++k) { + // Iterate over each key and define the appropriate value on the + // result. + const P = keys[k]; + const Desc = getOwnPropertyDescriptor(O, P); + if (Desc.enumerable) { + // P is an enumerable property. + defineProperty( + copy, + P, + assign( + create(null), + isAccessorDescriptor(Desc) + ? { + configurable: false, + enumerable: true, + get: Desc.get, + set: Desc.set, + } + : { + configurable: false, + enumerable: true, + value: Desc.value, + writable: false, + }, + ), + ); + } else { + // P is not an enumerable property. + /* do nothing */ + } + } + return objectPreventExtensions(copy); + } +}; + +/** + * 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 either nullish or callable. + */ +export const getMethod = (V, P) => { + const func = V[P]; + if (func == null) { + // The value of the provided property is nullish. + // + // Return undefined. + return UNDEFINED; + } else if (typeof func !== "function") { + // The value of the provided property is not callable; this is an + // error. + throw new TypeError(`${PISCĒS}: Method not callable: ${P}`); + } else { + // The value of the provided property is callable. + // + // Return it. + return func; + } +}; + +/** + * Returns the property descriptor record for the own property with the + * provided property key on the provided value, or null if none exists. + * + * ※ This is effectively an alias for + * `Object.getOwnPropertyDescriptor´, but the return value is a proxied + * object with null prototype. + */ +export const getOwnPropertyDescriptor = (O, P) => { + const desc = objectGetOwnPropertyDescriptor(O, P); + return desc === UNDEFINED + ? UNDEFINED + : toPropertyDescriptorRecord(desc); +}; + +/** + * Returns the property descriptors for the own properties on the + * provided value. + * + * ※ This is effectively an alias for + * `Object.getOwnPropertyDescriptors´, but the values on the resulting + * object are proxied objects with null prototypes. + */ +export const getOwnPropertyDescriptors = (O) => { + const obj = toObject(O); + const keys = ownKeys(obj); + const descriptors = {}; + for (let k = 0; k < keys.length; ++k) { + // Iterate over the keys of the provided object and collect its + // descriptors. + const key = keys[k]; + defineOwnDataProperty( + descriptors, + key, + getOwnPropertyDescriptor(O, key), + ); + } + return descriptors; +}; + +/** + * Returns an array of own property entries on the provided value, + * using the provided receiver if given. + */ +export const getOwnPropertyEntries = (O, Receiver = O) => { + const obj = toObject(O); + const keys = ownKeys(obj); + const target = Receiver === UNDEFINED ? obj : toObject(Receiver); + const result = createArray(keys.length); + for (let k = 0; k < keys.length; ++k) { + // Iterate over each key and add the corresponding entry to the + // result. + const key = keys[k]; + defineOwnDataProperty( + result, + k, + [key, getOwnPropertyValue(obj, keys[k], target)], + ); + } + return result; +}; + +/** + * Returns an array of own property keys on the provided value. + * + * ※ This is effectively an alias for `Reflect.ownKeys´, except that + * it does not require that the argument be an object. + */ +export const getOwnPropertyKeys = (O) => ownKeys(toObject(O)); + +/** + * Returns an array of string‐valued own property keys on the + * provided value. + * + * ☡ This includes both enumerable and non·enumerable properties. + * + * ※ This is effectively an alias for `Object.getOwnPropertyNames´. + */ +export const getOwnPropertyStrings = (O) => getOwnPropertyNames(O); + +/** + * Returns an array of symbol‐valued own property keys on the + * provided value. + * + * ☡ This includes both enumerable and non·enumerable properties. + * + * ※ This is effectively an alias for + * `Object.getOwnPropertySymbols´. + */ +export const getOwnPropertySymbols = (O) => + objectGetOwnPropertySymbols(O); + +/** + * Returns the value of the provided own property on the provided + * value using the provided receiver, or undefined if the provided + * property is not an own property on the provided value. + * + * ※ If the receiver is not provided, it defaults to the provided + * value. + */ +export const getOwnPropertyValue = (O, P, Receiver = UNDEFINED) => { + const obj = toObject(O); + const desc = getOwnPropertyDescriptor(O, P); + if (desc === UNDEFINED) { + // The provided property is not an own property on the provided + // value. + // + // Return undefined. + return UNDEFINED; + } + if (isDataDescriptor(desc)) { + // The provided property is a data property. + // + // Return its value. + return desc.value; + } else { + // The provided property is an accessor property. + // + // Get its value using the appropriate receiver. + const target = Receiver === UNDEFINED ? obj : toObject(Receiver); + return call(desc.get, target, []); + } +}; + +/** + * Returns an array of own property values on the provided value, using + * the provided receiver if given. + */ +export const getOwnPropertyValues = (O, Receiver = O) => { + const obj = toObject(O); + const keys = ownKeys(obj); + const target = Receiver === UNDEFINED ? obj : toObject(Receiver); + const result = createArray(keys.length); + for (let k = 0; k < keys.length; ++k) { + // Iterate over each key and collect the values. + defineOwnDataProperty( + result, + k, + getOwnPropertyValue(obj, keys[k], target), + ); + } + return result; +}; + +/** + * Returns the value of the provided property key on the provided + * value. + * + * ※ This is effectively an alias for `Reflect.get´, except that it + * does not require that the argument be an object. + */ +export const getPropertyValue = (O, P, Receiver = O) => + get(toObject(O), P, Receiver); + +/** + * Returns the prototype of the provided value. + * + * ※ This is effectively an alias for `Object.getPrototypeOf´. + */ +export const getPrototype = (O) => getPrototypeOf(O); + +/** + * Returns whether the provided value has an own property with the + * provided property key. + * + * ※ This is effectively an alias for `Object.hasOwn´. + */ +export const hasOwnProperty = (O, P) => hasOwn(O, P); + +/** + * Returns whether the provided property key exists on the provided + * value. + * + * ※ 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. + */ +export const hasProperty = (O, P) => has(toObject(O), P); + +/** Returns whether the provided value is an arraylike object. */ +export const isArraylikeObject = ($) => { + if (type($) !== "object") { + // The provided value is not an object. + return false; + } else { + // The provided value is an object. + try { + // Try to get the length and return true. + // + // ※ If this throws, the object is not arraylike. + lengthOfArraylike($); + return true; + } catch { + // Getting the length failed; return false. + return false; + } + } +}; + +/** + * Returns whether the provided value is spreadable during array + * concatenation. + * + * This is also used to determine which things should be treated as + * collections. + */ +export const isConcatSpreadableObject = ($) => { + if (type($) !== "object") { + // The provided value is not an object. + return false; + } else { + // The provided value is an object. + const spreadable = $[IS_CONCAT_SPREADABLE]; + return spreadable !== UNDEFINED ? !!spreadable : isArray($); + } +}; + +/** + * Returns whether the provided value is an extensible object. + * + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `Object.isExtensible´. + */ +export const isExtensibleObject = (O) => isExtensible(O); + +export const { + /** + * Returns whether the provided value is a property descriptor record + * as created by `toPropertyDescriptor´. + * + * ※ This function is provided to enable inspection of whether an + * object uses the ♓🧩 Piscēs property descriptor record proxy + * implementation, not as a general test of whether an object + * satisfies the requirements for property descriptors. In most + * cases, a more specific test, like `isAccessorDescriptor´, + * `isDataDescriptor´, or `isGenericDescriptor´, is preferrable. + */ + isPropertyDescriptorRecord, + + /** + * Converts the provided value to a property descriptor record. + * + * ※ The prototype of a property descriptor record is always `null´. + * + * ※ Actually constructing a property descriptor object using this + * class is only necessary if you need strict guarantees about the + * types of its properties; the resulting object is proxied to ensure + * the types match what one would expect from composing + * `FromPropertyDescriptor´ and `ToPropertyDescriptor´ in the + * Ecmascript specification. + */ + toPropertyDescriptorRecord, +} = (() => { + const proxyConstructor = Proxy; + const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype; + const propertyDescriptorRecords = new WeakSet(); + const coercePropertyDescriptorValue = (P, V) => { + switch (P) { + case "configurable": + case "enumerable": + case "writable": + return !!V; + case "value": + return V; + case "get": + if (V !== UNDEFINED && typeof V !== "function") { + // The provided value is not callable; this is an error. + throw new TypeError( + `${PISCĒS}: Getters must be callable.`, + ); + } else { + // The provided value is callable. + return V; + } + case "set": + if (V !== UNDEFINED && typeof V !== "function") { + // The provided value is not callable; this is an error. + throw new TypeError( + `${PISCĒS}: Setters must be callable.`, + ); + } else { + // The provided value is callable. + return V; + } + default: + return V; + } + }; + const propertyDescriptorProxyHandler = objectFreeze( + assign( + create(null), + { + defineProperty(O, P, Desc) { + if ( + P === "configurable" || P === "enumerable" + || P === "writable" || P === "value" + || P === "get" || P === "set" + ) { + // `P´ is a property descriptor attribute. + const desc = assign(objectCreate(null), Desc); + if ("get" in desc || "set" in desc) { + // `Desc´ is an accessor property descriptor. + throw new TypeError( + `${PISCĒS}: Property descriptor attributes must be data properties.`, + ); + } else if ("value" in desc || !(P in O)) { + // `Desc´ has a value or `P´ does not already exist on + // `O´. + desc.value = coercePropertyDescriptorValue( + P, + desc.value, + ); + } else { + // `Desc´ is not an accessor property descriptor and has + // no value, but an existing value is present on `O´. + /* do nothing */ + } + const isAccessorDescriptor = "get" === P || "set" === P + || "get" in O || "set" in O; + const isDataDescriptor = "value" === P + || "writable" === P + || "value" in O || "writable" in O; + if (isAccessorDescriptor && isDataDescriptor) { + // Both accessor and data attributes will be present on + // `O´ after defining `P´; this is an error. + throw new TypeError( + `${PISCĒS}: Property descriptors cannot specify both accessor and data attributes.`, + ); + } else { + // `P´ can be safely defined on `O´. + return reflectDefineProperty(O, P, desc); + } + } else { + // `P´ is not a property descriptor attribute. + return reflectDefineProperty(O, P, Desc); + } + }, + setPrototypeOf(O, V) { + if (V !== null) { + // `V´ is not the property descriptor prototype. + return false; + } else { + // `V´ is the property descriptor prototype. + return reflectSetPrototypeOf(O, V); + } + }, + }, + ), + ); + + return { + isPropertyDescriptorRecord: ($) => + call(weakSetHas, propertyDescriptorRecords, [$]), + toPropertyDescriptorRecord: (Obj) => { + if (type(Obj) !== "object") { + // The provided value is not an object; this is an error. 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. - const desc = objectCreate(propertyDescriptorPrototype); + const desc = create(null); if ("enumerable" in Obj) { // An enumerable property is specified. - desc.enumerable = !!Obj.enumerable; + defineOwnDataProperty(desc, "enumerable", !!Obj.enumerable); } else { // An enumerable property is not specified. /* do nothing */ } if ("configurable" in Obj) { // A configurable property is specified. - desc.configurable = !!Obj.configurable; + defineOwnDataProperty( + desc, + "configurable", + !!Obj.configurable, + ); } else { // A configurable property is not specified. /* do nothing */ } if ("value" in Obj) { // A value property is specified. - desc.value = Obj.value; + defineOwnDataProperty(desc, "value", Obj.value); } else { // A value property is not specified. /* do nothing */ } if ("writable" in Obj) { // A writable property is specified. - desc.writable = !!Obj.writable; + defineOwnDataProperty(desc, "writable", !!Obj.writable); } else { // A writable property is not specified. /* do nothing */ @@ -104,12 +754,14 @@ export const PropertyDescriptor = (() => { if ("get" in Obj) { // A get property is specified. const getter = Obj.get; - if (typeof getter != "function") { - // The getter is not callable. - throw new TypeError("Piscēs: Getters must be callable."); + if (getter !== UNDEFINED && typeof getter !== "function") { + // The getter is not callable; this is an error. + throw new TypeError( + `${PISCĒS}: Getters must be callable.`, + ); } else { // The getter is callable. - desc.get = getter; + defineOwnDataProperty(desc, "get", Obj.get); } } else { // A get property is not specified. @@ -118,383 +770,241 @@ export const PropertyDescriptor = (() => { if ("set" in Obj) { // A set property is specified. const setter = Obj.set; - if (typeof setter != "function") { - // The setter is not callable. - throw new TypeError("Piscēs: Setters must be callable."); + if (setter !== UNDEFINED && typeof setter !== "function") { + // The setter is not callable; this is an error. + throw new TypeError( + `${PISCĒS}: Setters must be callable.`, + ); } else { // The setter is callable. - desc.set = setter; + defineOwnDataProperty(desc, "set", Obj.set); } } else { // A set property is not specified. /* do nothing */ } if ( - ("get" in desc || "set" in desc) && - ("value" in desc || "writable" in desc) + ("get" in desc || "set" in desc) + && ("value" in desc || "writable" in desc) ) { - // Both accessor and data attributes have been defined. + // Both accessor and data attributes have been defined; this + // is an error. throw new TypeError( - "Piscēs: Property descriptors cannot specify both accessor and data attributes.", + `${PISCĒS}: Property descriptors cannot specify both accessor and data attributes.`, ); } else { // The property descriptor is valid. - return new Proxy(desc, propertyDescriptorProxyHandler); + const record = new proxyConstructor( + desc, + propertyDescriptorProxyHandler, + ); + call(weakSetAdd, propertyDescriptorRecords, [record]); + return record; } } - } + }, + }; +})(); - /** - * Completes this property descriptor by setting missing values to - * their defaults. - * - * This method modifies this object and returns undefined. - */ - complete() { - if (this !== undefined && !("get" in this || "set" in this)) { - // This is a generic or data descriptor. - if (!("value" in this)) { - // `value` is not defined on this. - this.value = undefined; - } else { - // `value` is already defined on this. - /* do nothing */ - } - if (!("writable" in this)) { - // `writable` is not defined on this. - this.writable = false; - } else { - // `writable` is already defined on this. - /* do nothing */ - } - } else { - // This is not a generic or data descriptor. - if (!("get" in this)) { - // `get` is not defined on this. - this.get = undefined; - } else { - // `get` is already defined on this. - /* do nothing */ - } - if (!("set" in this)) { - // `set` is not defined on this. - this.set = undefined; - } else { - // `set` is already defined on this. - /* do nothing */ - } - } - if (!("enumerable" in this)) { - // `enumerable` is not defined on this. - this.enumerable = false; - } else { - // `enumerable` is already defined on this. - /* do nothing */ - } - if (!("configurable" in this)) { - // `configurable` is not defined on this. - this.configurable = false; - } else { - // `configurable` is already defined on this. - /* do nothing */ - } - } +/** + * Returns whether the provided value is an unfrozen object. + * + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `!Object.isFrozen´. + */ +export const isUnfrozenObject = (O) => !isFrozen(O); - /** Returns whether this is an accessor descrtiptor. */ - get isAccessorDescriptor() { - return this !== undefined && ("get" in this || "set" in this); - } +/** + * Returns whether the provided value is an unsealed object. + * + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `!Object.isSealed´. + */ +export const isUnsealedObject = (O) => !isSealed(O); - /** Returns whether this is a data descrtiptor. */ - get isDataDescriptor() { - return this !== undefined && - ("value" in this || "writable" in this); - } +/** + * Returns the length of the provided arraylike value. + * + * This can produce larger lengths than can actually be stored in + * arrays, because no such restrictions exist on arraylike methods. + * + * ☡ This function throws if the provided value is not arraylike. + */ +export const lengthOfArraylike = ({ length }) => toLength(length); - /** - * Returns whether this is a fully‐populated property descriptor. - */ - get isFullyPopulated() { - return this !== undefined && - ("value" in this && "writable" in this || - "get" in this && "set" in this) && - "enumerable" in this && "configurable" in this; - } +/** + * Returns an array of key~value pairs for the enumerable, + * string‐valued property keys on the provided value. + * + * ※ This is effectively an alias for `Object.entries´. + */ +export const namedEntries = (O) => entries(O); - /** - * Returns whether this is a generic (not accessor or data) - * descrtiptor. - */ - get isGenericDescriptor() { - return this !== undefined && - !("get" in this || "set" in this || "value" in this || - "writable" in this); - } - } +/** + * Returns an array of the enumerable, string‐valued property keys on + * the provided value. + * + * ※ This is effectively an alias for `Object.keys´. + */ +export const namedKeys = (O) => keys(O); - const coercePropretyDescriptorValue = (P, V) => { - switch (P) { - case "configurable": - case "enumerable": - case "writable": - return !!V; - case "value": - return V; - case "get": - if (typeof V != "function") { - throw new TypeError( - "Piscēs: Getters must be callable.", - ); - } else { - return V; - } - case "set": - if (typeof V != "function") { - throw new TypeError( - "Piscēs: Setters must be callable.", - ); - } else { - return V; - } - default: - return V; - } - }; +/** + * Returns an array of property values for the enumerable, + * string‐valued property keys on the provided value. + * + * ※ This is effectively an alias for `Object.values´. + */ +export const namedValues = (O) => values(O); - const propertyDescriptorPrototype = PropertyDescriptor.prototype; +/** + * Returns a new object with the provided prototype and property + * descriptors. + * + * ※ This is effectively an alias for `Object.create´. + */ +export const objectCreate = (O, Properties) => create(O, Properties); - const propertyDescriptorProxyHandler = assignProperties( - objectCreate(null), - { - defineProperty(O, P, Desc) { - if ( - P === "configurable" || P === "enumerable" || - P === "writable" || P === "value" || - P === "get" || P === "set" - ) { - // P is a property descriptor attribute. - const desc = new PropertyDescriptor(Desc); - if ("get" in desc || "set" in desc) { - // Desc is an accessor property descriptor. - throw new TypeError( - "Piscēs: Property descriptor attributes must be data properties.", - ); - } else if ("value" in desc) { - // Desc has a value. - desc.value = coercePropretyDescriptorValue(P, desc.value); - } else { - // Desc is not an accessor property descriptor and has no - // value. - /* do nothing */ - } - const isAccessorDescriptor = "get" === P || "set" === P || - "get" in O || "set" in O; - const isDataDescriptor = "value" === P || "writable" === P || - "value" in O || "writable" in O; - if (isAccessorDescriptor && isDataDescriptor) { - // Both accessor and data attributes will be present on O - // after defining P. - throw new TypeError( - "Piscēs: Property descriptors cannot specify both accessor and data attributes.", - ); - } else { - // P can be safely defined on O. - return defineOwnProperty(O, P, desc); - } - } else { - // P is not a property descriptor attribute. - return defineOwnProperty(O, P, Desc); - } - }, - set(O, P, V, Receiver) { - const newValue = coercePropertyDescriptorValue(P, V); - const isAccessorDescriptor = "get" === P || "set" === P || - "get" in O || "set" in O; - const isDataDescriptor = "value" === P || "writable" === P || - "value" in O || "writable" in O; - if (isAccessorDescriptor && isDataDescriptor) { - // Both accessor and data attributes will be present on O - // after defining P. - throw new TypeError( - "Piscēs: Property descriptors cannot specify both accessor and data attributes.", - ); - } else { - // P can be safely defined on O. - return setPropertyValue(O, prop, newValue, Receiver); - } - }, - setPrototypeOf(O, V) { - if (V !== propertyDescriptorPrototype) { - // V is not the property descriptor prototype. - return false; - } else { - // V is the property descriptor prototype. - return setPrototype(O, V); - } - }, - }, - ); +/** + * Returns a new object with property keys and values from the provided + * iterable value. + * + * ※ This is effectively an alias for `Object.fromEntries´. + */ +export const objectFromEntries = (iterable) => fromEntries(iterable); - return PropertyDescriptor; -})(); +/** + * Marks the provided object as non·extensible, and returns the + * object. + * + * ※ This is effectively an alias for `Object.preventExtensions´. + */ +export const preventExtensions = (O) => objectPreventExtensions(O); /** - * Returns a new frozen shallow copy of the enumerable own properties - * of the provided object, according to the following rules :— + * Marks the provided object as non·extensible and marks all its + * properties as nonconfigurable, and returns the object. * - * - For data properties, create a nonconfigurable, nonwritable - * property with the same value. + * ※ This is effectively an alias for `Object.seal´. + */ +export const seal = (O) => objectSeal(O); + +/** + * Sets the provided property key to the provided value on the provided + * object and returns the object. * - * - For accessor properties, create a nonconfigurable accessor - * property with the same getter *and* setter. + * ※ This function differs from `Reflect.set´ in that it throws if the + * setting is unsuccessful. * - * 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 provided - * constructor is undefined. If the used constructor has a nonnullish - * `Symbol.species`, that will be used instead. + * ☡ This function throws if the first argument is not an object. */ -export const frozenCopy = (O, constructor = O?.constructor) => { - if (O == null) { - // O is null or undefined. +export const setPropertyValue = (O, P, V, Receiver = O) => { + if (type(O) !== "object") { + // The provided value is not an object; this is an error. + throw new TypeError( + `${PISCĒS}: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!set(O, P, V, Receiver)) { + // Setting the property fails; this is an error. throw new TypeError( - "Piscēs: Cannot copy properties of null or undefined.", + `${PISCĒS}: Tried to set property on object but [[Set]] returned false: ${P}`, ); } else { - // O is not null or undefined. + // The property was successfully set. // - // (If not provided, the constructor will be the value of getting - // the `constructor` property of O.) - const species = constructor?.[Symbol.species] ?? constructor; - return preventExtensions( - objectCreate( - species == null || !("prototype" in species) - ? null - : species.prototype, - objectFromEntries( - function* () { - for (const P of getOwnPropertyKeys(O)) { - const Desc = getOwnPropertyDescriptor(O, P); - if (Desc.enumerable) { - // P is an enumerable property. - yield [ - P, - "get" in Desc || "set" in Desc - ? { - configurable: false, - enumerable: true, - get: Desc.get, - set: Desc.set, - } - : { - configurable: false, - enumerable: true, - value: Desc.value, - writable: false, - }, - ]; - } else { - // P is not an enumerable property. - /* do nothing */ - } - } - }(), - ), - ), - ); + // Return the provided object. + return O; } }; -/** Returns whether the provided value is an object. */ -export const isObject = ($) => { - return $ !== null && - (typeof $ == "function" || typeof $ == "object"); -}; - /** - * Returns the primitive value of the provided object per its - * `toString` and `valueOf` methods. - * - * If the provided hint is "string", then `toString` takes precedence; - * otherwise, `valueOf` does. - * - * Throws an error if both of these methods are not callable or do not - * return a primitive. - */ -export const ordinaryToPrimitive = (O, hint) => { - for ( - const name of hint == "string" - ? ["toString", "valueOf"] - : ["valueOf", "toString"] - ) { - const method = O[name]; - if (typeof method == "function") { - // Method is callable. - const result = call(method, O, []); - if (!isObject(result)) { - // Method returns a primitive. - return result; - } else { - // Method returns an object. - continue; + * Sets the values of the enumerable own properties of the provided + * additional objects on the provided values. + * + * ※ This is effectively an alias for `Object.assign´. + */ +export const setPropertyValues = (target, source, ...sources) => { + const to = toObject(target); + for (let i = -1; i < sources.length; ++i) { + // Iterate over each source and set the appropriate property + // values. + const nextSource = i === -1 ? source : sources[i]; + if (nextSource != null) { + // The current source is not nullish. + // + // Handle its own properties. + const from = toObject(nextSource); + const keys = ownKeys(from); + for (let k = 0; k < keys.length; ++k) { + // Iterate over each key in the current source and set it in + // the target object if it is enumerable. + const nextKey = keys[k]; + const desc = reflectGetOwnPropertyDescriptor(from, nextKey); + if (desc !== UNDEFINED && desc.enumerable) { + // The current key is present and enumerable. + // + // Set it to its corresponding value. + const propValue = from[nextKey]; + to[nextKey] = propValue; + } else { + // The current key is not present or not enumerable. + /* do nothing */ + } } } else { - // Method is not callable. - continue; + // The current source is nullish. + /* do nothing */ } } - throw new TypeError("Piscēs: Unable to convert object to primitive"); + return to; }; /** - * Returns the provided value converted to a primitive, or throws if - * no such conversion is possible. + * Sets the prototype of the provided object to the provided value and + * returns the object. * - * The provided preferred type, if specified, should be "string", - * "number", or "default". If the provided input has a - * `Symbol.toPrimitive` method, this function will throw rather than - * calling that method with a preferred type other than one of the - * above. + * ※ This is effectively an alias for `Object.setPrototypeOf´, but it + * won¦t throw when setting the prototype of a primitive to its current + * value. */ -export const toPrimitive = ($, preferredType) => { - if (isObject($)) { +export const setPrototype = (O, proto) => { + const obj = toObject(O); + if (O === obj) { // The provided value is an object. - const exoticToPrim = $[Symbol.toPrimitive] ?? undefined; - if (exoticToPrim !== undefined) { - // The provided value has an exotic primitive conversion method. - if (typeof exoticToPrim != "function") { - // The method is not callable. - throw new TypeError( - "Piscēs: Symbol.toPrimitive was neither nullish nor callable.", - ); - } else { - // The method is callable. - const hint = `${preferredType ?? "default"}`; - if (!["default", "string", "number"].includes(hint)) { - // An invalid preferred type was specified. - throw new TypeError( - `Piscēs: Invalid preferred type: ${preferredType}.`, - ); - } else { - // The resulting hint is either default, string, or number. - return call(exoticToPrim, $, [hint]); - } - } - } else { - // Use the ordinary primitive conversion function. - ordinaryToPrimitive($, hint); - } + // + // Set its prototype normally. + return setPrototypeOf(O, proto); } else { - // The provided value is already a primitive. - return $; + // The provided value is not an object. + // + // Attempt to set the prototype on a coerced version with + // extensions prevented, then return the provided value. + // + // ☡ This will throw if the given prototype does not match the + // existing one on the coerced object. + setPrototypeOf(objectPreventExtensions(obj), proto); + return O; } }; /** - * Returns the property key (symbol or string) corresponding to the - * provided value. + * Returns the provided value converted to an object. + * + * Existing objects are returned with no modification. + * + * ☡ This function throws if its argument is null or undefined. */ -export const toPropertyKey = ($) => { - const key = toPrimitive($, "string"); - return typeof key == "symbol" ? key : `${key}`; +export const toObject = ($) => { + if ($ == null) { + // The provided value is nullish; this is an error. + throw new TypeError( + `${PISCĒS}: Cannot convert ${$} into an object.`, + ); + } else { + // The provided value is not nullish. + // + // Coerce it to an object. + return object($); + } };