]> Lady’s Gitweb - Pisces/blobdiff - value.js
De‐classify property descriptors; move to value.js
[Pisces] / value.js
index 782c857f934f6eaee9a107c940cfb22df2ea69ac..1039a3ca9617d7a76b8493360eaf9a5e8456baea 100644 (file)
--- a/value.js
+++ b/value.js
@@ -45,38 +45,507 @@ 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;
 
+/**
+ * Completes the provided property descriptor by setting missing values
+ * to their defaults.
+ *
+ * ※ This method modifies the provided object and returns undefined.
+ */
+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 {
+    // `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);
+
+/**
+ * Gets whether the provided value is a fully‐populated property
+ * descriptor.
+ */
+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 {
   /**
    * Returns the primitive value of the provided object per its
-   * `toString` and `valueOf` methods.
+   * `.toString` and `.valueOf` methods.
    *
-   * If the provided hint is "string", then `toString` takes
-   * precedence; otherwise, `valueOf` does.
+   * 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 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.
    *
    * 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
+   * `.[Symbol.toPrimitive]` method, this function will throw rather
    * than calling that method with a preferred type other than one of
    * the above.
    */
   toPrimitive,
 } = (() => {
   const { apply: call } = Reflect;
+  const getSymbolDescription = Object.getOwnPropertyDescriptor(
+    Symbol.prototype,
+    "description",
+  ).get;
 
   return {
     ordinaryToPrimitive: (O, hint) => {
@@ -104,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 (
@@ -123,7 +607,7 @@ export const {
           if (typeof exoticToPrim !== "function") {
             // The method is not callable.
             throw new TypeError(
-              "Piscēs: `[Symbol.toPrimitive]` was neither nullish nor callable.",
+              "Piscēs: `.[Symbol.toPrimitive]` was neither nullish nor callable.",
             );
           } else {
             // The method is callable.
@@ -141,14 +625,14 @@ export const {
   };
 })();
 
-/**
- * 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 the same value.
+   *
+   * ※ This differs from `===` in the cases of nan and zero.
+   */
+  sameValue,
+
   /**
    * Returns whether the provided values are either the same value or
    * both zero (either positive or negative).
@@ -156,9 +640,23 @@ export const {
    * ※ This differs from `===` in the case of nan.
    */
   sameValueZero,
+
+  /**
+   * Returns the result of converting the provided value to an index,
+   * or throws an error if it is out of range.
+   */
+  toIndex,
+
+  /**
+   * Returns the result of converting the provided value to a length.
+   */
+  toLength,
 } = (() => {
+  const { floor, max, min } = Math;
   const { isNaN: isNan } = Number;
+  const { is } = Object;
   return {
+    sameValue: (a, b) => is(a, b),
     sameValueZero: ($1, $2) => {
       const type1 = type($1);
       const type2 = type($2);
@@ -174,6 +672,29 @@ export const {
         return $1 === $2;
       }
     },
+    toIndex: ($) => {
+      const integer = floor($);
+      if (isNan(integer) || integer == 0) {
+        // The value is zero·like.
+        return 0;
+      } else {
+        // The value is not zero·like.
+        const clamped = toLength(integer);
+        if (clamped !== integer) {
+          // Clamping the value changes it.
+          throw new RangeError(`Piscēs: Index out of range: ${$}.`);
+        } else {
+          // The value is within appropriate bounds.
+          return integer;
+        }
+      }
+    },
+    toLength: ($) => {
+      const len = floor($);
+      return isNan(len) || len == 0
+        ? 0
+        : max(min(len, MAXIMUM_SAFE_INTEGRAL_NUMBER), 0);
+    },
   };
 })();
 
This page took 0.03217 seconds and 4 git commands to generate.