From f62da0cbcf836c1ff8a9aef48451a52275f461b4 Mon Sep 17 00:00:00 2001 From: ventywing Date: Tue, 4 Jul 2023 16:48:13 +0300 Subject: [PATCH] Support Dict --- .../Expressions/ExpressionResolver.swift | 6 +- .../Expressions/Functions/CastFunctions.swift | 32 ++ .../Expressions/Functions/DictFunctions.swift | 335 ++++++++++++++++++ .../Expressions/Functions/Function.swift | 59 ++- .../Variables/DivVariablesStorage.swift | 19 +- .../ExpressionResolvingTests.swift | 97 +++++ .../Expressions/FunctionSignaturesTests.swift | 5 +- schema/div-variable.json | 5 +- .../function_signatures_std.json | 48 ++- .../expression_test_data/functions_dict.json | 215 ++++++++--- .../functions_to_color.json | 6 +- .../functions_to_url.json | 3 +- 12 files changed, 746 insertions(+), 84 deletions(-) create mode 100644 client/ios/DivKit/Expressions/Functions/DictFunctions.swift diff --git a/client/ios/DivKit/Expressions/ExpressionResolver.swift b/client/ios/DivKit/Expressions/ExpressionResolver.swift index de7d864dd..1b16ed661 100644 --- a/client/ios/DivKit/Expressions/ExpressionResolver.swift +++ b/client/ios/DivKit/Expressions/ExpressionResolver.swift @@ -248,7 +248,10 @@ public final class ExpressionResolver { ) } - private lazy var supportedFunctions: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] = + private lazy var supportedFunctions: [ + AnyCalcExpression.Symbol: AnyCalcExpression + .SymbolEvaluator + ] = _supportedFunctions + ValueFunctions.allCases.map { $0.getDeclaration(resolver: self) }.flat() } @@ -259,6 +262,7 @@ private let _supportedFunctions: [AnyCalcExpression.Symbol: AnyCalcExpression.Sy + DatetimeFunctions.allCases.map(\.declaration).flat() + MathFunctions.allCases.map(\.declaration).flat() + IntervalFunctions.allCases.map(\.declaration).flat() + + DictFunctions.allCases.map(\.declaration).flat() extension ExpressionResolver: ConstantsProvider { func getValue(_ name: String) -> Any? { diff --git a/client/ios/DivKit/Expressions/Functions/CastFunctions.swift b/client/ios/DivKit/Expressions/Functions/CastFunctions.swift index 754ffab27..83e7e470e 100644 --- a/client/ios/DivKit/Expressions/Functions/CastFunctions.swift +++ b/client/ios/DivKit/Expressions/Functions/CastFunctions.swift @@ -7,6 +7,8 @@ enum CastFunctions: String, CaseIterable { case toString case toNumber case toInteger + case toColor + case toUrl var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { [.function(rawValue, arity: function.arity): function.symbolEvaluator] @@ -58,6 +60,10 @@ enum CastFunctions: String, CaseIterable { AnyCalcExpression.Error.toInteger($0.first) } ) + case .toColor: + return FunctionUnary(impl: _stringToColor) + case .toUrl: + return FunctionUnary(impl: _stringToUrl) } } } @@ -136,6 +142,20 @@ private func _doubleToInteger(value: Double) throws -> Int { return number } +private func _stringToColor(value: String) throws -> Color { + guard let color = Color.color(withHexString: value) else { + throw AnyCalcExpression.Error.toColor(value) + } + return color +} + +private func _stringToUrl(value: String) throws -> URL { + guard let url = URL(string: value) else { + throw AnyCalcExpression.Error.toUrl(value) + } + return url +} + extension Bool { fileprivate func toInteger() -> Int { switch self { @@ -206,6 +226,18 @@ extension AnyCalcExpression.Error { ) } + fileprivate static func toColor(_ value: String) -> AnyCalcExpression.Error { + .message( + "Failed to evaluate [toColor('\(value)')]. Unable to convert value to Color, expected format #AARRGGBB." + ) + } + + fileprivate static func toUrl(_ value: String) -> AnyCalcExpression.Error { + .message( + "Failed to evaluate [toUrl('\(value)')]. Unable to convert value to URL." + ) + } + private static func formatValue(_ value: Any?) -> String { switch value { case let strValue as String: diff --git a/client/ios/DivKit/Expressions/Functions/DictFunctions.swift b/client/ios/DivKit/Expressions/Functions/DictFunctions.swift new file mode 100644 index 000000000..a5ccff5cc --- /dev/null +++ b/client/ios/DivKit/Expressions/Functions/DictFunctions.swift @@ -0,0 +1,335 @@ +import CommonCorePublic +import Foundation + +enum DictFunctions: String, CaseIterable { + case getDictString + case getDictNumber + case getDictInteger + case getDictBoolean + case getDictColor + case getDictUrl + case getDictOptString + case getDictOptNumber + case getDictOptInteger + case getDictOptBoolean + case getDictOptColor + case getDictOptUrl + + var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { + [.function(rawValue, arity: function.arity): function.symbolEvaluator] + } + + var function: Function { + switch self { + case .getDictString: + return FunctionVarBinary(impl: _getDictString) + case .getDictNumber: + return FunctionVarBinary(impl: _getDictNumber) + case .getDictInteger: + return FunctionVarBinary(impl: _getDictInteger) + case .getDictBoolean: + return FunctionVarBinary(impl: _getDictBoolean) + case .getDictColor: + return FunctionVarBinary(impl: _getDictColor) + case .getDictUrl: + return FunctionVarBinary(impl: _getDictUrl) + case .getDictOptString: + return FunctionVarTernary(impl: _getDictOptString) + case .getDictOptNumber: + return FunctionVarTernary(impl: _getDictOptNumber) + case .getDictOptInteger: + return FunctionVarTernary(impl: _getDictOptInteger) + case .getDictOptBoolean: + return FunctionVarTernary(impl: _getDictOptBoolean) + case .getDictOptColor: + return OverloadedFunction( + functions: [ + FunctionVarTernary(impl: _getDictOptColorWithColorFallback), + FunctionVarTernary(impl: _getDictOptColorWithStringFallback), + ] + ) + case .getDictOptUrl: + return OverloadedFunction( + functions: [ + FunctionVarTernary(impl: _getDictOptUrlWithURLFallback), + FunctionVarTernary(impl: _getDictOptUrlWithStringFallback), + ] + ) + } + } + + private func _getDictString(dict: [String: AnyHashable], path: [String]) throws -> String { + let expression = makeExpression("getDictString", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + guard let stringValue = result as? String else { + throw Error.incorrectValueType(expression, "string", getActualType(result)).message + } + return stringValue + } + + private func _getDictNumber(dict: [String: AnyHashable], path: [String]) throws -> Double { + let expression = makeExpression("getDictNumber", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + if result.isBool { + throw Error.incorrectValueType(expression, "number", getActualType(result)).message + } + if let numberValue = result as? Double { + return numberValue + } + if let intValue = result as? Int { + return Double(intValue) + } + throw Error.incorrectValueType(expression, "number", getActualType(result)).message + } + + private func _getDictInteger(dict: [String: AnyHashable], path: [String]) throws -> Int { + let expression = makeExpression("getDictInteger", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + if result.isBool { + throw Error.incorrectValueType(expression, "integer", getActualType(result)).message + } + guard let intValue = result as? Int else { + if let doubleValue = result as? Double { + if doubleValue < Double(Int.min) || doubleValue > Double(Int.max) { + throw Error.integerOverflow(expression).message + } + throw Error.cannotConvertToInteger(expression).message + } + throw Error.incorrectValueType(expression, "integer", getActualType(result)).message + } + return intValue + } + + private func _getDictBoolean(dict: [String: AnyHashable], path: [String]) throws -> Bool { + let expression = makeExpression("getDictBoolean", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + guard result.isBool else { + throw Error.incorrectValueType(expression, "boolean", getActualType(result)).message + } + guard let boolValue = result as? Bool else { + throw Error.incorrectValueType(expression, "boolean", getActualType(result)).message + } + return boolValue + } + + private func _getDictColor(dict: [String: AnyHashable], path: [String]) throws -> Color { + let expression = makeExpression("getDictColor", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + guard let stringValue = result as? String else { + throw Error.incorrectValueType(expression, "color", getActualType(result)).message + } + guard let color = Color.color(withHexString: stringValue) else { + throw Error.incorrectColorFormat(expression).message + } + return color + } + + private func _getDictUrl(dict: [String: AnyHashable], path: [String]) throws -> URL { + let expression = makeExpression("getDictUrl", path) + let result = try getProp( + dict: dict, + path: path, + expression: expression + ) + guard let stringValue = result as? String else { + throw Error.incorrectValueType(expression, "url", getActualType(result)).message + } + guard let url = URL(string: stringValue) else { + throw Error.incorrectValueType(expression, "url", getActualType(result)).message + } + return url + } + + private func _getDictOptString( + fallback: String, + dict: [String: AnyHashable], + path: [String] + ) -> String { + guard let value = try? _getDictString(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptNumber( + fallback: Double, + dict: [String: AnyHashable], + path: [String] + ) -> Double { + guard let value = try? _getDictNumber(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptInteger( + fallback: Int, + dict: [String: AnyHashable], + path: [String] + ) -> Int { + guard let value = try? _getDictInteger(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptBoolean( + fallback: Bool, + dict: [String: AnyHashable], + path: [String] + ) -> Bool { + guard let value = try? _getDictBoolean(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptColorWithColorFallback( + fallback: Color, + dict: [String: AnyHashable], + path: [String] + ) -> Color { + guard let value = try? _getDictColor(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptColorWithStringFallback( + fallback: String, + dict: [String: AnyHashable], + path: [String] + ) -> Color { + guard let value = try? _getDictColor(dict: dict, path: path) else { + return Color.color(withHexString: fallback)! + } + return value + } + + private func _getDictOptUrlWithURLFallback( + fallback: URL, + dict: [String: AnyHashable], + path: [String] + ) -> URL { + guard let value = try? _getDictUrl(dict: dict, path: path) else { + return fallback + } + return value + } + + private func _getDictOptUrlWithStringFallback( + fallback: String, + dict: [String: AnyHashable], + path: [String] + ) -> URL { + guard let value = try? _getDictUrl(dict: dict, path: path) else { + return URL(string: fallback)! + } + return value + } + + private func getProp( + dict: [String: AnyHashable], + path: [String], + expression: String + ) throws -> AnyHashable { + var dictionary: [String: AnyHashable]? = dict + var i = 0 + while i < path.count - 1 { + let key = path[i] + if let current = dictionary { + dictionary = current[key] as? [String: AnyHashable] + } else { + throw Error.missingProperty(expression, key).message + } + i += 1 + } + guard let key = path.last else { + throw Error.wrongPath(expression).message + } + if let current = dictionary, + let result = current[key] { + return result + } else { + throw Error.missingProperty(expression, key).message + } + } +} + +private func getActualType(_ value: AnyHashable) -> String { + if value.isBool { + return "boolean" + } + switch value { + case is [String: AnyHashable]: + return "dict" + case is [Any]: + return "array" + case is String: + return "string" + case is Int, is Double: + return "number" + default: + return "null" + } +} + +private func makeExpression(_ function: String, _ path: [String]) -> String { + "\(function)(, \(path.map { "'\($0)'" }.joined(separator: ", ")))" +} + +private enum Error { + case missingProperty(String, String) + case wrongPath(String) + case incorrectValueType(String, String, String) + case cannotConvertToInteger(String) + case integerOverflow(String) + case incorrectColorFormat(String) + + private var description: String { + switch self { + case let .missingProperty(expression, missing): + return "Failed to evaluate [\(expression)]. Missing property \"\(missing)\" in the dict." + case let .wrongPath(expression): + return "Failed to evaluate [\(expression)]. Path is wrong." + case let .incorrectValueType(expression, expectedType, actualType): + return "Failed to evaluate [\(expression)]. Incorrect value type: expected \"\(expectedType)\", got \"\(actualType)\"." + case let .cannotConvertToInteger(expression): + return "Failed to evaluate [\(expression)]. Cannot convert value to integer." + case let .integerOverflow(expression): + return "Failed to evaluate [\(expression)]. Integer overflow." + case let .incorrectColorFormat(expression): + return "Failed to evaluate [\(expression)]. Unable to convert value to Color, expected format #AARRGGBB." + } + } + + var message: AnyCalcExpression.Error { + AnyCalcExpression.Error.message(description) + } +} + +extension AnyHashable { + fileprivate var isBool: Bool { + Bool(description) != nil && !(self is String) + } +} diff --git a/client/ios/DivKit/Expressions/Functions/Function.swift b/client/ios/DivKit/Expressions/Functions/Function.swift index 5b6050037..d76bee5bc 100644 --- a/client/ios/DivKit/Expressions/Functions/Function.swift +++ b/client/ios/DivKit/Expressions/Functions/Function.swift @@ -180,6 +180,63 @@ struct FunctionVarUnary: SimpleFunction { } } +struct FunctionVarBinary: SimpleFunction { + private let impl: (T1, [T2]) throws -> R + + var signature: FunctionSignature { + get throws { + .init( + arguments: [ + .init(type: try .from(type: T1.self)), + .init(type: try .from(type: T2.self), vararg: true), + ], + resultType: try .from(type: R.self) + ) + } + } + + init(impl: @escaping (T1, [T2]) throws -> R) { + self.impl = impl + } + + func invoke(args: [Any]) throws -> Any { + guard let v1 = args[0] as? T1 else { + throw FunctionSignature.Error.arityMismatch.message + } + let v2 = args.dropFirst().map { $0 as! T2 } + return try impl(v1, v2) + } +} + +struct FunctionVarTernary: SimpleFunction { + private let impl: (T1, T2, [T3]) throws -> R + + var signature: FunctionSignature { + get throws { + .init( + arguments: [ + .init(type: try .from(type: T1.self)), + .init(type: try .from(type: T2.self)), + .init(type: try .from(type: T3.self), vararg: true), + ], + resultType: try .from(type: R.self) + ) + } + } + + init(impl: @escaping (T1, T2, [T3]) throws -> R) { + self.impl = impl + } + + func invoke(args: [Any]) throws -> Any { + guard let v1 = args[0] as? T1, let v2 = args[1] as? T2 else { + throw FunctionSignature.Error.arityMismatch.message + } + let v3 = args.dropFirst(2).map { $0 as! T3 } + return try impl(v1, v2, v3) + } +} + struct OverloadedFunction: Function { let functions: [SimpleFunction] let makeError: ([Any]) -> Error @@ -257,7 +314,7 @@ struct FunctionSignature: Decodable { case .url: return URL.self case .dict: - return [String: Any].self + return [String: AnyHashable].self } } diff --git a/client/ios/DivKit/Variables/DivVariablesStorage.swift b/client/ios/DivKit/Variables/DivVariablesStorage.swift index aa5f44b4d..135e5a564 100644 --- a/client/ios/DivKit/Variables/DivVariablesStorage.swift +++ b/client/ios/DivKit/Variables/DivVariablesStorage.swift @@ -13,6 +13,7 @@ public enum DivVariableValue: Equatable { case bool(Bool) case color(Color) case url(URL) + case dict([String: AnyHashable]) @inlinable public func typedValue() -> T? { @@ -29,6 +30,8 @@ public enum DivVariableValue: Equatable { return value as? T case let .url(value): return value as? T + case let .dict(value): + return value as? T } } } @@ -80,7 +83,7 @@ public final class DivVariablesStorage { let variable = storage.local[cardId]?[name] ?? storage.global[name] return variable?.typedValue() } - + public func set( cardId: DivCardID, variables: DivVariables @@ -254,6 +257,9 @@ extension Dictionary where Key == DivVariableName, Value == DivVariableValue { } else { newValue = nil } + case .dict: + newValue = nil + DivKitLogger.warning("Unsupported variable type: dict") } if newValue == oldValue { @@ -300,8 +306,15 @@ extension Collection where Element == DivVariable { let name = DivVariableName(rawValue: urlVariable.name) if variables.keys.contains(name) { return } variables[name] = .url(urlVariable.value) - case .dictVariable: - DivKitLogger.warning("Unsupported variable type: dict") + case let .dictVariable(dictVariable): + let name = DivVariableName(rawValue: dictVariable.name) + if variables.keys.contains(name) { return } + if let dictionary = NSDictionary(dictionary: dictVariable.value) as? [String: AnyHashable] { + variables[name] = .dict(dictionary) + } else { + DivKitLogger.error("Incorrect value for dict variable \(name): \(dictVariable.value)") + variables[name] = .dict([:]) + } } } return variables diff --git a/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift b/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift index 6e2c505a1..641e2983a 100644 --- a/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift +++ b/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift @@ -61,6 +61,18 @@ final class ExpressionResolvingTests: XCTestCase { perform(on: testCases, type: type) } + func test_Functions_To_Color() throws { + let testCases = try makeTestCases(for: "functions_to_color") + let type: ExpressionType = .stringBased(initializer: { $0 }) + perform(on: testCases, type: type) + } + + func test_Functions_To_Url() throws { + let testCases = try makeTestCases(for: "functions_to_url") + let type: ExpressionType = .stringBased(initializer: { $0 }) + perform(on: testCases, type: type) + } + func test_Functions_Datetime() throws { let testCases = try makeTestCases(for: "functions_datetime") let type: ExpressionType = .singleItem @@ -73,6 +85,12 @@ final class ExpressionResolvingTests: XCTestCase { perform(on: testCases, type: type) } + func test_Functions_Dict() throws { + let testCases = try makeTestCases(for: "functions_dict") + let type: ExpressionType = .stringBased(initializer: { $0 }) + perform(on: testCases, type: type) + } + func test_Functions_Interval() throws { let testCases = try makeTestCases(for: "functions_interval") let type: ExpressionType = .stringBased(initializer: { $0 }) @@ -514,6 +532,9 @@ extension DivVariable: Decodable { case UrlVariable.type: let value = try container.decode(String.self, forKey: .value) self = .urlVariable(UrlVariable(name: name, value: URL(string: value)!)) + case DictVariable.type: + let dict = try container.decode(Dict.self, forKey: .value) + self = .dictVariable(DictVariable(name: name, value: dict.value)) default: throw DecodingError.valueNotFound( String.self, @@ -529,3 +550,79 @@ extension DivVariable: Decodable { case type, name, value } } + +fileprivate struct JSONCodingKeys: CodingKey { + var stringValue: String + + init(stringValue: String) { + self.stringValue = stringValue + } + + var intValue: Int? + + init?(intValue: Int) { + self.init(stringValue: "\(intValue)") + self.intValue = intValue + } +} + +fileprivate struct Dict: Decodable { + var value: [String: Any] = [:] + + public init(from decoder: Decoder) throws { + if let container = try? decoder.container(keyedBy: JSONCodingKeys.self) { + self.value = decode(fromObject: container) + } + } + + func decode(fromObject container: KeyedDecodingContainer) -> [String: Any] { + var result: [String: Any] = [:] + + for key in container.allKeys { + if let val = try? container.decode(Int.self, forKey: key) { + result[key.stringValue] = val + } else if let val = try? container.decode(Double.self, forKey: key) { + result[key.stringValue] = val + } else if let val = try? container.decode(String.self, forKey: key) { + result[key.stringValue] = val + } else if let val = try? container.decode(Bool.self, forKey: key) { + result[key.stringValue] = val + } else if let nestedContainer = try? container.nestedContainer( + keyedBy: JSONCodingKeys.self, + forKey: key + ) { + result[key.stringValue] = decode(fromObject: nestedContainer) + } else if var nestedArray = try? container.nestedUnkeyedContainer(forKey: key) { + result[key.stringValue] = decode(fromArray: &nestedArray) + } else if (try? container.decodeNil(forKey: key)) == true { + result.updateValue(Any?(nil) as Any, forKey: key.stringValue) + } + } + + return result + } + + func decode(fromArray container: inout UnkeyedDecodingContainer) -> [Any] { + var result: [Any] = [] + + while !container.isAtEnd { + if let value = try? container.decode(String.self) { + result.append(value) + } else if let value = try? container.decode(Int.self) { + result.append(value) + } else if let value = try? container.decode(Double.self) { + result.append(value) + } else if let value = try? container.decode(Bool.self) { + result.append(value) + } else if let nestedContainer = try? container.nestedContainer(keyedBy: JSONCodingKeys.self) { + result.append(decode(fromObject: nestedContainer)) + } else if var nestedArray = try? container.nestedUnkeyedContainer() { + result.append(decode(fromArray: &nestedArray)) + } else if (try? container.decodeNil()) == true { + result.append(Any?(nil) as Any) + } + } + + return result + } +} diff --git a/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift b/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift index a30b1ddbb..47c451e1b 100644 --- a/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift +++ b/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift @@ -26,9 +26,10 @@ private let functions: [String: Function] = { var result = [String: Function]() DatetimeFunctions.allCases.forEach { result[$0.rawValue] = $0.function } ColorFunctions.allCases.forEach { result[$0.rawValue] = $0.function } - StringFunctions.allCases.forEach { result[$0.rawValue] = $0.function} - CastFunctions.allCases.forEach { result[$0.rawValue] = $0.function } + StringFunctions.allCases.forEach { result[$0.rawValue] = $0.function } + CastFunctions.allCases.forEach { result[$0.rawValue] = $0.function } MathFunctions.allCases.forEach { result[$0.rawValue] = $0.function } + DictFunctions.allCases.forEach { result[$0.rawValue] = $0.function } IntervalFunctions.allCases.forEach { result[$0.rawValue] = $0.function } ValueFunctions.allCases.forEach { result[$0.rawValue] = $0.getFunction(resolver: ExpressionResolver(variables: [:])) diff --git a/schema/div-variable.json b/schema/div-variable.json index e86e34905..c60a62e94 100644 --- a/schema/div-variable.json +++ b/schema/div-variable.json @@ -202,7 +202,10 @@ }, { "$ref": "#/definitions/dict_variable", - "platforms": [] + "platforms": [ + "web", + "ios" + ] } ] } diff --git a/test_data/expression_test_data/function_signatures_std.json b/test_data/expression_test_data/function_signatures_std.json index d2c27eedb..92e960978 100644 --- a/test_data/expression_test_data/function_signatures_std.json +++ b/test_data/expression_test_data/function_signatures_std.json @@ -215,7 +215,8 @@ ], "result_type": "color", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -230,7 +231,8 @@ ], "result_type": "url", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -416,7 +418,8 @@ ], "result_type": "string", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -436,7 +439,8 @@ ], "result_type": "number", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -456,7 +460,8 @@ ], "result_type": "integer", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -476,7 +481,8 @@ ], "result_type": "boolean", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -496,7 +502,8 @@ ], "result_type": "color", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -516,7 +523,8 @@ ], "result_type": "url", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -540,7 +548,8 @@ ], "result_type": "string", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -564,7 +573,8 @@ ], "result_type": "number", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -588,7 +598,8 @@ ], "result_type": "integer", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -612,7 +623,8 @@ ], "result_type": "boolean", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -636,7 +648,8 @@ ], "result_type": "color", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -660,7 +673,8 @@ ], "result_type": "color", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -684,7 +698,8 @@ ], "result_type": "url", "platforms": [ - "web" + "web", + "ios" ] }, { @@ -708,7 +723,8 @@ ], "result_type": "url", "platforms": [ - "web" + "web", + "ios" ] } ] diff --git a/test_data/expression_test_data/functions_dict.json b/test_data/expression_test_data/functions_dict.json index 6565d6768..20e20c20b 100644 --- a/test_data/expression_test_data/functions_dict.json +++ b/test_data/expression_test_data/functions_dict.json @@ -37,7 +37,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -57,7 +58,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -79,7 +81,33 @@ } ], "platforms": [ - "web" + "web", + "ios" + ] + }, + { + "name": "getDictString(dict, 'prop', 'prop2', 'prop3') => 'val'", + "expression": "@{getDictString(dict, 'prop', 'prop2', 'prop3')}", + "expected": { + "type": "string", + "value": "val" + }, + "variables": [ + { + "type": "dict", + "name": "dict", + "value": { + "prop": { + "prop2": { + "prop3": "val" + } + } + } + } + ], + "platforms": [ + "web", + "ios" ] }, { @@ -101,7 +129,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -121,7 +150,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -143,7 +173,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -163,7 +194,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -183,7 +215,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -203,7 +236,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -223,7 +257,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -243,7 +278,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -263,7 +299,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -283,7 +320,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -303,7 +341,29 @@ } ], "platforms": [ - "web" + "web", + "ios" + ] + }, + { + "name": "getDictNumber(dict, 'prop') => error (2)", + "expression": "@{getDictNumber(dict, 'prop')}", + "expected": { + "type": "error", + "value": "Failed to evaluate [getDictNumber(, 'prop')]. Incorrect value type: expected \"number\", got \"string\"." + }, + "variables": [ + { + "type": "dict", + "name": "dict", + "value": { + "prop": "val" + } + } + ], + "platforms": [ + "web", + "ios" ] }, { @@ -323,7 +383,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -343,7 +404,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -363,7 +425,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -383,7 +446,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -398,12 +462,13 @@ "type": "dict", "name": "dict", "value": { - "prop": 9223372036854775808 + "prop": 9223372036854775808000000000000000000000 } } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -423,7 +488,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -443,7 +509,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -463,7 +530,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -483,7 +551,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -503,7 +572,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -523,7 +593,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -543,7 +614,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -563,7 +635,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -583,7 +656,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -603,7 +677,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -625,7 +700,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -645,7 +721,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -667,7 +744,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -687,7 +765,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -707,7 +786,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -727,7 +807,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -747,7 +828,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -767,7 +849,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -787,7 +870,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -807,7 +891,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -827,7 +912,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -847,7 +933,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -867,7 +954,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -882,12 +970,13 @@ "type": "dict", "name": "dict", "value": { - "prop": 9223372036854775808 + "prop": 922337203685477580800 } } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -907,7 +996,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -927,7 +1017,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -947,7 +1038,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -967,7 +1059,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -987,7 +1080,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1007,7 +1101,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1027,7 +1122,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1047,7 +1143,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1067,7 +1164,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1087,7 +1185,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1107,7 +1206,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -1127,7 +1227,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] } ] diff --git a/test_data/expression_test_data/functions_to_color.json b/test_data/expression_test_data/functions_to_color.json index 2d06ce60e..4014fd8d9 100644 --- a/test_data/expression_test_data/functions_to_color.json +++ b/test_data/expression_test_data/functions_to_color.json @@ -9,7 +9,8 @@ }, "variables": [], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -21,7 +22,8 @@ }, "variables": [], "platforms": [ - "web" + "web", + "ios" ] } ] diff --git a/test_data/expression_test_data/functions_to_url.json b/test_data/expression_test_data/functions_to_url.json index 799b542a7..73af91067 100644 --- a/test_data/expression_test_data/functions_to_url.json +++ b/test_data/expression_test_data/functions_to_url.json @@ -9,7 +9,8 @@ }, "variables": [], "platforms": [ - "web" + "web", + "ios" ] } ]