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