From 191ebf0047fff414be1ade2dfc98c3247ebd9b6e Mon Sep 17 00:00:00 2001 From: morevsavva Date: Sun, 26 Mar 2023 20:58:24 +0300 Subject: [PATCH] add getValue function --- .../Expressions/ExpressionResolver.swift | 9 +- .../Functions/ValueFunctions.swift | 117 ++++++++++++++++++ .../ExpressionResolvingTests.swift | 6 + .../Expressions/FunctionSignaturesTests.swift | 4 +- .../function_signatures_std.json | 16 +-- .../functions_variables.json | 85 ++++++++----- 6 files changed, 196 insertions(+), 41 deletions(-) create mode 100644 client/ios/DivKit/Expressions/Functions/ValueFunctions.swift diff --git a/client/ios/DivKit/Expressions/ExpressionResolver.swift b/client/ios/DivKit/Expressions/ExpressionResolver.swift index c248381b2..8a2d405b3 100644 --- a/client/ios/DivKit/Expressions/ExpressionResolver.swift +++ b/client/ios/DivKit/Expressions/ExpressionResolver.swift @@ -129,7 +129,7 @@ public final class ExpressionResolver { let result: T = try AnyCalcExpression( parsedExpression, constants: self, - symbols: supportedFunctions + symbols: getSupportedFunctions(resolver: self) ).evaluate() return validatedValue(value: result, validator: link.validator, rawValue: link.rawValue) } catch let error as CalcExpression.Error { @@ -161,7 +161,7 @@ public final class ExpressionResolver { let value: String = try AnyCalcExpression( parsedExpression, constants: self, - symbols: supportedFunctions + symbols: getSupportedFunctions(resolver: self) ).evaluate() stringValue += value } catch let error as CalcExpression.Error { @@ -255,13 +255,16 @@ extension ExpressionResolver: ConstantsProvider { } } -private let supportedFunctions: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] = +private func getSupportedFunctions(resolver: ExpressionResolver) + -> [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { CastFunctions.allCases.map(\.declaration).flat() + StringFunctions.allCases.map(\.declaration).flat() + ColorFunctions.allCases.map(\.declaration).flat() + DatetimeFunctions.allCases.map(\.declaration).flat() + MathFunctions.allCases.map(\.declaration).flat() + IntervalFunctions.allCases.map(\.declaration).flat() + + ValueFunctions.allCases.map { $0.getDeclaration(resolver: resolver) }.flat() +} extension Array where Element == [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { fileprivate func flat() -> [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { diff --git a/client/ios/DivKit/Expressions/Functions/ValueFunctions.swift b/client/ios/DivKit/Expressions/Functions/ValueFunctions.swift new file mode 100644 index 000000000..0be7204df --- /dev/null +++ b/client/ios/DivKit/Expressions/Functions/ValueFunctions.swift @@ -0,0 +1,117 @@ +import BaseTiny +import Foundation + +fileprivate typealias GetOrDefaultWithTransform = (String, U) throws -> T +fileprivate typealias GetOrDefault = GetOrDefaultWithTransform + +enum ValueFunctions: String, CaseIterable { + case getIntegerValue + case getNumberValue + case getStringValue + case getUrlValue + case getColorValue + case getBooleanValue + + func getDeclaration(resolver: ExpressionResolver) + -> [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] { + [ + .function( + rawValue, + arity: getFunction(resolver: resolver).arity + ): getFunction(resolver: resolver) + .symbolEvaluator, + ] + } + + func getFunction(resolver: ExpressionResolver) -> Function { + switch self { + case .getIntegerValue: + let function: GetOrDefault = resolver.getValueFunction() + return FunctionBinary(impl: function) + case .getNumberValue: + let function: GetOrDefault = resolver.getValueFunction() + return FunctionBinary(impl: function) + case .getStringValue: + let function: GetOrDefault = resolver.getValueFunction() + return FunctionBinary(impl: function) + case .getUrlValue: + let fromUrlFunction: GetOrDefault = resolver.getValueFunction() + let fromStringFunction: GetOrDefaultWithTransform = resolver + .getValueFunctionWithTransform { + guard let url = URL(string: $0) else { + throw AnyCalcExpression.Error.toURL($0) + } + return url + } + return OverloadedFunction(functions: [ + FunctionBinary(impl: fromUrlFunction), + FunctionBinary(impl: fromStringFunction), + ]) + case .getColorValue: + let fromColorFunction: GetOrDefault = resolver.getValueFunction() + let fromStringFunction: GetOrDefaultWithTransform = resolver + .getValueFunctionWithTransform { + guard let color = Color.color(withHexString: $0) else { + throw AnyCalcExpression.Error.toColor($0) + } + return color + } + return OverloadedFunction(functions: [ + FunctionBinary(impl: fromColorFunction), + FunctionBinary(impl: fromStringFunction), + ]) + case .getBooleanValue: + let function: GetOrDefault = resolver.getValueFunction() + return FunctionBinary(impl: function) + } + } +} + +extension ExpressionResolver { + fileprivate func getValueFunction() -> GetOrDefault { + { name, fallbackValue in + guard let value = self.getValue(name) else { + return fallbackValue + } + let typpedValue: T? = value as? T + if typpedValue == nil { + DivKitLogger.warning("The type of the variable \(name) is not \(T.self)") + } + return typpedValue ?? fallbackValue + } + } + + fileprivate func getValueFunctionWithTransform< + T, + U + >(transform: @escaping (U) throws -> T) -> GetOrDefaultWithTransform { + { name, fallbackValue in + guard let value = self.getValue(name) else { + return try transform(fallbackValue) + } + let typpedValue: T? = value as? T + if typpedValue == nil { + DivKitLogger.warning("The type of the variable \(name) is not \(T.self)") + } + return try typpedValue ?? transform(fallbackValue) + } + } +} + +extension AnyCalcExpression.Error { + fileprivate static func toColor(_ value: Any?) -> AnyCalcExpression.Error { + .message( + "Failed to get Color from (\(formatValue(value))). Unable to convert value to Color." + ) + } + + fileprivate static func toURL(_ value: Any?) -> AnyCalcExpression.Error { + .message( + "Failed to get URL from (\(formatValue(value))). Unable to convert value to URL." + ) + } + + private static func formatValue(_ value: Any?) -> String { + (value as? CustomStringConvertible)?.description ?? "" + } +} diff --git a/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift b/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift index c98ae31e8..81b4dc93c 100644 --- a/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift +++ b/client/ios/DivKitTests/Expressions/ExpressionResolvingTests.swift @@ -271,6 +271,12 @@ final class ExpressionResolvingTests: XCTestCase { perform(on: testCases, type: type) } + func test_functions_variables() throws { + let testCases = try makeTestCases(for: "functions_variables") + let type: ExpressionType = .stringBased(initializer: { $0 }) + perform(on: testCases, type: type) + } + private func perform(on testCases: TestCases, type: ExpressionType) { testCases.cases.filter { $0.platforms.contains(.ios) }.forEach { testCase = $0 diff --git a/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift b/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift index 01a902e6b..80b14c403 100644 --- a/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift +++ b/client/ios/DivKitTests/Expressions/FunctionSignaturesTests.swift @@ -28,7 +28,9 @@ private func findFunction(name: String) -> Function? { StringFunctions.allCases.first { $0.rawValue == name }?.function ?? CastFunctions.allCases.first { $0.rawValue == name }?.function ?? MathFunctions.allCases.first { $0.rawValue == name }?.function ?? - IntervalFunctions.allCases.first { $0.rawValue == name }?.function + IntervalFunctions.allCases.first { $0.rawValue == name }?.function ?? + ValueFunctions.allCases.first { $0.rawValue == name }? + .getFunction(resolver: ExpressionResolver(variables: [:])) } private var signatures: [SuiteDTO.TestDTO] { diff --git a/test_data/expression_test_data/function_signatures_std.json b/test_data/expression_test_data/function_signatures_std.json index 39b735170..3c50c780e 100644 --- a/test_data/expression_test_data/function_signatures_std.json +++ b/test_data/expression_test_data/function_signatures_std.json @@ -197,7 +197,7 @@ } ], "result_type": "integer", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] }, { "name": "getNumberValue(string, number) number", @@ -214,7 +214,7 @@ } ], "result_type": "number", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] }, { "name": "getStringValue(string, string) string", @@ -231,7 +231,7 @@ } ], "result_type": "string", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] }, { "name": "getUrlValue(string, url) url", @@ -248,7 +248,7 @@ } ], "result_type": "url", - "platforms": ["web"] + "platforms": ["web", "ios"] }, { "name": "getUrlValue(string, string) url", @@ -265,7 +265,7 @@ } ], "result_type": "url", - "platforms": ["web"] + "platforms": ["web", "ios"] }, { "name": "getColorValue(string, color) color", @@ -282,7 +282,7 @@ } ], "result_type": "color", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] }, { "name": "getColorValue(string, string) color", @@ -299,7 +299,7 @@ } ], "result_type": "color", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] }, { "name": "getBooleanValue(string, boolean) boolean", @@ -316,7 +316,7 @@ } ], "result_type": "boolean", - "platforms": [ "android", "web" ] + "platforms": [ "android", "web", "ios" ] } ] } diff --git a/test_data/expression_test_data/functions_variables.json b/test_data/expression_test_data/functions_variables.json index 74274da9c..d1496d3cc 100644 --- a/test_data/expression_test_data/functions_variables.json +++ b/test_data/expression_test_data/functions_variables.json @@ -16,7 +16,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -35,7 +36,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -48,7 +50,8 @@ "variables": [], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -67,7 +70,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -86,7 +90,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -99,7 +104,8 @@ "variables": [], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -118,7 +124,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -137,7 +144,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -150,7 +158,8 @@ "variables": [], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -169,7 +178,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -188,7 +198,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -201,7 +212,8 @@ "variables": [], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -225,7 +237,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -249,7 +262,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -268,7 +282,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -287,7 +302,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -306,7 +322,8 @@ ], "platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -318,7 +335,8 @@ }, "variables": [], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -335,9 +353,10 @@ "value": "#000" } ], - "platforms": [ +"platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -355,7 +374,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -366,9 +386,10 @@ "value": "#FFFFFFFF" }, "variables": [], - "platforms": [ +"platforms": [ "android", - "web" + "web", + "ios" ] }, { @@ -391,7 +412,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -414,7 +436,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -432,7 +455,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -450,7 +474,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -468,7 +493,8 @@ } ], "platforms": [ - "web" + "web", + "ios" ] }, { @@ -480,7 +506,8 @@ }, "variables": [], "platforms": [ - "web" + "web", + "ios" ] } ]