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