1ec62a260STomasz Sapeta // Copyright 2022-present 650 Industries. All rights reserved.
2ec62a260STomasz Sapeta 
3ec62a260STomasz Sapeta public extension JavaScriptRuntime {
4ec62a260STomasz Sapeta   /**
54d7ea828STomasz Sapeta    A type of the closure that you pass to the `createSyncFunction` function.
64d7ea828STomasz Sapeta    */
74d7ea828STomasz Sapeta   typealias SyncFunctionClosure = (_ this: JavaScriptValue, _ arguments: [JavaScriptValue]) throws -> Any
84d7ea828STomasz Sapeta 
94d7ea828STomasz Sapeta   /**
10ec62a260STomasz Sapeta    Evaluates JavaScript code represented as a string.
11ec62a260STomasz Sapeta 
12ec62a260STomasz Sapeta    - Parameter source: A string representing a JavaScript expression, statement, or sequence of statements.
13ec62a260STomasz Sapeta                        The expression can include variables and properties of existing objects.
14ec62a260STomasz Sapeta    - Returns: The completion value of evaluating the given code represented as `JavaScriptValue`.
15ec62a260STomasz Sapeta               If the completion value is empty, `undefined` is returned.
16d84b7ae9STomasz Sapeta    - Throws: `JavaScriptEvalException` when evaluated code has invalid syntax or throws an error.
17ec62a260STomasz Sapeta    - Note: It wraps the original `evaluateScript` to better handle and rethrow exceptions.
18ec62a260STomasz Sapeta    */
19ce45e284STomasz Sapeta   @discardableResult
evalnull20ec62a260STomasz Sapeta   func eval(_ source: String) throws -> JavaScriptValue {
21d84b7ae9STomasz Sapeta     do {
22d84b7ae9STomasz Sapeta       var result: JavaScriptValue?
23d84b7ae9STomasz Sapeta       try EXUtilities.catchException {
24*419852c4STomasz Sapeta         result = self.__evaluateScript(source)
25d84b7ae9STomasz Sapeta       }
26d84b7ae9STomasz Sapeta       // There is no risk to force unwrapping as long as the `evaluateScript` returns nonnull value.
27d84b7ae9STomasz Sapeta       return result!
28d84b7ae9STomasz Sapeta     } catch {
29d84b7ae9STomasz Sapeta       throw JavaScriptEvalException(error as NSError)
30d84b7ae9STomasz Sapeta     }
31d84b7ae9STomasz Sapeta   }
324d7ea828STomasz Sapeta 
334d7ea828STomasz Sapeta   /**
34*419852c4STomasz Sapeta    Evaluates the JavaScript code made by joining an array of strings with a newline separator.
35*419852c4STomasz Sapeta    See the other ``eval(_:)`` for more details.
36*419852c4STomasz Sapeta    */
37*419852c4STomasz Sapeta   @discardableResult
evalnull38*419852c4STomasz Sapeta   func eval(_ source: [String]) throws -> JavaScriptValue {
39*419852c4STomasz Sapeta     try eval(source.joined(separator: "\n"))
40*419852c4STomasz Sapeta   }
41*419852c4STomasz Sapeta 
42*419852c4STomasz Sapeta   /**
434d7ea828STomasz Sapeta    Creates a synchronous host function that runs the given closure when it's called.
444d7ea828STomasz Sapeta    The value returned by the closure is synchronously returned to JS.
454d7ea828STomasz Sapeta    - Returns: A JavaScript function represented as a `JavaScriptObject`.
464d7ea828STomasz Sapeta    - Note: It refines the ObjC implementation from `EXJavaScriptRuntime` to properly catch Swift errors and rethrow them as ObjC `NSError`.
474d7ea828STomasz Sapeta    */
createSyncFunctionnull484d7ea828STomasz Sapeta   func createSyncFunction(_ name: String, argsCount: Int = 0, closure: @escaping SyncFunctionClosure) -> JavaScriptObject {
494d7ea828STomasz Sapeta     return __createSyncFunction(name, argsCount: argsCount) { this, args, errorPointer in
504d7ea828STomasz Sapeta       do {
514d7ea828STomasz Sapeta         return try runWithErrorPointer(errorPointer) {
524d7ea828STomasz Sapeta           return try closure(this, args)
534d7ea828STomasz Sapeta         }
544d7ea828STomasz Sapeta       } catch {
554d7ea828STomasz Sapeta         // Nicely log all errors to the console.
564d7ea828STomasz Sapeta         log.error(error)
574d7ea828STomasz Sapeta 
584d7ea828STomasz Sapeta         // Can return anything as the error will be caught through the error pointer already.
594d7ea828STomasz Sapeta         return nil
604d7ea828STomasz Sapeta       }
614d7ea828STomasz Sapeta     }
624d7ea828STomasz Sapeta   }
63d84b7ae9STomasz Sapeta }
64d84b7ae9STomasz Sapeta 
65d84b7ae9STomasz Sapeta internal final class JavaScriptEvalException: GenericException<NSError> {
66d84b7ae9STomasz Sapeta   override var reason: String {
67d84b7ae9STomasz Sapeta     return param.userInfo["message"] as? String ?? "unknown reason"
68ec62a260STomasz Sapeta   }
69ec62a260STomasz Sapeta }
70