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 try validateArgumentsNumber(function: self, received: args.count) 69 70 var arguments = concat( 71 arguments: args, 72 withOwner: owner, 73 withPromise: nil, 74 forFunction: self, 75 appContext: appContext 76 ) 77 78 // Convert JS values to non-JS native types. 79 arguments = try cast(jsValues: arguments, forFunction: self, appContext: appContext) 80 81 // Convert arguments to the types desired by the function. 82 arguments = try cast(arguments: arguments, forFunction: self, appContext: appContext) 83 84 let argumentsTuple = try Conversions.toTuple(arguments) as! Args 85 let result = try body(argumentsTuple) 86 return Conversions.convertFunctionResult(result, appContext: appContext) 87 } catch let error as Exception { 88 throw FunctionCallException(name).causedBy(error) 89 } catch { 90 throw UnexpectedException(error) 91 } 92 } 93 94 // MARK: - JavaScriptObjectBuilder 95 96 func build(appContext: AppContext) throws -> JavaScriptObject { 97 // We intentionally capture a strong reference to `self`, otherwise the "detached" objects would 98 // immediately lose the reference to the definition and thus the underlying native function. 99 // It may potentially cause memory leaks, but at the time of writing this comment, 100 // the native definition instance deallocates correctly when the JS VM triggers the garbage collector. 101 return try appContext.runtime.createSyncFunction(name, argsCount: argumentsCount) { [weak appContext, self] this, args in 102 guard let appContext else { 103 throw Exceptions.AppContextLost() 104 } 105 let result = try self.call(by: this, withArguments: args, appContext: appContext) 106 return Conversions.convertFunctionResult(result, appContext: appContext) 107 } 108 } 109 } 110 111 /** 112 Synchronous function without arguments. 113 */ 114 public func Function<R>( 115 _ name: String, 116 @_implicitSelfCapture _ closure: @escaping () throws -> R 117 ) -> SyncFunctionComponent<(), Void, R> { 118 return SyncFunctionComponent( 119 name, 120 firstArgType: Void.self, 121 dynamicArgumentTypes: [], 122 closure 123 ) 124 } 125 126 /** 127 Synchronous function with one argument. 128 */ 129 public func Function<R, A0: AnyArgument>( 130 _ name: String, 131 @_implicitSelfCapture _ closure: @escaping (A0) throws -> R 132 ) -> SyncFunctionComponent<(A0), A0, R> { 133 return SyncFunctionComponent( 134 name, 135 firstArgType: A0.self, 136 dynamicArgumentTypes: [~A0.self], 137 closure 138 ) 139 } 140 141 /** 142 Synchronous function with two arguments. 143 */ 144 public func Function<R, A0: AnyArgument, A1: AnyArgument>( 145 _ name: String, 146 @_implicitSelfCapture _ closure: @escaping (A0, A1) throws -> R 147 ) -> SyncFunctionComponent<(A0, A1), A0, R> { 148 return SyncFunctionComponent( 149 name, 150 firstArgType: A0.self, 151 dynamicArgumentTypes: [~A0.self, ~A1.self], 152 closure 153 ) 154 } 155 156 /** 157 Synchronous function with three arguments. 158 */ 159 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument>( 160 _ name: String, 161 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2) throws -> R 162 ) -> SyncFunctionComponent<(A0, A1, A2), A0, R> { 163 return SyncFunctionComponent( 164 name, 165 firstArgType: A0.self, 166 dynamicArgumentTypes: [ 167 ~A0.self, 168 ~A1.self, 169 ~A2.self 170 ], 171 closure 172 ) 173 } 174 175 /** 176 Synchronous function with four arguments. 177 */ 178 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument>( 179 _ name: String, 180 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3) throws -> R 181 ) -> SyncFunctionComponent<(A0, A1, A2, A3), A0, R> { 182 return SyncFunctionComponent( 183 name, 184 firstArgType: A0.self, 185 dynamicArgumentTypes: [ 186 ~A0.self, 187 ~A1.self, 188 ~A2.self, 189 ~A3.self 190 ], 191 closure 192 ) 193 } 194 195 /** 196 Synchronous function with five arguments. 197 */ 198 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument>( 199 _ name: String, 200 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4) throws -> R 201 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4), A0, R> { 202 return SyncFunctionComponent( 203 name, 204 firstArgType: A0.self, 205 dynamicArgumentTypes: [ 206 ~A0.self, 207 ~A1.self, 208 ~A2.self, 209 ~A3.self, 210 ~A4.self 211 ], 212 closure 213 ) 214 } 215 216 /** 217 Synchronous function with six arguments. 218 */ 219 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument>( 220 _ name: String, 221 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5) throws -> R 222 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5), A0, R> { 223 return SyncFunctionComponent( 224 name, 225 firstArgType: A0.self, 226 dynamicArgumentTypes: [ 227 ~A0.self, 228 ~A1.self, 229 ~A2.self, 230 ~A3.self, 231 ~A4.self, 232 ~A5.self 233 ], 234 closure 235 ) 236 } 237 238 /** 239 Synchronous function with seven arguments. 240 */ 241 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument>( 242 _ name: String, 243 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6) throws -> R 244 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6), A0, R> { 245 return SyncFunctionComponent( 246 name, 247 firstArgType: A0.self, 248 dynamicArgumentTypes: [ 249 ~A0.self, 250 ~A1.self, 251 ~A2.self, 252 ~A3.self, 253 ~A4.self, 254 ~A5.self, 255 ~A6.self 256 ], 257 closure 258 ) 259 } 260 261 /** 262 Synchronous function with eight arguments. 263 */ 264 public func Function<R, A0: AnyArgument, A1: AnyArgument, A2: AnyArgument, A3: AnyArgument, A4: AnyArgument, A5: AnyArgument, A6: AnyArgument, A7: AnyArgument>( 265 _ name: String, 266 @_implicitSelfCapture _ closure: @escaping (A0, A1, A2, A3, A4, A5, A6, A7) throws -> R 267 ) -> SyncFunctionComponent<(A0, A1, A2, A3, A4, A5, A6, A7), A0, R> { 268 return SyncFunctionComponent( 269 name, 270 firstArgType: A0.self, 271 dynamicArgumentTypes: [ 272 ~A0.self, 273 ~A1.self, 274 ~A2.self, 275 ~A3.self, 276 ~A4.self, 277 ~A5.self, 278 ~A6.self, 279 ~A7.self 280 ], 281 closure 282 ) 283 } 284