X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/8c273de93ce5adb58bb90c3fe676730a451b3c89..50ab30fc3a257e46da6b8bdc0dad054f729e16e1:/value.js diff --git a/value.js b/value.js index 5e9f052..1039a3c 100644 --- a/value.js +++ b/value.js @@ -45,40 +45,470 @@ export const { unscopables: UNSCOPABLES, } = Symbol; +export const { + /** + * ln(10). + * + * ※ This is an alias for `Math.LN10`. + */ + LN10, + + /** + * ln(2). + * + * ※ This is an alias for `Math.LN2`. + */ + LN2, + + /** + * log10(ℇ). + * + * ※ This is an alias for `Math.LOG10E`. + */ + LOG10E: LOG10ℇ, + + /** + * log2(ℇ). + * + * ※ This is an alias for `Math.LOG2E`. + */ + LOG2E: LOG2ℇ, + + /** + * sqrt(½). + * + * ※ This is an alias for `Math.SQRT1_2`. + */ + SQRT1_2: RECIPROCAL_SQRT2, + + /** + * sqrt(2). + * + * ※ This is an alias for `Math.SQRT2`. + */ + SQRT2, + + /** + * The mathematical constant π. + * + * ※ This is an alias for `Math.PI`. + */ + PI: Π, + + /** + * The Euler number. + * + * ※ This is an alias for `Math.E`. + */ + E: ℇ, +} = Math; + +export const { + /** + * The largest number value less than infinity. + * + * ※ This is an alias for `Number.MAX_VALUE`. + */ + MAX_VALUE: MAXIMUM_NUMBER, + + /** + * 2**53 - 1. + * + * ※ This is an alias for `Number.MAX_SAFE_INTEGER`. + */ + MAX_SAFE_INTEGER: MAXIMUM_SAFE_INTEGRAL_NUMBER, + + /** + * The smallest number value greater than negative infinity. + * + * ※ This is an alias for `Number.MIN_VALUE`. + */ + MIN_VALUE: MINIMUM_NUMBER, + + /** + * -(2**53 - 1). + * + * ※ This is an alias for `Number.MIN_SAFE_INTEGER`. + */ + MIN_SAFE_INTEGER: MINIMUM_SAFE_INTEGRAL_NUMBER, + + /** + * Negative infinity. + * + * ※ This is an alias for `Number.NEGATIVE_INFINITY`. + */ + NEGATIVE_INFINITY, + + /** + * Nan. + * + * ※ This is an alias for `Number.NaN`. + */ + NaN: NAN, + + /** + * Positive infinity. + * + * ※ This is an alias for `Number.POSITIVE_INFINITY`. + */ + POSITIVE_INFINITY, + + /** + * The difference between 1 and the smallest number greater than 1. + * + * ※ This is an alias for `Number.EPSILON`. + */ + EPSILON: Ε, +} = Number; + +/** Negative zero. */ +export const NEGATIVE_ZERO = -0; + /** The null primitive. */ export const NULL = null; +/** Positive zero. */ +export const POSITIVE_ZERO = 0; + /** The undefined primitive. */ export const UNDEFINED = undefined; /** - * Returns −0 if the provided argument is "-0"; returns a number - * representing the index if the provided argument is a canonical - * numeric index string; otherwise, returns undefined. + * Completes the provided property descriptor by setting missing values + * to their defaults. * - * There is no clamping of the numeric index, but note that numbers - * above 2^53 − 1 are not safe nor valid integer indices. + * ※ This method modifies the provided object and returns undefined. */ -export const canonicalNumericIndexString = ($) => { - if (typeof $ !== "string") { - return undefined; - } else if ($ === "-0") { - return -0; +export const completePropertyDescriptor = (Desc) => { + if (Desc === UNDEFINED) { + throw new TypeError( + "Piscēs: Cannot complete undefined property descriptor.", + ); + } else if (!("get" in Desc || "set" in Desc)) { + // This is a generic or data descriptor. + if (!("value" in Desc)) { + // `value` is not defined on this. + Desc.value = UNDEFINED; + } else { + // `value` is already defined on this. + /* do nothing */ + } + if (!("writable" in Desc)) { + // `writable` is not defined on this. + Desc.writable = false; + } else { + // `writable` is already defined on this. + /* do nothing */ + } + } else { + // This is not a generic or data descriptor. + if (!("get" in Desc)) { + // `get` is not defined on this. + Desc.get = UNDEFINED; + } else { + // `get` is already defined on this. + /* do nothing */ + } + if (!("set" in Desc)) { + // `set` is not defined on this. + Desc.set = UNDEFINED; + } else { + // `set` is already defined on this. + /* do nothing */ + } + } + if (!("enumerable" in Desc)) { + // `enumerable` is not defined on this. + Desc.enumerable = false; } else { - const n = +$; - return $ === `${n}` ? n : undefined; + // `enumerable` is already defined on this. + /* do nothing */ + } + if (!("configurable" in Desc)) { + // `configurable` is not defined on this. + Desc.configurable = false; + } else { + // `configurable` is already defined on this. + /* do nothing */ } }; +/** Gets whether the provided value is an accessor descrtiptor. */ +export const isAccessorDescriptor = (Desc) => + Desc !== UNDEFINED && ("get" in Desc || "set" in Desc); + +/** Gets whether the provided value is a data descrtiptor. */ +export const isDataDescriptor = (Desc) => + Desc !== UNDEFINED && ("value" in Desc || "writable" in Desc); + /** - * 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. + * Gets whether the provided value is a fully‐populated property + * descriptor. */ -export const lengthOfArraylike = ({ length }) => toLength(length); +export const isFullyPopulatedDescriptor = (Desc) => + Desc !== UNDEFINED && + ("value" in Desc && "writable" in Desc || + "get" in Desc && "set" in Desc) && + "enumerable" in Desc && "configurable" in Desc; + +/** + * Gets whether the provided value is a generic (not accessor or data) + * descrtiptor. + */ +export const isGenericDescriptor = (Desc) => + Desc !== UNDEFINED && + !("get" in Desc || "set" in Desc || "value" in Desc || + "writable" in Desc); + +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 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. + */ + toPropertyDescriptor, +} = (() => { + const { + assign: setPropertyValues, + create: objectCreate, + defineProperty: defineOwnProperty, + } = Object; + const { + apply: call, + defineProperty: reflectDefineProperty, + setPrototypeOf: reflectSetPrototypeOf, + } = Reflect; + 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") { + throw new TypeError( + "Piscēs: Getters must be callable.", + ); + } else { + return V; + } + case "set": + if (V !== undefined && typeof V !== "function") { + throw new TypeError( + "Piscēs: Setters must be callable.", + ); + } else { + return V; + } + default: + return V; + } + }; + const propertyDescriptorProxyHandler = Object.freeze( + Object.assign( + Object.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 = setPropertyValues(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. + 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, [$]), + toPropertyDescriptor: (Obj) => { + if (type(Obj) !== "object") { + // The provided value is not an object. + throw new TypeError( + `Piscēs: Cannot convert primitive to property descriptor: ${O}.`, + ); + } else { + // The provided value is an object. + const desc = objectCreate(null); + if ("enumerable" in Obj) { + // An enumerable property is specified. + defineOwnProperty(desc, "enumerable", { + configurable: true, + enumerable: true, + value: !!Obj.enumerable, + writable: true, + }); + } else { + // An enumerable property is not specified. + /* do nothing */ + } + if ("configurable" in Obj) { + // A configurable property is specified. + defineOwnProperty(desc, "configurable", { + configurable: true, + enumerable: true, + value: !!Obj.configurable, + writable: true, + }); + } else { + // A configurable property is not specified. + /* do nothing */ + } + if ("value" in Obj) { + // A value property is specified. + defineOwnProperty(desc, "value", { + configurable: true, + enumerable: true, + value: Obj.value, + writable: true, + }); + } else { + // A value property is not specified. + /* do nothing */ + } + if ("writable" in Obj) { + // A writable property is specified. + defineOwnProperty(desc, "writable", { + configurable: true, + enumerable: true, + value: !!Obj.writable, + writable: true, + }); + } else { + // A writable property is not specified. + /* do nothing */ + } + if ("get" in Obj) { + // A get property is specified. + const getter = Obj.get; + if (getter !== UNDEFINED && typeof getter !== "function") { + // The getter is not callable. + throw new TypeError("Piscēs: Getters must be callable."); + } else { + // The getter is callable. + defineOwnProperty(desc, "get", { + configurable: true, + enumerable: true, + value: getter, + writable: true, + }); + } + } else { + // A get property is not specified. + /* do nothing */ + } + if ("set" in Obj) { + // A set property is specified. + const setter = Obj.set; + if (setter !== UNDEFINED && typeof setter !== "function") { + // The setter is not callable. + throw new TypeError("Piscēs: Setters must be callable."); + } else { + // The setter is callable. + defineOwnProperty(desc, "set", { + configurable: true, + enumerable: true, + value: setter, + writable: true, + }); + } + } else { + // A set property is not specified. + /* do nothing */ + } + if ( + ("get" in desc || "set" in desc) && + ("value" in desc || "writable" in desc) + ) { + // Both accessor and data attributes have been defined. + throw new TypeError( + "Piscēs: Property descriptors cannot specify both accessor and data attributes.", + ); + } else { + // The property descriptor is valid. + const record = new proxyConstructor( + desc, + propertyDescriptorProxyHandler, + ); + call(weakSetAdd, propertyDescriptorRecords, [record]); + return record; + } + } + }, + }; +})(); export const { /** @@ -93,6 +523,12 @@ export const { */ ordinaryToPrimitive, + /** + * Returns a string function name generated from the provided value + * and optional prefix. + */ + toFunctionName, + /** * Returns the provided value converted to a primitive, or throws if * no such conversion is possible. @@ -106,6 +542,10 @@ export const { toPrimitive, } = (() => { const { apply: call } = Reflect; + const getSymbolDescription = Object.getOwnPropertyDescriptor( + Symbol.prototype, + "description", + ).get; return { ordinaryToPrimitive: (O, hint) => { @@ -133,6 +573,21 @@ export const { "Piscēs: Unable to convert object to primitive", ); }, + toFunctionName: ($, prefix = undefined) => { + const key = toPrimitive($, "string"); + const name = (() => { + if (typeof key === "symbol") { + // The provided value is a symbol; format its description. + const description = call(getSymbolDescription, key, []); + return description === undefined ? "" : `[${description}]`; + } else { + // The provided value not a symbol; convert it to a string + // property key. + return `${key}`; + } + })(); + return prefix !== undefined ? `${prefix} ${name}` : name; + }, toPrimitive: ($, preferredType = "default") => { const hint = `${preferredType}`; if ( @@ -171,9 +626,6 @@ export const { })(); export const { - /** Returns whether the provided value is an integer index string. */ - isIntegerIndexString, - /** * Returns whether the provided values are the same value. * @@ -201,27 +653,9 @@ export const { toLength, } = (() => { const { floor, max, min } = Math; - const { - MAX_SAFE_INTEGER: MAXIMUM_SAFE_INTEGRAL_NUMBER, - isInteger: isIntegralNumber, - isNaN: isNan, - } = Number; + const { isNaN: isNan } = Number; const { is } = Object; return { - isIntegerIndexString: ($) => { - const value = canonicalNumericIndexString($); - if (value !== undefined && isIntegralNumber(value)) { - // The provided value is an integral canonical numeric index - // string. - return sameValue(value, 0) || - value > 0 && value <= MAXIMUM_SAFE_INTEGRAL_NUMBER && - value === toLength(value); - } else { - // The provided value is not an integral canonical numeric - // index string. - return false; - } - }, sameValue: (a, b) => is(a, b), sameValueZero: ($1, $2) => { const type1 = type($1);