1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 /**
4  Type-erased protocol for synchronous functions.
5  */
6 internal protocol AnySyncFunctionComponent: AnyFunction {
7   /**
8    Calls the function synchronously with given arguments.
9    - Parameters:
10      - owner: An object that calls this function. If the `takesOwner` property is true
11        and type of the first argument matches the owner type, it's being passed as the argument.
12      - args: An array of arguments to pass to the function. The arguments must be of the same type as in the underlying closure.
13    - Returns: A value returned by the called function when succeeded or an error when it failed.
14    */
15   func call(by owner: AnyObject?, withArguments args: [Any]) throws -> Any
16 }
17 
18 /**
19  Represents a function that can only be called synchronously.
20  */
21 public final class SyncFunctionComponent<Args, FirstArgType, ReturnType>: AnySyncFunctionComponent {
22   typealias ClosureType = (Args) throws -> ReturnType
23 
24   /**
25    The underlying closure to run when the function is called.
26    */
27   let body: ClosureType
28 
29   init(
30     _ name: String,
31     firstArgType: FirstArgType.Type,
32     dynamicArgumentTypes: [AnyDynamicType],
33     _ body: @escaping ClosureType
34   ) {
35     self.name = name
36     self.dynamicArgumentTypes = dynamicArgumentTypes
37     self.body = body
38   }
39 
40   // MARK: - AnyFunction
41 
42   let name: String
43 
44   let dynamicArgumentTypes: [AnyDynamicType]
45 
46   var argumentsCount: Int {
47     return dynamicArgumentTypes.count - (takesOwner ? 1 : 0)
48   }
49 
50   var takesOwner: Bool = false
51 
52   func call(by owner: AnyObject?, withArguments args: [Any], callback: @escaping (FunctionCallResult) -> ()) {
53     do {
54       let result = try call(by: owner, withArguments: args)
55       callback(.success(Conversions.convertFunctionResult(result)))
56     } catch let error as Exception {
57       callback(.failure(error))
58     } catch {
59       callback(.failure(UnexpectedException(error)))
60     }
61   }
62 
63   // MARK: - AnySyncFunctionComponent
64 
65   func call(by owner: AnyObject?, withArguments args: [Any]) throws -> Any {
66     do {
67       let arguments = concat(
68         arguments: try cast(arguments: args, forFunction: self),
69         withOwner: owner,
70         forFunction: self
71       )
72       let argumentsTuple = try Conversions.toTuple(arguments) as! Args
73       let result = try body(argumentsTuple)
74       return Conversions.convertFunctionResult(result)
75     } catch let error as Exception {
76       throw FunctionCallException(name).causedBy(error)
77     } catch {
78       throw UnexpectedException(error)
79     }
80   }
81 
82   // MARK: - JavaScriptObjectBuilder
83 
84   func build(inRuntime runtime: JavaScriptRuntime) -> JavaScriptObject {
85     // We intentionally capture a strong reference to `self`, otherwise the "detached" objects would
86     // immediately lose the reference to the definition and thus the underlying native function.
87     // It may potentially cause memory leaks, but at the time of writing this comment,
88     // the native definition instance deallocates correctly when the JS VM triggers the garbage collector.
89     return runtime.createSyncFunction(name, argsCount: argumentsCount) { [weak runtime, self] this, args in
90       guard let runtime = runtime else {
91         throw Exceptions.RuntimeLost()
92       }
93       let result = try self.call(by: this, withArguments: args)
94       return Conversions.convertFunctionResult(result, runtime: runtime)
95     }
96   }
97 }
98 
99 /**
100  Synchronous function without arguments.
101  */
102 public func Function<R>(
103   _ name: String,
104   @_implicitSelfCapture _ closure: @escaping () throws -> R
105 ) -> SyncFunctionComponent<(), Void, R> {
106   return SyncFunctionComponent(
107     name,
108     firstArgType: Void.self,
109     dynamicArgumentTypes: [],
110     closure
111   )
112 }
113 
114 /**
115  Synchronous function with one argument.
116  */
117 public func Function<R, A0: AnyArgument>(
118   _ name: String,
119   @_implicitSelfCapture _ closure: @escaping (A0) throws -> R
120 ) -> SyncFunctionComponent<(A0), A0, R> {
121   return SyncFunctionComponent(
122     name,
123     firstArgType: A0.self,
124     dynamicArgumentTypes: [~A0.self],
125     closure
126   )
127 }
128 
129 /**
130  Synchronous function with two arguments.
131  */
132 public func Function<R, A0: AnyArgument, A1: AnyArgument>(
133   _ name: String,
134   @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R
135 ) -> SyncFunctionComponent<(A0, A1), A0, R> {
136   return SyncFunctionComponent(
137     name,
138     firstArgType: A0.self,
139     dynamicArgumentTypes: [~A0.self, ~A1.self],
140     closure
141   )
142 }
143 
144 /**
145  Synchronous function with three arguments.
146  */
147 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>(
148   _ name: String,
149   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R
150 ) -> SyncFunctionComponent<(A0, A1, A2), A0, R> {
151   return SyncFunctionComponent(
152     name,
153     firstArgType: A0.self,
154     dynamicArgumentTypes: [
155       ~A0.self,
156       ~A1.self,
157       ~A2.self
158     ],
159     closure
160   )
161 }
162 
163 /**
164  Synchronous function with four arguments.
165  */
166 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>(
167   _ name: String,
168   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R
169 ) -> SyncFunctionComponent<(A0, A1, A2, A3), A0, R> {
170   return SyncFunctionComponent(
171     name,
172     firstArgType: A0.self,
173     dynamicArgumentTypes: [
174       ~A0.self,
175       ~A1.self,
176       ~A2.self,
177       ~A3.self
178     ],
179     closure
180   )
181 }
182 
183 /**
184  Synchronous function with five arguments.
185  */
186 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>(
187   _ name: String,
188   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R
189 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> {
190   return SyncFunctionComponent(
191     name,
192     firstArgType: A0.self,
193     dynamicArgumentTypes: [
194       ~A0.self,
195       ~A1.self,
196       ~A2.self,
197       ~A3.self,
198       ~A4.self
199     ],
200     closure
201   )
202 }
203 
204 /**
205  Synchronous function with six arguments.
206  */
207 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>(
208   _ name: String,
209   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R
210 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> {
211   return SyncFunctionComponent(
212     name,
213     firstArgType: A0.self,
214     dynamicArgumentTypes: [
215       ~A0.self,
216       ~A1.self,
217       ~A2.self,
218       ~A3.self,
219       ~A4.self,
220       ~A5.self
221     ],
222     closure
223   )
224 }
225 
226 /**
227  Synchronous function with seven arguments.
228  */
229 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>(
230   _ name: String,
231   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R
232 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> {
233   return SyncFunctionComponent(
234     name,
235     firstArgType: A0.self,
236     dynamicArgumentTypes: [
237       ~A0.self,
238       ~A1.self,
239       ~A2.self,
240       ~A3.self,
241       ~A4.self,
242       ~A5.self,
243       ~A6.self
244     ],
245     closure
246   )
247 }
248 
249 /**
250  Synchronous function with eight arguments.
251  */
252 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>(
253   _ name: String,
254   @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R
255 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> {
256   return SyncFunctionComponent(
257     name,
258     firstArgType: A0.self,
259     dynamicArgumentTypes: [
260       ~A0.self,
261       ~A1.self,
262       ~A2.self,
263       ~A3.self,
264       ~A4.self,
265       ~A5.self,
266       ~A6.self,
267       ~A7.self
268     ],
269     closure
270   )
271 }
272