]> Lady’s Gitweb - Pisces/blob - function.js
Make create⸺Function setup more flexible
[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, toFunctionName, toLength } 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 * The `length`, `name`, and prototype of the provided function will
28 * be preserved in the new one. A second argument may be used to
29 * override `length` and `name`.
30 */
31 createArrowFunction,
32
33 /**
34 * Returns a new function which calls the provided function with its
35 * first argument as the `this` value and the remaining arguments
36 * passed through.
37 *
38 * The `length`, `name`, and prototype of the provided function will
39 * be preserved in the new one. A second argument may be used to
40 * override `length` and `name`.
41 *
42 * ※ This is effectively an alias for `Function::call.bind`.
43 */
44 createCallableFunction,
45
46 /**
47 * Returns a constructor which throws whenever it is called but has
48 * the same `.name` and `.prototype` as the provided value.
49 *
50 * The `length`, `name`, `prototype`, and prototype of the provided
51 * function will be preserved in the new one. A second argument may
52 * be used to override `length`, `name`, and `prototype`.
53 */
54 createIllegalConstructor,
55 } = (() => {
56 const { prototype: functionPrototype } = Function;
57 const {
58 bind: functionBind,
59 call: functionCall,
60 } = functionPrototype;
61 const callBind = Reflect.apply(functionBind, functionCall, [
62 functionBind,
63 ]);
64 const {
65 create: objectCreate,
66 defineProperty: defineOwnProperty,
67 defineProperties: defineOwnProperties,
68 hasOwn: hasOwnProperty,
69 getPrototypeOf: getPrototype,
70 setPrototypeOf: setPrototype,
71 } = Object;
72 const { [ITERATOR]: arrayIterator } = Array.prototype;
73 const {
74 next: arrayIteratorNext,
75 } = getPrototype([][ITERATOR]());
76 const argumentIterablePrototype = {
77 [ITERATOR]() {
78 return {
79 [ITERATOR]() {
80 return this;
81 },
82 next: callBind(
83 arrayIteratorNext,
84 call(arrayIterator, this.args, []),
85 ),
86 };
87 },
88 };
89 const applyBaseFunction = ($, base, lengthDelta = 0) => {
90 if (base === undefined) {
91 // No base function was provided to apply.
92 return $;
93 } else {
94 // A base function was provided; apply it.
95 const { length, name, prototype } = base;
96 if (getPrototype($) === functionPrototype) {
97 setPrototype($, getPrototype(base));
98 } else {
99 /* do nothing */
100 }
101 return applyProperties($, {
102 length: +length + lengthDelta,
103 name,
104 prototype,
105 });
106 }
107 };
108 const applyProperties = ($, override) => {
109 if (override === undefined) {
110 // No properties were provided to apply.
111 return $;
112 } else {
113 // Properties were provided; apply them.
114 const { length, name, prototype } = override;
115 if (
116 !isConstructor($) || !hasOwnProperty($, "prototype") ||
117 prototype === undefined
118 ) {
119 // The provided function is not a constructor, has no
120 // `.prototype`, or no prototype value was provided.
121 //
122 // Do not modify the prototype property of the provided
123 // function.
124 /* do nothing */
125 } else {
126 // The provided function is a constructor and a prototype value
127 // was provided.
128 //
129 // Change the prototype property of the provided function to
130 // match.
131 defineOwnProperty($, "prototype", { value: prototype });
132 }
133 return defineOwnProperties($, {
134 length: {
135 value: toLength(length === undefined ? $.length : length),
136 },
137 name: {
138 value: toFunctionName(
139 name === undefined ? $.name ?? "" : name,
140 ),
141 },
142 });
143 }
144 };
145
146 return {
147 bind: ($, boundThis, boundArgs) =>
148 callBind(
149 $,
150 boundThis,
151 ...objectCreate(
152 argumentIterablePrototype,
153 { args: { value: boundArgs } },
154 ),
155 ),
156 createArrowFunction: ($, propertyOverride = undefined) =>
157 applyProperties(
158 applyBaseFunction(
159 (...$s) =>
160 $(...objectCreate(
161 argumentIterablePrototype,
162 { args: { value: $s } },
163 )),
164 $,
165 ),
166 propertyOverride,
167 ),
168 createCallableFunction: ($, propertyOverride = undefined) =>
169 applyProperties(
170 applyBaseFunction(
171 (...$s) => {
172 const iterator = objectCreate(
173 argumentIterablePrototype,
174 { args: { value: $s } },
175 )[ITERATOR]();
176 const { value: thisValue } = iterator.next();
177 return call($, thisValue, [...iterator]);
178 },
179 $,
180 1,
181 ),
182 propertyOverride,
183 ),
184 createIllegalConstructor: ($, propertyOverride = undefined) => {
185 const constructor = applyProperties(
186 applyBaseFunction(
187 function () {
188 throw new TypeError("Illegal constructor");
189 },
190 $,
191 ),
192 propertyOverride,
193 );
194 return defineOwnProperty(constructor, "prototype", {
195 writable: false,
196 });
197 },
198 };
199 })();
200
201 export const {
202 /**
203 * Calls the provided function with the provided this value and
204 * arguments list.
205 *
206 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
207 * must be passed as an arraylike.
208 */
209 call,
210
211 /**
212 * Constructs the provided function with the provided arguments list
213 * and new target.
214 *
215 * ☡ This is effectively an alias for `Reflect.construct`—the
216 * arguments must be passed as an arraylike.
217 */
218 construct,
219
220 /** Returns whether the provided value is a constructor. */
221 isConstructor,
222 } = (() => {
223 const { apply, construct } = Reflect;
224 return {
225 call: (target, thisArgument, argumentsList) =>
226 apply(target, thisArgument, argumentsList),
227 construct: (target, argumentsList, ...args) =>
228 args.length > 0
229 ? construct(target, argumentsList, args[0])
230 : construct(target, argumentsList),
231 isConstructor: ($) =>
232 completesNormally(() =>
233 // Try constructing a new object with the provided value as its
234 // `new.target`. This will throw if the provided value is not a
235 // constructor.
236 construct(function () {}, [], $)
237 ),
238 };
239 })();
240
241 /**
242 * Returns whether calling the provided function with no `this` value
243 * or arguments completes normally; that is, does not throw an error.
244 *
245 * ☡ This function will throw an error if the provided argument is not
246 * callable.
247 */
248 export const completesNormally = ($) => {
249 if (!isCallable($)) {
250 // The provided value is not callable; this is an error.
251 throw new TypeError(
252 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
253 );
254 } else {
255 // The provided value is callable.
256 try {
257 // Attempt to call the function and return true if this succeeds.
258 $();
259 return true;
260 } catch {
261 // Calling the function did not succeed; return false.
262 return false;
263 }
264 }
265 };
266
267 /**
268 * Returns the provided value.
269 *
270 * ※ This function can be called as a constructor. When used in an
271 * `extends` clause and called via `super`, it will set the value of
272 * `this` to the provided value, enabling it to be extended with
273 * private class features.
274 */
275 export const identity = function ($) {
276 return $;
277 };
278
279 /** Returns whether the provided value is callable. */
280 export const isCallable = ($) => typeof $ === "function";
281
282 /**
283 * Returns whether the provided object inherits from the prototype of
284 * the provided function.
285 */
286 export const ordinaryHasInstance = createCallableFunction(
287 Function.prototype[Symbol.hasInstance],
288 { name: "ordinaryHasInstance" },
289 );
This page took 0.142041 seconds and 5 git commands to generate.