]> Lady’s Gitweb - Pisces/blobdiff - function.js
Define names and lengths for iterator functions
[Pisces] / function.js
index 5d59af1a6c56f99beb4a1fad81db62ada3b7394c..5f9c2b5610568c873b5512f7b690105d8892927f 100644 (file)
@@ -1,11 +1,14 @@
-// ♓🌟 Piscēs ∷ function.js
-// ====================================================================
-//
-// Copyright © 2022‐2023 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/>.
+// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
+// SPDX-License-Identifier: MPL-2.0
+/**
+ * ⁌ ♓🧩 Piscēs ∷ function.js
+ *
+ * Copyright © 2022–2023, 2025 Lady [@ Ladys 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 {
   ITERATOR,
@@ -20,78 +23,81 @@ import {
   defineOwnProperty,
   getOwnPropertyDescriptor,
   getPrototype,
+  hasOwnProperty,
   objectCreate,
   setPropertyValues,
   setPrototype,
 } from "./object.js";
 
+const PISCĒS = "♓🧩 Piscēs";
+
 export const {
   /**
    * Creates a bound function from the provided function using the
    * provided this value and arguments list.
    *
-   * ☡ As with `call` and `construct`, the arguments must be passed as
+   * ☡ As with `call´ and `construct´, the arguments must be passed as
    * an array.
    */
   bind,
 
   /**
-   * Returns a new function which calls the provided function with its
-   * first argument as the `this` value and the remaining arguments
-   * passed through.
+   * Returns a new arrow function function which wraps the provided
+   * function and passes its arguments thru.
    *
-   * The `length`, `name`, and prototype of the provided function will
+   * The `length´, `name´, and prototype of the provided function will
    * be preserved in the new one. A second argument may be used to
-   * override `length` and `name`.
+   * override `length´ and `name´.
    */
   createArrowFunction,
 
   /**
-   * Returns a new function which calls the provided function with its
-   * first argument as the `this` value and the remaining arguments
-   * passed through.
+   * Returns a new arrow function which wraps the provided function,
+   * using its first argument as the this value when calling the
+   * provided function and passing the remainder thru.
    *
-   * The `length`, `name`, and prototype of the provided function will
+   * The `length´, `name´, and prototype of the provided function will
    * be preserved in the new one. A second argument may be used to
-   * override `length` and `name`.
+   * override `length´ and `name´.
    *
-   * ※ This is effectively an alias for `Function::call.bind`.
+   * ※ This is effectively an alias for `Function::call.bind´.
    */
   createCallableFunction,
 
   /**
    * Returns a constructor which throws whenever it is called but has
-   * the same `.name` and `.prototype` as the provided value.
+   * the same `.length´, `.name´, and `.prototype´ as the provided
+   * value.
    *
-   * The `length`, `name`, `prototype`, and prototype of the provided
+   * The `length´, `name´, `prototype´, and prototype of the provided
    * function will be preserved in the new one. A second argument may
-   * be used to override `length`, `name`, and `prototype`.
+   * be used to override `length´, `name´, and `prototype´.
    */
   createIllegalConstructor,
 
   /**
-   * Returns a constructor which produces a new constructor which wraps
+   * Returns a constructor which produces a new constructor that wraps
    * the provided constructor, but returns a proxy of the result using
    * the provided handler.
    *
    * The resulting constructor inherits from, and has the same basic
-   * shape as, `Proxy`.
+   * shape as, `Proxy´.
    *
-   * If a base constructor is not provided, `Object` will be used.
+   * If a base constructor is not provided, `Object´ will be used.
    *
    * If a third argument is provided, it is used as the target for the
    * provided constructor when it is constructed. This can be used to
    * prevent leakage of the provided constructor to superclasses
-   * through `new.target`.
+   * through `new.target´.
    *
-   * The `length` of the provided function will be preserved in the new
-   * one. A fourth argument may be used to override `length` and
-   * `name`.
+   * The `length´ of the provided function will be preserved in the new
+   * one. A fourth argument may be used to override `length´ and
+   * `name´.
    *
-   * ※ `.prototype` will be present, but undefined, on the resulting
-   * constructor. This differs from the behaviour of `Proxy`, for which
-   * `.prototype` is not present at all. It is not presently possible
-   * to create a constructor with no `.prototype` property in
+   * ※ `.prototype´ will be present, but undefined, on the resulting
+   * constructor. This differs from the behaviour of `Proxy´, for which
+   * `.prototype´ is not present at all. It is not presently possible
+   * to create a constructor with no `.prototype´ property in
    * Ecmascript code.
    */
   createProxyConstructor,
@@ -123,28 +129,79 @@ export const {
         },
         next: callBind(
           arrayIteratorNext,
-          call(arrayIterator, this.args, []),
+          reflectApply(arrayIterator, this.args, []),
         ),
       };
     },
   };
+  const { get: wmGet, set: wmSet } = WeakMap.prototype;
+  const wsConstructor = WeakSet;
+  const { add: wsAdd, has: wsHas } = WeakSet.prototype;
+  const proxyConstructorValuesMap = new WeakMap();
+  const registerConstructedProxy = (constructor, proxy) => {
+    const values = (() => {
+      const existing = reflectApply(wmGet, proxyConstructorValuesMap, [
+        constructor,
+      ]);
+      if (existing) {
+        // There is an existing values set for this constructor.
+        //
+        // It is returned.
+        return existing;
+      } else {
+        // There is no existing values set for this constructor so one
+        // must be created.
+        const result = new wsConstructor();
+        reflectApply(wmSet, proxyConstructorValuesMap, [
+          constructor,
+          result,
+        ]);
+        return result;
+      }
+    })();
+    reflectApply(wsAdd, values, [proxy]);
+    return proxy;
+  };
   const applyBaseFunction = ($, base, lengthDelta = 0) => {
     if (base === UNDEFINED) {
       // No base function was provided to apply.
       return $;
     } else {
-      // A base function was provided; apply it.
-      const { length, name, prototype } = base;
+      // A base function was provided.
+      //
+      // Apply it.
+      const overrides = objectCreate(null);
+      overrides.length = +base.length + lengthDelta;
+      overrides.name = base.name;
       if (getPrototype($) === functionPrototype) {
+        // The provided function has the function prototype.
+        //
+        // Change it to match the base function.
         setPrototype($, getPrototype(base));
       } else {
+        // The provided function already does not have the function
+        // prototype, so its prototype is not changed.
         /* do nothing */
       }
-      return applyProperties($, {
-        length: +length + lengthDelta,
-        name,
-        prototype,
-      });
+      if (hasOwnProperty($, "prototype") && "prototype" in base) {
+        // The provided function has a `.prototype´ property, and one
+        // was provided in the base function as well.
+        try {
+          // If this does not throw, the provided function is a
+          // constructor. Its `.prototype´ property is set alongside
+          // the others.
+          reflectConstruct(function () {}, [], $);
+          overrides.prototype = base.prototype;
+        } catch {
+          // The provided function is not a constructor.
+          /* do nothing */
+        }
+      } else {
+        // The provided function does not have a `.prototype´ property,
+        // or the base function did not have one.
+        /* do nothing */
+      }
+      return applyProperties($, overrides);
     }
   };
   const applyProperties = ($, override) => {
@@ -152,21 +209,23 @@ export const {
       // No properties were provided to apply.
       return $;
     } else {
-      // Properties were provided; apply them.
-      const { length, name, prototype } = override;
+      // Properties were provided.
+      //
+      // Apply them.
+      const { length, name } = override;
       if (
-        prototype === UNDEFINED ||
-        !getOwnPropertyDescriptor($, "prototype")?.writable
+        !("prototype" in override)
+        || !getOwnPropertyDescriptor($, "prototype")?.writable
       ) {
-        // The provided function has no `.prototype`, its prototype is
+        // The provided function has no `.prototype´, its prototype is
         // not writable, or no prototype value was provided.
         //
         // Do not modify the prototype property of the provided
         // function.
         /* do nothing */
       } else {
-        // The provided function is a constructor and a prototype value
-        // was provided.
+        // The provided function has a writable `.prototype´ and a
+        // prototype value was provided.
         //
         // Change the prototype property of the provided function to
         // match.
@@ -176,7 +235,7 @@ export const {
           defineOwnDataProperty(
             objectCreate(null),
             "value",
-            prototype,
+            override.prototype,
           ),
         );
       }
@@ -251,49 +310,55 @@ export const {
       newTarget = UNDEFINED,
       propertyOverride = UNDEFINED,
     ) => {
-      const constructor = $ === UNDEFINED ? objectConstructor : $;
+      const constructor = $ === UNDEFINED
+        ? function ($) {
+          return new objectConstructor($);
+        }
+        : $;
       const target = newTarget === UNDEFINED ? constructor : newTarget;
       const len = toLength(constructor.length);
       if (!(type(handler) === "object")) {
         // The provided handler is not an object; this is an error.
         throw new TypeError(
-          `Piscēs: Proxy handler must be an object, but got: ${handler}.`,
+          `${PISCĒS}: Proxy handler must be an object, but got: ${handler}.`,
         );
       } else if (!isConstructor(constructor)) {
         // The provided constructor is not a constructor; this is an
         // error.
         throw new TypeError(
-          "Piscēs: Cannot create proxy constructor from nonconstructible value.",
+          `${PISCĒS}: Cannot create proxy constructor from nonconstructible value.`,
         );
       } else if (!isConstructor(target)) {
         // The provided new target is not a constructor; this is an
         // error.
         throw new TypeError(
-          "Piscēs: New target must be a constructor.",
+          `${PISCĒS}: New target must be a constructor.`,
         );
       } else {
         // The arguments are acceptable.
-        return applyProperties(
+        const C = applyProperties(
           defineOwnProperties(
             setPrototype(
-              function C(...$s) {
+              function (...$s) {
                 if (new.target === UNDEFINED) {
                   // The constructor was not called with new; this is
                   // an error.
                   throw new TypeError(
-                    `Piscēs: ${
+                    `${PISCĒS}: ${
                       C.name ?? "Proxy"
                     } must be called with new.`,
                   );
                 } else {
-                  // The constructor was called with new; return the
-                  // appropriate proxy.
+                  // The constructor was called with new.
+                  //
+                  // Return the appropriate proxy.
                   const O = reflectConstruct(
                     constructor,
                     $s,
                     target,
                   );
-                  return new proxyConstructor(O, handler);
+                  const proxy = new proxyConstructor(O, handler);
+                  return registerConstructedProxy(C, proxy);
                 }
               },
               proxyConstructor,
@@ -315,37 +380,72 @@ export const {
                 value: UNDEFINED,
                 writable: false,
               }),
-              revocable: setPropertyValues(objectCreate(null), {
-                configurable: true,
-                enumerable: false,
-                value: defineOwnProperties(
-                  (...$s) => {
-                    const O = reflectConstruct(
-                      constructor,
-                      $s,
-                      target,
-                    );
-                    return revocable(O, handler);
-                  },
-                  {
-                    length: defineOwnDataProperty(
-                      objectCreate(null),
-                      "value",
-                      len,
-                    ),
-                    name: defineOwnDataProperty(
-                      objectCreate(null),
-                      "value",
-                      "revocable",
-                    ),
-                  },
-                ),
-                writable: true,
-              }),
             },
           ),
           propertyOverride,
         );
+        const { name } = C;
+        return defineOwnProperties(C, {
+          revocable: setPropertyValues(objectCreate(null), {
+            configurable: true,
+            enumerable: false,
+            value: defineOwnProperties(
+              (...$s) => {
+                const O = reflectConstruct(
+                  constructor,
+                  $s,
+                  target,
+                );
+                const proxy = revocable(O, handler);
+                return registerConstructedProxy(C, proxy);
+              },
+              {
+                length: defineOwnDataProperty(
+                  objectCreate(null),
+                  "value",
+                  len,
+                ),
+                name: defineOwnDataProperty(
+                  objectCreate(null),
+                  "value",
+                  "revocable",
+                ),
+              },
+            ),
+            writable: true,
+          }),
+          [`is${name}`]: setPropertyValues(objectCreate(null), {
+            configurable: true,
+            enumerable: false,
+            value: defineOwnProperty(
+              ($) => {
+                const values = reflectApply(
+                  wmGet,
+                  proxyConstructorValuesMap,
+                  [C],
+                );
+                if (values === UNDEFINED) {
+                  // No values have been registered for the current
+                  // constructor.
+                  return false;
+                } else {
+                  // One or more values has been registered for the
+                  // current constructor.
+                  //
+                  // Return whether the provided argument is one.
+                  return reflectApply(wsHas, values, [$]);
+                }
+              },
+              "name",
+              defineOwnDataProperty(
+                objectCreate(null),
+                "value",
+                `is${name}`,
+              ),
+            ),
+            writable: true,
+          }),
+        });
       }
     },
   };
@@ -355,7 +455,7 @@ export const {
  * Calls the provided function with the provided this value and
  * arguments list.
  *
- * ☡ This is effectively an alias for `Reflect.apply`—the arguments
+ * ☡ This is effectively an alias for `Reflect.apply´—the arguments
  * must be passed as an arraylike.
  */
 export const call = createArrowFunction(Reflect.apply, {
@@ -363,7 +463,7 @@ export const call = createArrowFunction(Reflect.apply, {
 });
 
 /**
- * Returns whether calling the provided function with no `this` value
+ * Returns whether calling the provided function with no this value
  * or arguments completes normally; that is, does not throw an error.
  *
  * ☡ This function will throw an error if the provided argument is not
@@ -373,16 +473,20 @@ export const completesNormally = ($) => {
   if (!isCallable($)) {
     // The provided value is not callable; this is an error.
     throw new TypeError(
-      `Piscēs: Cannot determine completion of noncallable value: ${$}`,
+      `${PISCĒS}: Cannot determine completion of noncallable value: ${$}.`,
     );
   } else {
     // The provided value is callable.
     try {
-      // Attempt to call the function and return true if this succeeds.
+      // This will throw if calling the function throws.
+      //
+      // Otherwise, return true.
       $();
       return true;
     } catch {
-      // Calling the function did not succeed; return false.
+      // Calling the function did not succeed.
+      //
+      // Return false.
       return false;
     }
   }
@@ -392,7 +496,7 @@ export const completesNormally = ($) => {
  * Constructs the provided function with the provided arguments list
  * and new target.
  *
- * ☡ This is effectively an alias for `Reflect.construct`—the
+ * ☡ This is effectively an alias for `Reflect.construct´—the
  * arguments must be passed as an arraylike.
  */
 export const construct = createArrowFunction(Reflect.construct);
@@ -401,8 +505,8 @@ export const construct = createArrowFunction(Reflect.construct);
  * Returns the provided value.
  *
  * ※ This function can be called as a constructor. When used in an
- * `extends` clause and called via `super`, it will set the value of
- * `this` to the provided value, enabling it to be extended with
+ * `extends´ clause and called via `super´, it will set the value of
+ * `this´ to the provided value, enabling it to be extended with
  * private class features.
  */
 export const identity = function ($) {
@@ -415,9 +519,10 @@ export const isCallable = ($) => typeof $ === "function";
 /** Returns whether the provided value is a constructor. */
 export const isConstructor = ($) =>
   completesNormally(() =>
-    // Try constructing a new object with the provided value as its
-    // `new.target`. This will throw if the provided value is not a
-    // constructor.
+    // Try constructing a new object using the provided value as
+    // `new.target´.
+    //
+    // ☡ This will throw if the provided value is not a constructor.
     construct(function () {}, [], $)
   );
 
This page took 0.236034 seconds and 4 git commands to generate.