]> Lady’s Gitweb - Pisces/blob - function.js
cc81fb661534a96e53f5b67e420d516d2498ce8a
[Pisces] / function.js
1 // ♓🌟 Piscēs ∷ function.js
2 // ====================================================================
3 //
4 // Copyright © 2022‐2023 Lady [@ Lady’s Computer].
5 //
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
9
10 import { ITERATOR } from "./value.js";
11
12 export const {
13 /**
14 * Creates a bound function from the provided function using the
15 * provided this value and arguments list.
16 *
17 * ☡ As with `call` and `construct`, the arguments must be passed as
18 * an array.
19 */
20 bind,
21
22 /**
23 * Returns a new function which calls the provided function with its
24 * first argument as the `this` value and the remaining arguments
25 * passed through.
26 *
27 * ※ This is effectively an alias for `Function::call.bind`.
28 */
29 createCallableFunction,
30
31 /**
32 * Returns a constructor which throws whenever it is called but has
33 * the same `.name` and `.prototype` as the provided value.
34 *
35 * If a second argument is provided, the returned constructor will
36 * use that as its prototype; otherwise, it will use the prototype of
37 * the provided value.
38 */
39 createIllegalConstructor,
40 } = (() => {
41 // ☡ Because these functions are used to initialize module constants,
42 // they can’t depend on imports from elsewhere.
43 const {
44 bind: functionBind,
45 call: functionCall,
46 } = Function.prototype;
47 const callBind = Reflect.apply(functionBind, functionCall, [
48 functionBind,
49 ]);
50 const {
51 create: objectCreate,
52 defineProperties: defineOwnProperties,
53 getPrototypeOf: getPrototype,
54 setPrototypeOf: setPrototype,
55 } = Object;
56 const { [ITERATOR]: arrayIterator } = Array.prototype;
57 const {
58 next: arrayIteratorNext,
59 } = getPrototype([][ITERATOR]());
60 const argumentIterablePrototype = {
61 [ITERATOR]() {
62 return {
63 next: callBind(
64 arrayIteratorNext,
65 call(arrayIterator, this.args, []),
66 ),
67 };
68 },
69 };
70 return {
71 bind: ($, boundThis, boundArgs) =>
72 callBind(
73 $,
74 boundThis,
75 ...objectCreate(
76 argumentIterablePrototype,
77 { args: { value: boundArgs } },
78 ),
79 ),
80 createCallableFunction: ($, name = undefined) =>
81 defineOwnProperties(callBind(functionCall, $), {
82 length: { value: $.length + 1 },
83 name: { value: name ?? $.name ?? "" },
84 }),
85 createIllegalConstructor: ($, proto = undefined) => {
86 const constructor = function () {
87 throw new TypeError("Illegal constructor");
88 };
89 if ($ == null && proto === undefined) {
90 // The provided argument is nullish and no explicit prototype
91 // was provided.
92 //
93 // Do not modify the prototype of the generated constructor.
94 /* do nothing */
95 } else {
96 // The provided argument is not nullish or an explicit
97 // prototype was provided.
98 //
99 // Set the prototype of the generated constructor to match.
100 setPrototype(
101 constructor,
102 proto === undefined ? getPrototype($) : proto,
103 );
104 }
105 return defineOwnProperties(constructor, {
106 name: { value: $?.name ?? "" },
107 prototype: { value: $?.prototype ?? {}, writable: false },
108 });
109 },
110 };
111 })();
112
113 export const {
114 /**
115 * Calls the provided function with the provided this value and
116 * arguments list.
117 *
118 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
119 * must be passed as an arraylike.
120 */
121 call,
122
123 /**
124 * Constructs the provided function with the provided arguments list
125 * and new target.
126 *
127 * ☡ This is effectively an alias for `Reflect.construct`—the
128 * arguments must be passed as an arraylike.
129 */
130 construct,
131
132 /** Returns whether the provided value is a constructor. */
133 isConstructor,
134 } = (() => {
135 const { apply, construct } = Reflect;
136 return {
137 call: (target, thisArgument, argumentsList) =>
138 apply(target, thisArgument, argumentsList),
139 construct: (target, argumentsList, ...args) =>
140 args.length > 0
141 ? construct(target, argumentsList, args[0])
142 : construct(target, argumentsList),
143 isConstructor: ($) =>
144 completesNormally(() =>
145 // Try constructing a new object with the provided value as its
146 // `new.target`. This will throw if the provided value is not a
147 // constructor.
148 construct(function () {}, [], $)
149 ),
150 };
151 })();
152
153 /**
154 * Returns whether calling the provided function with no `this` value
155 * or arguments completes normally; that is, does not throw an error.
156 *
157 * ☡ This function will throw an error if the provided argument is not
158 * callable.
159 */
160 export const completesNormally = ($) => {
161 if (!isCallable($)) {
162 // The provided value is not callable; this is an error.
163 throw new TypeError(
164 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
165 );
166 } else {
167 // The provided value is callable.
168 try {
169 // Attempt to call the function and return true if this succeeds.
170 $();
171 return true;
172 } catch {
173 // Calling the function did not succeed; return false.
174 return false;
175 }
176 }
177 };
178
179 /**
180 * Returns the provided value.
181 *
182 * ※ This function can be called as a constructor. When used in an
183 * `extends` clause and called via `super`, it will set the value of
184 * `this` to the provided value, enabling it to be extended with
185 * private class features.
186 */
187 export const identity = function ($) {
188 return $;
189 };
190
191 /** Returns whether the provided value is callable. */
192 export const isCallable = ($) => typeof $ === "function";
193
194 /**
195 * Returns whether the provided object inherits from the prototype of
196 * the provided function.
197 */
198 export const ordinaryHasInstance = createCallableFunction(
199 Function.prototype[Symbol.hasInstance],
200 "ordinaryHasInstance",
201 );
This page took 0.106371 seconds and 3 git commands to generate.