]> Lady’s Gitweb - Pisces/blobdiff - value.js
Split some object code into value and unit test
[Pisces] / value.js
diff --git a/value.js b/value.js
new file mode 100644 (file)
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 <https://mozilla.org/MPL/2.0/>.
+
+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;
+  }
+};
This page took 0.024768 seconds and 4 git commands to generate.