X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/6f1ff895670d04034ef09faea4779923a85097fb..7c3a2eda590637af9463e5a59ab798f802d274a9:/value.js diff --git a/value.js b/value.js new file mode 100644 index 0000000..77cb6b4 --- /dev/null +++ b/value.js @@ -0,0 +1,160 @@ +// ♓🌟 Piscēs ∷ value.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 . + +import { call } from "./function.js"; + +/** The null primitive. */ +export const NULL = null; + +/** The undefined primitive. */ +export const UNDEFINED = undefined; + +export const { + /** + * 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. + */ + ordinaryToPrimitive, + + /** + * Returns the provided value converted to a primitive, or throws if + * no such conversion is possible. + * + * 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. + */ + toPrimitive, +} = (() => { + const { toPrimitive: toPrimitiveSymbol } = Symbol; + + return { + ordinaryToPrimitive: (O, hint) => { + const methodNames = hint == "string" + ? ["toString", "valueOf"] + : ["valueOf", "toString"]; + for (let index = 0; index < methodNames.length; ++index) { + const method = O[methodNames[index]]; + if (typeof method === "function") { + // Method is callable. + const result = call(method, O, []); + if (type(result) !== "object") { + // Method returns a primitive. + return result; + } else { + // Method returns an object. + continue; + } + } else { + // Method is not callable. + continue; + } + } + throw new TypeError( + "Piscēs: Unable to convert object to primitive", + ); + }, + toPrimitive: ($, preferredType = "default") => { + const hint = `${preferredType}`; + if ( + "default" !== hint && "string" !== hint && + "number" !== hint + ) { + // An invalid preferred type was specified. + throw new TypeError( + `Piscēs: Invalid preferred type: ${preferredType}.`, + ); + } else if (type($) === "object") { + // The provided value is an object. + const exoticToPrim = $[toPrimitiveSymbol] ?? 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. + return call(exoticToPrim, $, [hint]); + } + } else { + // Use the ordinary primitive conversion function. + return ordinaryToPrimitive($, hint); + } + } else { + // The provided value is already a primitive. + return $; + } + }, + }; +})(); + +/** + * Returns whether the provided values are the same value. + * + * ※ This differs from `===` in the cases of nan and zero. + */ +export const sameValue = Object.is; + +export const { + /** + * Returns whether the provided values are either the same value or + * both zero (either positive or negative). + * + * ※ This differs from `===` in the case of nan. + */ + sameValueZero, +} = (() => { + const { isNaN: isNan } = Number; + return { + sameValueZero: ($1, $2) => { + const type1 = type($1); + const type2 = type($2); + if (type1 !== type2) { + // The provided values are not of the same type. + return false; + } else if (type1 === "number") { + // The provided values are numbers; check if they are nan and + // use strict equality otherwise. + return isNan($1) && isNan($2) || $1 === $2; + } else { + // The provided values are not numbers; use strict equality. + return $1 === $2; + } + }, + }; +})(); + +/** + * Returns a lowercase string identifying the type of the provided + * value. + * + * This differs from the value of the `typeof` operator only in the + * cases of objects and null. + */ +export const type = ($) => { + if ($ === null) { + // The provided value is null. + return "null"; + } else { + // The provided value is not null. + const type·of = typeof $; + return type·of === "function" ? "object" : type·of; + } +};