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