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