1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 // MARK: - Arguments
4 
5 /**
6  Tries to cast a given value to the type that is wrapped by the dynamic type.
7  - Parameters:
8   - value: A value to be cast. If it's a ``JavaScriptValue``, it's first unpacked to the raw value.
9   - type: Something that implements ``AnyDynamicType`` and knows how to cast the argument.
10  - Returns: A new value converted according to the dynamic type.
11  - Throws: Rethrows various exceptions that could be thrown by the dynamic types.
12  */
13 internal func cast(_ value: Any, toType type: AnyDynamicType) throws -> Any {
14   // TODO: Accept JavaScriptValue and JavaScriptObject as argument types.
15   if !(type is DynamicTypedArrayType), let value = value as? JavaScriptValue {
16     return try type.cast(value.getRaw())
17   }
18   return try type.cast(value)
19 }
20 
21 /**
22  Tries to cast the given arguments to the types expected by the function.
23  - Parameters:
24    - arguments: An array of arguments to be cast.
25    - function: A function for which to cast the arguments.
26  - Returns: An array of arguments after casting. Its size is the same as the input arrays.
27  - Throws: `InvalidArgsNumberException` when the number of arguments is not equal to the actual number
28  of function's arguments (without an owner and promise). Rethrows exceptions thrown by `cast(_:toType:)`.
29  */
30 internal func cast(arguments: [Any], forFunction function: AnyFunction) throws -> [Any] {
31   if arguments.count != function.argumentsCount {
32     throw InvalidArgsNumberException((received: arguments.count, expected: function.argumentsCount))
33   }
34   let argumentTypeOffset = function.takesOwner ? 1 : 0
35   return try arguments.enumerated().map { index, argument in
36     let argumentType = function.dynamicArgumentTypes[index + argumentTypeOffset]
37 
38     do {
39       return try cast(argument, toType: argumentType)
40     } catch {
41       throw ArgumentCastException((index: index, type: argumentType)).causedBy(error)
42     }
43   }
44 }
45 
46 /**
47  Prepends the owner to the array of arguments if the given function can take it.
48  */
49 internal func concat(arguments: [Any], withOwner owner: AnyObject?, forFunction function: AnyFunction) -> [Any] {
50   if function.takesOwner, let owner = try? function.dynamicArgumentTypes.first?.cast(owner) {
51     return [owner] + arguments
52   }
53   return arguments
54 }
55 
56 // MARK: - Exceptions
57 
58 internal class InvalidArgsNumberException: GenericException<(received: Int, expected: Int)> {
59   override var reason: String {
60     "Received \(param.received) arguments, but \(param.expected) was expected"
61   }
62 }
63 
64 internal class ArgumentCastException: GenericException<(index: Int, type: AnyDynamicType)> {
65   override var reason: String {
66     "Argument at index '\(param.index)' couldn't be cast to type \(param.type.description)"
67   }
68 }
69 
70 private class ModuleUnavailableException: GenericException<String> {
71   override var reason: String {
72     "Module '\(param)' is no longer available"
73   }
74 }
75