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 */
castnull13 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 */
castnull30 internal func cast(arguments: [Any], forFunction function: AnyFunction) throws -> [Any] {
31 let requiredArgumentsCount = function.requiredArgumentsCount
32 let argumentTypeOffset = function.takesOwner ? 1 : 0
33
34 if arguments.count < requiredArgumentsCount || arguments.count > function.argumentsCount {
35 throw InvalidArgsNumberException((
36 received: arguments.count,
37 expected: function.argumentsCount,
38 required: requiredArgumentsCount
39 ))
40 }
41 return try arguments.enumerated().map { index, argument in
42 let argumentType = function.dynamicArgumentTypes[index + argumentTypeOffset]
43
44 do {
45 return try cast(argument, toType: argumentType)
46 } catch {
47 throw ArgumentCastException((index: index, type: argumentType)).causedBy(error)
48 }
49 }
50 }
51
52 /**
53 Ensures the provided array of arguments matches the number of arguments expected by the function.
54 - If the function takes the owner, it's added to the beginning.
55 - If the array is still too small, missing arguments are very likely to be optional so it puts `nil` in their place.
56 */
concatnull57 internal func concat(arguments: [Any], withOwner owner: AnyObject?, forFunction function: AnyFunction) -> [Any] {
58 var result = arguments
59
60 if function.takesOwner, let owner = try? function.dynamicArgumentTypes.first?.cast(owner) {
61 result = [owner] + arguments
62 }
63 if arguments.count < function.argumentsCount {
64 result += Array(repeating: Any?.none as Any, count: function.argumentsCount - arguments.count)
65 }
66 return result
67 }
68
69 // MARK: - Exceptions
70
71 internal class InvalidArgsNumberException: GenericException<(received: Int, expected: Int, required: Int)> {
72 override var reason: String {
73 if param.required < param.expected {
74 return "Received \(param.received) arguments, but \(param.expected) was expected and at least \(param.required) is required"
75 } else {
76 return "Received \(param.received) arguments, but \(param.expected) was expected"
77 }
78 }
79 }
80
81 internal class ArgumentCastException: GenericException<(index: Int, type: AnyDynamicType)> {
82 override var reason: String {
83 "The \(formatOrdinalNumber(param.index + 1)) argument cannot be cast to type \(param.type.description)"
84 }
85
formatOrdinalNumbernull86 func formatOrdinalNumber(_ number: Int) -> String {
87 let formatter = NumberFormatter()
88 formatter.numberStyle = .ordinal
89 formatter.locale = Locale(identifier: "en_US")
90 return formatter.string(from: NSNumber(value: number)) ?? ""
91 }
92 }
93
94 private class ModuleUnavailableException: GenericException<String> {
95 override var reason: String {
96 "Module '\(param)' is no longer available"
97 }
98 }
99