// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+import { bind, call } from "./function.js";
+import { 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. */
+ constructor(loadMethods) {
+ 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.
*
*
* Otherwise, the instance properties and methods are generic.
*/
-export const PropertyDescriptor = (() => {
+export const { PropertyDescriptor } = (() => {
class PropertyDescriptor extends null {
/**
* Constructs a new property descriptor object from the provided
* boolean).
*/
//deno-lint-ignore constructor-super
- constructor(Obj) {
- if (!isObject(Obj)) {
+ constructor(O) {
+ if (type(O) !== "object") {
// The provided value is not an object.
throw new TypeError(
"Piscēs: Cannot convert primitive to property descriptor.",
);
} else {
// The provided value is an object.
- const desc = Object.create(propertyDescriptorPrototype);
- if ("enumerable" in Obj) {
+ const desc = objectCreate(propertyDescriptorPrototype);
+ if ("enumerable" in O) {
// An enumerable property is specified.
- desc.enumerable = !!Obj.enumerable;
+ desc.enumerable = !!O.enumerable;
} else {
// An enumerable property is not specified.
/* do nothing */
}
- if ("configurable" in Obj) {
+ if ("configurable" in O) {
// A configurable property is specified.
- desc.configurable = !!Obj.configurable;
+ desc.configurable = !!O.configurable;
} else {
// A configurable property is not specified.
/* do nothing */
}
- if ("value" in Obj) {
+ if ("value" in O) {
// A value property is specified.
- desc.value = Obj.value;
+ desc.value = O.value;
} else {
// A value property is not specified.
/* do nothing */
}
- if ("writable" in Obj) {
+ if ("writable" in O) {
// A writable property is specified.
- desc.writable = !!Obj.writable;
+ desc.writable = !!O.writable;
} else {
// A writable property is not specified.
/* do nothing */
}
- if ("get" in Obj) {
+ if ("get" in O) {
// A get property is specified.
- const getter = Obj.get;
- if (typeof getter != "function") {
+ const getter = O.get;
+ if (getter !== undefined && typeof getter !== "function") {
// The getter is not callable.
throw new TypeError("Piscēs: Getters must be callable.");
} else {
// A get property is not specified.
/* do nothing */
}
- if ("set" in Obj) {
+ if ("set" in O) {
// A set property is specified.
- const setter = Obj.set;
- if (typeof setter != "function") {
+ const setter = O.set;
+ if (setter !== undefined && typeof setter !== "function") {
// The setter is not callable.
throw new TypeError("Piscēs: Setters must be callable.");
} else {
}
}
- /** Returns whether this is an accessor descrtiptor. */
+ /** Gets whether this is an accessor descrtiptor. */
get isAccessorDescriptor() {
return this !== undefined && ("get" in this || "set" in this);
}
- /** Returns whether this is a data descrtiptor. */
+ /** Gets whether this is a data descrtiptor. */
get isDataDescriptor() {
return this !== undefined &&
("value" in this || "writable" in this);
}
- /**
- * Returns whether this is a fully‐populated property descriptor.
- */
+ /** Gets whether this is a fully‐populated property descriptor. */
get isFullyPopulated() {
return this !== undefined &&
("value" in this && "writable" in this ||
}
/**
- * Returns whether this is a generic (not accessor or data)
+ * Gets whether this is a generic (not accessor or data)
* descrtiptor.
*/
get isGenericDescriptor() {
}
}
- const coercePropretyDescriptorValue = (P, V) => {
+ const coercePropertyDescriptorValue = (P, V) => {
switch (P) {
case "configurable":
case "enumerable":
case "value":
return V;
case "get":
- if (typeof V != "function") {
+ if (V !== undefined && typeof V !== "function") {
throw new TypeError(
"Piscēs: Getters must be callable.",
);
return V;
}
case "set":
- if (typeof V != "function") {
+ if (V !== undefined && typeof V !== "function") {
throw new TypeError(
"Piscēs: Setters must be callable.",
);
}
};
- const propertyDescriptorPrototype = PropertyDescriptor.prototype;
+ const {
+ prototype: propertyDescriptorPrototype,
+ } = PropertyDescriptor;
const propertyDescriptorProxyHandler = Object.assign(
Object.create(null),
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 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.
);
} else {
// P can be safely defined on O.
- return Reflect.defineProperty(O, P, desc);
+ return defineOwnProperty(O, P, desc);
}
} else {
// P is not a property descriptor attribute.
- return Reflect.defineProperty(O, P, Desc);
+ 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.",
- );
+ if (
+ P === "configurable" || P === "enumerable" ||
+ P === "writable" || P === "value" ||
+ P === "get" || P === "set"
+ ) {
+ // P is a property descriptor attribute.
+ 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.
+ //
+ // ☡ Receiver will be the *proxied* object, so passing it
+ // through to setPropertyValue here would produce an
+ // infinite loop.
+ //
+ // ☡ This has implications on objects with a proxied
+ // PropertyDescriptor in their prototype.
+ return setPropertyValue(O, P, newValue, O);
+ }
} else {
- // P can be safely defined on O.
- return Reflect.set(O, prop, newValue, Receiver);
+ return setPropertyValue(O, P, V, Receiver);
}
},
setPrototypeOf(O, V) {
return false;
} else {
// V is the property descriptor prototype.
- return Reflect.setPrototypeOf(O, V);
+ return setPrototype(O, V);
}
},
},
);
- return PropertyDescriptor;
+ return { PropertyDescriptor };
})();
-/**
- * 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.
- */
-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?.[Symbol.species] ?? constructor;
- return Object.preventExtensions(
- Object.create(
- species == null || !("prototype" in species)
- ? null
- : species.prototype,
- Object.fromEntries(
- function* () {
- for (const P of Reflect.ownKeys(O)) {
- const Desc = Object.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 */
- }
- }
- }(),
- ),
- ),
- );
- }
-};
+export const {
+ /**
+ * Defines own properties on the provided object using the
+ * descriptors on the enumerable own properties of the provided
+ * additional objects.
+ *
+ * ※ This differs from Object.defineProperties in that it can take
+ * multiple source objects.
+ */
+ defineOwnProperties,
+} = (() => {
+ const { defineProperties } = Object;
+ const { forEach: arrayForEach } = Array.prototype;
+ return {
+ defineOwnProperties: (O, ...sources) => {
+ call(
+ arrayForEach,
+ sources,
+ [(source) => defineProperties(O, source)],
+ );
+ return O;
+ },
+ };
+})();
-/** Returns whether the provided value is a constructor. */
-export const isConstructor = ($) => {
- if (!isObject($)) {
- // The provided value is not an object.
- return false;
- } else {
- // The provided value is an object.
- try {
- Reflect.construct(
- function () {},
- [],
- $,
- ); // will throw if $ is not a constructor
- return true;
- } catch {
- return false;
- }
- }
-};
+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.
+ */
+ defineProperty: defineOwnProperty,
-/** Returns whether the provided value is an object. */
-export const isObject = ($) => {
- return $ !== null &&
- (typeof $ == "function" || typeof $ == "object");
-};
+ /**
+ * 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 an alias for Object.freeze.
+ */
+ freeze,
-/**
- * Returns whether the provided object inherits from the prototype of
- * the provided function.
- */
-export const ordinaryHasInstance = Function.prototype.call.bind(
- Function.prototype[Symbol.hasInstance],
-);
+ /**
+ * Returns the property descriptor for the own property with the
+ * provided property key on the provided object, or null if none
+ * exists.
+ *
+ * ※ This is an alias for Object.getOwnPropertyDescriptor.
+ */
+ getOwnPropertyDescriptor,
-/**
- * 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 = method.call(O);
- if (!isObject(result)) {
- // Method returns a primitive.
- return result;
+ /**
+ * Returns the property descriptors for the own properties on the
+ * provided object.
+ *
+ * ※ This is an alias for Object.getOwnPropertyDescriptors.
+ */
+ getOwnPropertyDescriptors,
+
+ /**
+ * Returns an array of string‐valued own property keys on the
+ * provided object.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is an alias for Object.getOwnPropertyNames.
+ */
+ getOwnPropertyNames: getOwnPropertyStrings,
+
+ /**
+ * Returns an array of symbol‐valued own property keys on the
+ * provided object.
+ *
+ * ☡ This includes both enumerable and non·enumerable properties.
+ *
+ * ※ This is an alias for Object.getOwnPropertySymbols.
+ */
+ getOwnPropertySymbols,
+
+ /**
+ * Returns the prototype of the provided object.
+ *
+ * ※ This is an alias for Object.getPrototypeOf.
+ */
+ getPrototypeOf: getPrototype,
+
+ /**
+ * Returns whether the provided object has an own property with the
+ * provided property key.
+ *
+ * ※ This is an alias for Object.hasOwn.
+ */
+ hasOwn: hasOwnProperty,
+
+ /**
+ * Returns whether the provided object is extensible.
+ *
+ * ※ This is an alias for Object.isExtensible.
+ */
+ isExtensible,
+
+ /**
+ * Returns whether the provided object is frozen.
+ *
+ * ※ This is an alias for Object.isFrozen.
+ */
+ isFrozen,
+
+ /**
+ * Returns whether the provided object is sealed.
+ *
+ * ※ This is an alias for Object.isSealed.
+ */
+ isSealed,
+
+ /**
+ * 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.
+ */
+ entries: namedEntries,
+
+ /**
+ * Returns an array of the enumerable, string‐valued property keys on
+ * the provided object.
+ *
+ * ※ This is an alias for Object.keys.
+ */
+ keys: namedKeys,
+
+ /**
+ * Returns an array of property values for the enumerable,
+ * string‐valued property keys on the provided object.
+ *
+ * ※ This is an alias for Object.values.
+ */
+ values: namedValues,
+
+ /**
+ * Returns a new object with the provided prototype and property
+ * descriptors.
+ *
+ * ※ 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.
+ */
+ fromEntries: objectFromEntries,
+
+ /**
+ * Marks the provided object as non·extensible, and returns the
+ * object.
+ *
+ * ※ This is an alias for Object.preventExtensions.
+ */
+ preventExtensions,
+
+ /**
+ * 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.
+ */
+ seal,
+
+ /**
+ * Sets the values of the enumerable own properties of the provided
+ * additional objects on the provided object.
+ *
+ * ※ This is an alias for Object.assign.
+ */
+ assign: setPropertyValues,
+
+ /**
+ * Sets the prototype of the provided object to the provided value
+ * and returns the object.
+ *
+ * ※ This is an alias for Object.setPrototypeOf.
+ */
+ setPrototypeOf: setPrototype,
+} = Object;
+
+export const {
+ /**
+ * 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.
+ */
+ 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 throws if the first argument is not an object.
+ */
+ setPropertyValue,
+} = (() => {
+ const { deleteProperty, get, has, ownKeys, set } = Reflect;
+
+ return {
+ deleteOwnProperty: (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}`,
+ );
} else {
- // Method returns an object.
- continue;
+ return O;
}
- } else {
- // Method is not callable.
- continue;
- }
+ },
+ 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 (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}`,
+ );
+ } else {
+ return O;
+ }
+ },
+ };
+})();
+
+export const {
+ /**
+ * 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.
+ */
+ frozenCopy,
+} = (() => {
+ const {
+ iterator: iteratorSymbol,
+ species: speciesSymbol,
+ } = Symbol;
+ const {
+ next: generatorIteratorNext,
+ } = getPrototype(function* () {}.prototype);
+ const propertyDescriptorEntryIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: bind(generatorIteratorNext, this.generator(), []),
+ };
+ },
+ };
+ return {
+ 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?.[speciesSymbol] ?? constructor;
+ return preventExtensions(
+ objectCreate(
+ species == null || !("prototype" in species)
+ ? null
+ : species.prototype,
+ objectFromEntries(
+ objectCreate(
+ propertyDescriptorEntryIterablePrototype,
+ {
+ generator: {
+ value: function* () {
+ const ownPropertyKeys = getOwnPropertyKeys(O);
+ for (
+ let i = 0;
+ i < ownPropertyKeys.length;
+ ++i
+ ) {
+ const P = ownPropertyKeys[i];
+ 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 */
+ }
+ }
+ },
+ },
+ },
+ ),
+ ),
+ ),
+ );
+ }
+ },
+ };
+})();
+
+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;
}
- throw new TypeError("Piscēs: Unable to convert object to primitive");
};
/**
- * Returns the provided value converted to a primitive, or throws if
- * no such conversion is possible.
+ * Returns the provided value converted to an object.
+ *
+ * Existing objects are returned with no modification.
*
- * 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 function throws a TypeError if its argument is null or
+ * undefined.
*/
-export const toPrimitive = ($, preferredType) => {
- if (isObject($)) {
- // 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.
+export const { toObject } = (() => {
+ const makeObject = Object;
+ return {
+ toObject: ($) => {
+ if ($ == null) {
throw new TypeError(
- "Piscēs: Symbol.toPrimitive was neither nullish nor callable.",
+ `Piscēs: Cannot convert ${$} into an object.`,
);
} 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 exoticToPrim.call($, hint);
- }
+ return makeObject($);
}
- } else {
- // Use the ordinary primitive conversion function.
- ordinaryToPrimitive($, hint);
- }
- } else {
- // The provided value is already a primitive.
- return $;
- }
-};
+ },
+ };
+})();
/**
* Returns the property key (symbol or string) corresponding to the