]> Lady’s Gitweb - Pisces/blobdiff - function.js
Add methods for own entries and values to object.js
[Pisces] / function.js
index 5da9f6fc4891a356b7ce5482d16d744477ee36fe..2f242fe206eb466deb20a768f0098c00df8f3024 100644 (file)
@@ -7,7 +7,23 @@
 // 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 } from "./value.js";
+import {
+  ITERATOR,
+  toFunctionName,
+  toLength,
+  type,
+  UNDEFINED,
+} from "./value.js";
+import {
+  defineOwnDataProperty,
+  defineOwnProperties,
+  defineOwnProperty,
+  getOwnPropertyDescriptor,
+  getPrototype,
+  objectCreate,
+  setPropertyValues,
+  setPrototype,
+} from "./object.js";
 
 export const {
   /**
@@ -24,35 +40,77 @@ export const {
    * first argument as the `this` value and the remaining arguments
    * passed through.
    *
+   * 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`.
+   */
+  createArrowFunction,
+
+  /**
+   * Returns a new function which calls the provided function with its
+   * first argument as the `this` value and the remaining arguments
+   * passed through.
+   *
+   * 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`.
+   *
    * ※ This is effectively an alias for `Function::call.bind`.
    */
-  makeCallable,
+  createCallableFunction,
 
   /**
    * Returns a constructor which throws whenever it is called but has
    * the same `.name` and `.prototype` as the provided value.
    *
-   * If a second argument is provided, the returned constructor will
-   * use that as its prototype; otherwise, it will use the prototype of
-   * the provided value.
+   * 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`.
    */
-  makeIllegalConstructor,
+  createIllegalConstructor,
+
+  /**
+   * Returns a constructor which produces a new constructor which 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`.
+   *
+   * 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`.
+   *
+   * 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
+   * Ecmascript code.
+   */
+  createProxyConstructor,
 } = (() => {
-  // ☡ Because these functions are used to initialize module constants,
-  // they can’t depend on imports from elsewhere.
+  const { prototype: functionPrototype } = Function;
   const {
     bind: functionBind,
     call: functionCall,
-  } = Function.prototype;
-  const callBind = Reflect.apply(functionBind, functionCall, [
+  } = functionPrototype;
+  const objectConstructor = Object;
+  const proxyConstructor = Proxy;
+  const {
+    apply: reflectApply,
+    construct: reflectConstruct,
+  } = Reflect;
+  const callBind = reflectApply(functionBind, functionCall, [
     functionBind,
   ]);
-  const {
-    create: objectCreate,
-    defineProperties: defineOwnProperties,
-    getPrototypeOf: getPrototype,
-    setPrototypeOf: setPrototype,
-  } = Object;
+  const { revocable } = Proxy;
   const { [ITERATOR]: arrayIterator } = Array.prototype;
   const {
     next: arrayIteratorNext,
@@ -60,6 +118,9 @@ export const {
   const argumentIterablePrototype = {
     [ITERATOR]() {
       return {
+        [ITERATOR]() {
+          return this;
+        },
         next: callBind(
           arrayIteratorNext,
           call(arrayIterator, this.args, []),
@@ -67,88 +128,301 @@ export const {
       };
     },
   };
+  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) {
+        return existing;
+      } else {
+        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;
+      if (getPrototype($) === functionPrototype) {
+        setPrototype($, getPrototype(base));
+      } else {
+        /* do nothing */
+      }
+      return applyProperties($, {
+        length: +length + lengthDelta,
+        name,
+        prototype,
+      });
+    }
+  };
+  const applyProperties = ($, override) => {
+    if (override === UNDEFINED) {
+      // No properties were provided to apply.
+      return $;
+    } else {
+      // Properties were provided; apply them.
+      const { length, name, prototype } = override;
+      if (
+        prototype === UNDEFINED ||
+        !getOwnPropertyDescriptor($, "prototype")?.writable
+      ) {
+        // 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.
+        //
+        // Change the prototype property of the provided function to
+        // match.
+        defineOwnProperty(
+          $,
+          "prototype",
+          defineOwnDataProperty(
+            objectCreate(null),
+            "value",
+            prototype,
+          ),
+        );
+      }
+      return defineOwnProperties($, {
+        length: defineOwnDataProperty(
+          objectCreate(null),
+          "value",
+          toLength(length === UNDEFINED ? $.length : length),
+        ),
+        name: defineOwnDataProperty(
+          objectCreate(null),
+          "value",
+          toFunctionName(name === UNDEFINED ? $.name ?? "" : name),
+        ),
+      });
+    }
+  };
+
   return {
     bind: ($, boundThis, boundArgs) =>
       callBind(
         $,
         boundThis,
-        ...objectCreate(
-          argumentIterablePrototype,
-          { args: { value: boundArgs } },
+        ...defineOwnDataProperty(
+          objectCreate(argumentIterablePrototype),
+          "args",
+          boundArgs,
         ),
       ),
-    makeCallable: ($, name = undefined) =>
-      defineOwnProperties(callBind(functionCall, $), {
-        length: { value: $.length + 1 },
-        name: { value: name ?? $.name ?? "" },
-      }),
-    makeIllegalConstructor: ($, proto = undefined) => {
-      const constructor = function () {
-        throw new TypeError("Illegal constructor");
-      };
-      if ($ == null && proto === undefined) {
-        // The provided argument is nullish and no explicit prototype
-        // was provided.
-        //
-        // Do not modify the prototype of the generated constructor.
-        /* do nothing */
+    createArrowFunction: ($, propertyOverride = UNDEFINED) =>
+      applyProperties(
+        applyBaseFunction(
+          (...$s) => reflectApply($, UNDEFINED, $s),
+          $,
+        ),
+        propertyOverride,
+      ),
+    createCallableFunction: ($, propertyOverride = UNDEFINED) =>
+      applyProperties(
+        applyBaseFunction(
+          (...$s) => {
+            const iterator = defineOwnDataProperty(
+              objectCreate(argumentIterablePrototype),
+              "args",
+              $s,
+            )[ITERATOR]();
+            const { value: thisValue } = iterator.next();
+            return reflectApply($, thisValue, [...iterator]);
+          },
+          $,
+          1,
+        ),
+        propertyOverride,
+      ),
+    createIllegalConstructor: ($, propertyOverride = UNDEFINED) =>
+      defineOwnProperty(
+        applyProperties(
+          applyBaseFunction(
+            function () {
+              throw new TypeError("Illegal constructor");
+            },
+            $,
+          ),
+          propertyOverride,
+        ),
+        "prototype",
+        { writable: false },
+      ),
+    createProxyConstructor: (
+      handler,
+      $,
+      newTarget = UNDEFINED,
+      propertyOverride = UNDEFINED,
+    ) => {
+      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}.`,
+        );
+      } 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.",
+        );
+      } 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.",
+        );
       } else {
-        // The provided argument is not nullish or an explicit
-        // prototype was provided.
-        //
-        // Set the prototype of the generated constructor to match.
-        setPrototype(
-          constructor,
-          proto === undefined ? getPrototype($) : proto,
+        // The arguments are acceptable.
+        const C = applyProperties(
+          defineOwnProperties(
+            setPrototype(
+              function (...$s) {
+                if (new.target === UNDEFINED) {
+                  // The constructor was not called with new; this is an
+                  // error.
+                  throw new TypeError(
+                    `Piscēs: ${
+                      C.name ?? "Proxy"
+                    } must be called with new.`,
+                  );
+                } else {
+                  // The constructor was called with new; return the
+                  // appropriate proxy.
+                  const O = reflectConstruct(
+                    constructor,
+                    $s,
+                    target,
+                  );
+                  const proxy = new proxyConstructor(O, handler);
+                  return registerConstructedProxy(C, proxy);
+                }
+              },
+              proxyConstructor,
+            ),
+            {
+              length: defineOwnDataProperty(
+                objectCreate(null),
+                "value",
+                len,
+              ),
+              name: defineOwnDataProperty(
+                objectCreate(null),
+                "value",
+                `${toFunctionName(constructor.name ?? "")}Proxy`,
+              ),
+              prototype: setPropertyValues(objectCreate(null), {
+                configurable: false,
+                enumerable: false,
+                value: UNDEFINED,
+                writable: false,
+              }),
+            },
+          ),
+          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,
+          }),
+        });
       }
-      return defineOwnProperties(constructor, {
-        name: { value: $?.name ?? "" },
-        prototype: { value: $?.prototype ?? {}, writable: false },
-      });
     },
   };
 })();
 
-export const {
-  /**
-   * Calls the provided function with the provided this value and
-   * arguments list.
-   *
-   * ☡ This is effectively an alias for `Reflect.apply`—the arguments
-   * must be passed as an arraylike.
-   */
-  call,
-
-  /**
-   * Constructs the provided function with the provided arguments list
-   * and new target.
-   *
-   * ☡ This is effectively an alias for `Reflect.construct`—the
-   * arguments must be passed as an arraylike.
-   */
-  construct,
-
-  /** Returns whether the provided value is a constructor. */
-  isConstructor,
-} = (() => {
-  const { apply, construct } = Reflect;
-  return {
-    call: (target, thisArgument, argumentsList) =>
-      apply(target, thisArgument, argumentsList),
-    construct: (target, argumentsList, ...args) =>
-      args.length > 0
-        ? construct(target, argumentsList, args[0])
-        : construct(target, argumentsList),
-    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.
-        construct(function () {}, [], $)
-      ),
-  };
-})();
+/**
+ * Calls the provided function with the provided this value and
+ * arguments list.
+ *
+ * ☡ This is effectively an alias for `Reflect.apply`—the arguments
+ * must be passed as an arraylike.
+ */
+export const call = createArrowFunction(Reflect.apply, {
+  name: "call",
+});
 
 /**
  * Returns whether calling the provided function with no `this` value
@@ -176,6 +450,15 @@ export const completesNormally = ($) => {
   }
 };
 
+/**
+ * Constructs the provided function with the provided arguments list
+ * and new target.
+ *
+ * ☡ This is effectively an alias for `Reflect.construct`—the
+ * arguments must be passed as an arraylike.
+ */
+export const construct = createArrowFunction(Reflect.construct);
+
 /**
  * Returns the provided value.
  *
@@ -191,11 +474,27 @@ export const identity = function ($) {
 /** Returns whether the provided value is callable. */
 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.
+    construct(function () {}, [], $)
+  );
+
+/**
+ * Calls the provided callback with the provided argument if the
+ * provided argument is not nullish; otherwise, returns the provided
+ * argument unmodified.
+ */
+export const maybe = ($, callback) => $ == null ? $ : callback($);
+
 /**
  * Returns whether the provided object inherits from the prototype of
  * the provided function.
  */
-export const ordinaryHasInstance = makeCallable(
+export const ordinaryHasInstance = createCallableFunction(
   Function.prototype[Symbol.hasInstance],
-  "ordinaryHasInstance",
+  { name: "ordinaryHasInstance" },
 );
This page took 0.150688 seconds and 4 git commands to generate.