From f662c673f263343042f1ee99348c80d7a457bc1d Mon Sep 17 00:00:00 2001 From: pkurchatov Date: Mon, 15 Apr 2024 11:58:29 +0300 Subject: [PATCH] Added len() function for arrays, enabled iOS tests 5fb7f1116ec1dbe94f92996969da1f4f6913201a --- .mapping.json | 1 - .../CalcExpression+Stringify.swift | 2 +- .../CalcExpression/CalcExpression.swift | 2 +- .../Functions/ArrayFunctions.swift | 235 ++++++++---------- .../Expressions/Functions/DictFunctions.swift | 233 ++++++++--------- .../Expressions/Functions/Function.swift | 2 +- .../Expressions/Functions/GetMethods.swift | 23 -- .../Functions/StringFunctions.swift | 204 ++++++--------- .../Functions/ToStringFunctions.swift | 41 +-- .../Expressions/FunctionsProvider.swift | 37 ++- .../function_signatures_array.json | 3 +- .../expression_test_data/functions_array.json | 9 +- .../expression_test_data/functions_dict.json | 30 +-- .../functions_dict_legacy.json | 20 -- .../functions_to_boolean.json | 2 + .../functions_to_integer.json | 1 + .../functions_to_number.json | 2 + .../functions_to_string.json | 3 +- .../functions_unsupported.json | 19 ++ 19 files changed, 376 insertions(+), 493 deletions(-) delete mode 100644 client/ios/DivKit/Expressions/Functions/GetMethods.swift diff --git a/.mapping.json b/.mapping.json index 60756499f..c3f2b54ae 100644 --- a/.mapping.json +++ b/.mapping.json @@ -9253,7 +9253,6 @@ "client/ios/DivKit/Expressions/Functions/DictFunctions.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/DictFunctions.swift", "client/ios/DivKit/Expressions/Functions/EqualityOperators.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/EqualityOperators.swift", "client/ios/DivKit/Expressions/Functions/Function.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/Function.swift", - "client/ios/DivKit/Expressions/Functions/GetMethods.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/GetMethods.swift", "client/ios/DivKit/Expressions/Functions/GetStoredValueFunctions.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/GetStoredValueFunctions.swift", "client/ios/DivKit/Expressions/Functions/GetValueFunctions.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/GetValueFunctions.swift", "client/ios/DivKit/Expressions/Functions/IntervalFunctions.swift":"divkit/public/client/ios/DivKit/Expressions/Functions/IntervalFunctions.swift", diff --git a/client/ios/DivKit/Expressions/CalcExpression/CalcExpression+Stringify.swift b/client/ios/DivKit/Expressions/CalcExpression/CalcExpression+Stringify.swift index 63c96214e..670f5dbee 100644 --- a/client/ios/DivKit/Expressions/CalcExpression/CalcExpression+Stringify.swift +++ b/client/ios/DivKit/Expressions/CalcExpression/CalcExpression+Stringify.swift @@ -7,7 +7,7 @@ extension CalcExpression { static func stringify(_ value: Any) -> String { switch value { case let bool as Bool: - return bool ? "true" : "false" + return bool.description case let int as Int: return String(int) case let double as Double: diff --git a/client/ios/DivKit/Expressions/CalcExpression/CalcExpression.swift b/client/ios/DivKit/Expressions/CalcExpression/CalcExpression.swift index 70c2717a5..74c46bc67 100644 --- a/client/ios/DivKit/Expressions/CalcExpression/CalcExpression.swift +++ b/client/ios/DivKit/Expressions/CalcExpression/CalcExpression.swift @@ -544,7 +544,7 @@ extension UnicodeScalarView { if scanCharacter(".") { guard let fraction = scanInteger() else { self = endOfInt - return .number(sign + integer) + return .integer(sign + integer) } number = .number("\(sign)\(integer).\(fraction)") } else { diff --git a/client/ios/DivKit/Expressions/Functions/ArrayFunctions.swift b/client/ios/DivKit/Expressions/Functions/ArrayFunctions.swift index 560b6b674..4a84dd8b9 100644 --- a/client/ios/DivKit/Expressions/Functions/ArrayFunctions.swift +++ b/client/ios/DivKit/Expressions/Functions/ArrayFunctions.swift @@ -6,29 +6,42 @@ private typealias Dict = [String: AnyHashable] extension [String: Function] { mutating func addArrayFunctions() { - self["getArrayFromArray"] = ArrayFunctions.getArray() - self["getOptArrayFromArray"] = getOptArrayFunction() + addFunction("getArrayFromArray", _getArray) + addFunction("getOptArrayFromArray", _getOptArray) - self["getDictFromArray"] = ArrayFunctions.getDict() - self["getOptDictFromArray"] = getOptDictFunction() + addFunction("getDictFromArray", _getDict) + addFunction("getOptDictFromArray", _getOptDict) - addFunctions("Boolean", ArrayFunctions.getBoolean()) - addFunctions("OptBoolean", getOptBooleanFunction()) + addFunctions("Boolean", _getBoolean) + addFunctions("OptBoolean", _getOptBoolean) - addFunctions("Color", ArrayFunctions.getColor()) - addFunctions("OptColor", getOptColorFunction()) + addFunctions("Color", _getColor) + addFunctions("OptColor", _getOptColor) - addFunctions("Integer", ArrayFunctions.getInteger()) - addFunctions("OptInteger", getOptIntegerFunction()) + addFunctions("Integer", _getInteger) + addFunctions("OptInteger", _getOptInteger) - addFunctions("Number", ArrayFunctions.getNumber()) - addFunctions("OptNumber", getOptNumberFunction()) + addFunctions("Number", _getNumber) + addFunctions("OptNumber", _getOptNumber) - addFunctions("String", ArrayFunctions.getString()) - addFunctions("OptString", getOptStringFunction()) + addFunctions("String", _getString) + addFunctions("OptString", _getOptString) - addFunctions("Url", ArrayFunctions.getUrl()) - addFunctions("OptUrl", getOptUrlFunction()) + addFunctions("Url", _getUrl) + addFunctions("OptUrl", _getOptUrl) + + addFunction("len", FunctionUnary<[AnyHashable], Int> { $0.count }) + } + + mutating func addArrayMethods() { + addFunction("getArray", _getArray) + addFunction("getBoolean", _getBoolean) + addFunction("getColor", _getColor) + addFunction("getDict", _getDict) + addFunction("getInteger", _getInteger) + addFunction("getNumber", _getNumber) + addFunction("getString", _getString) + addFunction("getUrl", _getUrl) } private mutating func addFunctions( @@ -40,123 +53,85 @@ extension [String: Function] { } } -enum ArrayFunctions { - static func getArray() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, [AnyHashable]> { - try $0.getArray(index: $1) +private var _getArray = FunctionBinary<[AnyHashable], Int, [AnyHashable]> { + try $0.getArray(index: $1) +} + +private var _getBoolean = FunctionBinary<[AnyHashable], Int, Bool> { + try $0.getBoolean(index: $1) +} + +private var _getColor = FunctionBinary<[AnyHashable], Int, Color> { + try $0.getColor(index: $1) +} + +private var _getDict = FunctionBinary<[AnyHashable], Int, Dict> { + try $0.getDict(index: $1) +} + +private var _getInteger = FunctionBinary<[AnyHashable], Int, Int> { + try $0.getInteger(index: $1) +} + +private var _getNumber = FunctionBinary<[AnyHashable], Int, Double> { + try $0.getNumber(index: $1) +} + +private var _getString = FunctionBinary<[AnyHashable], Int, String> { + try $0.getString(index: $1) +} + +private var _getUrl = FunctionBinary<[AnyHashable], Int, URL> { + try $0.getUrl(index: $1) +} + +private var _getOptArray = FunctionBinary<[AnyHashable], Int, [AnyHashable]> { + (try? $0.getArray(index: $1)) ?? [] +} + +private var _getOptBoolean = FunctionTernary<[AnyHashable], Int, Bool, Bool> { + (try? $0.getBoolean(index: $1)) ?? $2 +} + +private var _getOptColor = OverloadedFunction(functions: [ + FunctionTernary<[AnyHashable], Int, Color, Color> { + (try? $0.getColor(index: $1)) ?? $2 + }, + FunctionTernary<[AnyHashable], Int, String, Color> { + if let value = try? $0.getColor(index: $1) { + return value } - } + return Color.color(withHexString: $2)! + }, +]) - static func getBoolean() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, Bool> { - try $0.getBoolean(index: $1) +private var _getOptDict = FunctionBinary<[AnyHashable], Int, Dict> { + (try? $0.getDict(index: $1)) ?? [:] +} + +private var _getOptInteger = FunctionTernary<[AnyHashable], Int, Int, Int> { + (try? $0.getInteger(index: $1)) ?? $2 +} + +private var _getOptNumber = FunctionTernary<[AnyHashable], Int, Double, Double> { + (try? $0.getNumber(index: $1)) ?? $2 +} + +private var _getOptString = FunctionTernary<[AnyHashable], Int, String, String> { + (try? $0.getString(index: $1)) ?? $2 +} + +private var _getOptUrl = OverloadedFunction(functions: [ + FunctionTernary<[AnyHashable], Int, URL, URL> { + (try? $0.getUrl(index: $1)) ?? $2 + }, + FunctionTernary<[AnyHashable], Int, String, URL> { + if let value = try? $0.getUrl(index: $1) { + return value } - } - - static func getColor() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, Color> { - try $0.getColor(index: $1) - } - } - - static func getDict() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, Dict> { - try $0.getDict(index: $1) - } - } - - static func getInteger() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, Int> { - try $0.getInteger(index: $1) - } - } - - static func getNumber() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, Double> { - try $0.getNumber(index: $1) - } - } - - static func getString() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, String> { - try $0.getString(index: $1) - } - } - - static func getUrl() -> SimpleFunction { - FunctionBinary<[AnyHashable], Int, URL> { - try $0.getUrl(index: $1) - } - } -} - -private func getOptArrayFunction() -> Function { - FunctionBinary<[AnyHashable], Int, [AnyHashable]> { array, index in - (try? array.getArray(index: index)) ?? [] - } -} - -private func getOptBooleanFunction() -> Function { - FunctionTernary<[AnyHashable], Int, Bool, Bool> { array, index, fallback in - (try? array.getBoolean(index: index)) ?? fallback - } -} - -private func getOptColorFunction() -> Function { - OverloadedFunction( - functions: [ - FunctionTernary<[AnyHashable], Int, Color, Color> { array, index, fallback in - (try? array.getColor(index: index)) ?? fallback - }, - FunctionTernary<[AnyHashable], Int, String, Color> { array, index, fallback in - if let value = try? array.getColor(index: index) { - return value - } - return Color.color(withHexString: fallback)! - }, - ] - ) -} - -private func getOptDictFunction() -> Function { - FunctionBinary<[AnyHashable], Int, Dict> { array, index in - (try? array.getDict(index: index)) ?? [:] - } -} - -private func getOptIntegerFunction() -> Function { - FunctionTernary<[AnyHashable], Int, Int, Int> { array, index, fallback in - (try? array.getInteger(index: index)) ?? fallback - } -} - -private func getOptNumberFunction() -> Function { - FunctionTernary<[AnyHashable], Int, Double, Double> { array, index, fallback in - (try? array.getNumber(index: index)) ?? fallback - } -} - -private func getOptStringFunction() -> Function { - FunctionTernary<[AnyHashable], Int, String, String> { array, index, fallback in - (try? array.getString(index: index)) ?? fallback - } -} - -private func getOptUrlFunction() -> Function { - OverloadedFunction( - functions: [ - FunctionTernary<[AnyHashable], Int, URL, URL> { array, index, fallback in - (try? array.getUrl(index: index)) ?? fallback - }, - FunctionTernary<[AnyHashable], Int, String, URL> { array, index, fallback in - if let value = try? array.getUrl(index: index) { - return value - } - return URL(string: fallback)! - }, - ] - ) -} + return URL(string: $2)! + }, +]) extension [AnyHashable] { fileprivate func getArray(index: Int) throws -> [AnyHashable] { diff --git a/client/ios/DivKit/Expressions/Functions/DictFunctions.swift b/client/ios/DivKit/Expressions/Functions/DictFunctions.swift index 6db275aac..f7384cbe6 100644 --- a/client/ios/DivKit/Expressions/Functions/DictFunctions.swift +++ b/client/ios/DivKit/Expressions/Functions/DictFunctions.swift @@ -6,29 +6,40 @@ private typealias Dict = [String: AnyHashable] extension [String: Function] { mutating func addDictFunctions() { - self["getArrayFromDict"] = DictFunctions.getArray() - self["getOptArrayFromDict"] = getOptArrayFunction() + addFunction("getArrayFromDict", _getArray) + addFunction("getOptArrayFromDict", _getOptArray) - self["getDictFromDict"] = DictFunctions.getDict() - self["getOptDictFromDict"] = getOptDictFunction() + addFunction("getDictFromDict", _getDict) + addFunction("getOptDictFromDict", _getOptDict) - addFunctions("Boolean", DictFunctions.getBoolean()) - addFunctions("OptBoolean", getOptBooleanFunction()) + addFunctions("Boolean", _getBoolean) + addFunctions("OptBoolean", _getOptBoolean) - addFunctions("Color", DictFunctions.getColor()) - addFunctions("OptColor", getOptColorFunction()) + addFunctions("Color", _getColor) + addFunctions("OptColor", _getOptColor) - addFunctions("Integer", DictFunctions.getInteger()) - addFunctions("OptInteger", getOptIntegerFunction()) + addFunctions("Integer", _getInteger) + addFunctions("OptInteger", _getOptInteger) - addFunctions("Number", DictFunctions.getNumber()) - addFunctions("OptNumber", getOptNumberFunction()) + addFunctions("Number", _getNumber) + addFunctions("OptNumber", _getOptNumber) - addFunctions("String", DictFunctions.getString()) - addFunctions("OptString", getOptStringFunction()) + addFunctions("String", _getString) + addFunctions("OptString", _getOptString) - addFunctions("Url", DictFunctions.getUrl()) - addFunctions("OptUrl", getOptUrlFunction()) + addFunctions("Url", _getUrl) + addFunctions("OptUrl", _getOptUrl) + } + + mutating func addDictMethods() { + addFunction("getArray", _getArray) + addFunction("getBoolean", _getBoolean) + addFunction("getColor", _getColor) + addFunction("getDict", _getDict) + addFunction("getInteger", _getInteger) + addFunction("getNumber", _getNumber) + addFunction("getString", _getString) + addFunction("getUrl", _getUrl) } private mutating func addFunctions( @@ -40,123 +51,85 @@ extension [String: Function] { } } -enum DictFunctions { - static func getArray() -> SimpleFunction { - FunctionVarBinary { - try $0.getArray(path: $1) +private var _getArray = FunctionVarBinary { + try $0.getArray(path: $1) +} + +private var _getBoolean = FunctionVarBinary { + try $0.getBoolean(path: $1) +} + +private var _getColor = FunctionVarBinary { + try $0.getColor(path: $1) +} + +private var _getDict = FunctionVarBinary { + try $0.getDict(path: $1) +} + +private var _getInteger = FunctionVarBinary { + try $0.getInteger(path: $1) +} + +private var _getNumber = FunctionVarBinary { + try $0.getNumber(path: $1) +} + +private var _getString = FunctionVarBinary { + try $0.getString(path: $1) +} + +private var _getUrl = FunctionVarBinary { + try $0.getUrl(path: $1) +} + +private var _getOptArray = FunctionVarBinary { + (try? $0.getArray(path: $1)) ?? [] +} + +private var _getOptBoolean = FunctionVarTernary { + (try? $1.getBoolean(path: $2)) ?? $0 +} + +private var _getOptColor = OverloadedFunction(functions: [ + FunctionVarTernary { + (try? $1.getColor(path: $2)) ?? $0 + }, + FunctionVarTernary { + if let value = try? $1.getColor(path: $2) { + return value } - } + return Color.color(withHexString: $0)! + }, +]) - static func getBoolean() -> SimpleFunction { - FunctionVarBinary { - try $0.getBoolean(path: $1) +private var _getOptDict = FunctionVarBinary { + (try? $0.getDict(path: $1)) ?? [:] +} + +private var _getOptInteger = FunctionVarTernary { + (try? $1.getInteger(path: $2)) ?? $0 +} + +private var _getOptNumber = FunctionVarTernary { + (try? $1.getNumber(path: $2)) ?? $0 +} + +private var _getOptString = FunctionVarTernary { + (try? $1.getString(path: $2)) ?? $0 +} + +private var _getOptUrl = OverloadedFunction(functions: [ + FunctionVarTernary { + (try? $1.getUrl(path: $2)) ?? $0 + }, + FunctionVarTernary { + if let value = try? $1.getUrl(path: $2) { + return value } - } - - static func getColor() -> SimpleFunction { - FunctionVarBinary { - try $0.getColor(path: $1) - } - } - - static func getDict() -> SimpleFunction { - FunctionVarBinary { - try $0.getDict(path: $1) - } - } - - static func getInteger() -> SimpleFunction { - FunctionVarBinary { - try $0.getInteger(path: $1) - } - } - - static func getNumber() -> SimpleFunction { - FunctionVarBinary { - try $0.getNumber(path: $1) - } - } - - static func getString() -> SimpleFunction { - FunctionVarBinary { - try $0.getString(path: $1) - } - } - - static func getUrl() -> SimpleFunction { - FunctionVarBinary { - try $0.getUrl(path: $1) - } - } -} - -private func getOptArrayFunction() -> Function { - FunctionVarBinary { dict, path in - (try? dict.getArray(path: path)) ?? [] - } -} - -private func getOptBooleanFunction() -> Function { - FunctionVarTernary { fallback, dict, path in - (try? dict.getBoolean(path: path)) ?? fallback - } -} - -private func getOptColorFunction() -> Function { - OverloadedFunction( - functions: [ - FunctionVarTernary { fallback, dict, path in - (try? dict.getColor(path: path)) ?? fallback - }, - FunctionVarTernary { fallback, dict, path in - if let value = try? dict.getColor(path: path) { - return value - } - return Color.color(withHexString: fallback)! - }, - ] - ) -} - -private func getOptDictFunction() -> Function { - FunctionVarBinary { dict, path in - (try? dict.getDict(path: path)) ?? [:] - } -} - -private func getOptIntegerFunction() -> Function { - FunctionVarTernary { fallback, dict, path in - (try? dict.getInteger(path: path)) ?? fallback - } -} - -private func getOptNumberFunction() -> Function { - FunctionVarTernary { fallback, dict, path in - (try? dict.getNumber(path: path)) ?? fallback - } -} - -private func getOptStringFunction() -> Function { - FunctionVarTernary { fallback, dict, path in - (try? dict.getString(path: path)) ?? fallback - } -} - -private func getOptUrlFunction() -> Function { - OverloadedFunction( - functions: [ - FunctionVarTernary { fallback, dict, path in - (try? dict.getUrl(path: path)) ?? fallback - }, - FunctionVarTernary { fallback, dict, path in - if let value = try? dict.getUrl(path: path) { - return value - } - return URL(string: fallback)! - }, - ] - ) -} + return URL(string: $0)! + }, +]) extension Dict { fileprivate func getArray(path: [String]) throws -> [AnyHashable] { diff --git a/client/ios/DivKit/Expressions/Functions/Function.swift b/client/ios/DivKit/Expressions/Functions/Function.swift index f802f8165..a35d31041 100644 --- a/client/ios/DivKit/Expressions/Functions/Function.swift +++ b/client/ios/DivKit/Expressions/Functions/Function.swift @@ -232,7 +232,7 @@ struct FunctionVarTernary: SimpleFunction { } struct OverloadedFunction: Function { - private let functions: [SimpleFunction] + let functions: [SimpleFunction] private let makeError: ([Argument]) -> Error init(functions: [SimpleFunction], makeError: (([Argument]) -> Error)? = nil) { diff --git a/client/ios/DivKit/Expressions/Functions/GetMethods.swift b/client/ios/DivKit/Expressions/Functions/GetMethods.swift deleted file mode 100644 index 11c7cac9c..000000000 --- a/client/ios/DivKit/Expressions/Functions/GetMethods.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -extension [String: Function] { - mutating func addGetMethods() { - addMethod("getArray", [ArrayFunctions.getArray, DictFunctions.getArray]) - addMethod("getBoolean", [ArrayFunctions.getBoolean, DictFunctions.getBoolean]) - addMethod("getColor", [ArrayFunctions.getColor, DictFunctions.getColor]) - addMethod("getDict", [ArrayFunctions.getDict, DictFunctions.getDict]) - addMethod("getInteger", [ArrayFunctions.getInteger, DictFunctions.getInteger]) - addMethod("getNumber", [ArrayFunctions.getNumber, DictFunctions.getNumber]) - addMethod("getString", [ArrayFunctions.getString, DictFunctions.getString]) - addMethod("getUrl", [ArrayFunctions.getUrl, DictFunctions.getUrl]) - } - - private mutating func addMethod( - _ name: String, - _ functionFactories: [() -> SimpleFunction] - ) { - self[name] = OverloadedFunction( - functions: functionFactories.map { $0() } - ) - } -} diff --git a/client/ios/DivKit/Expressions/Functions/StringFunctions.swift b/client/ios/DivKit/Expressions/Functions/StringFunctions.swift index 7055ba539..de45c710f 100644 --- a/client/ios/DivKit/Expressions/Functions/StringFunctions.swift +++ b/client/ios/DivKit/Expressions/Functions/StringFunctions.swift @@ -1,68 +1,23 @@ import Foundation -enum StringFunctions: String, CaseIterable { - case len - case contains - case substring - case replaceAll - case index - case lastIndex - case trim - case trimLeft - case trimRight - case toUpperCase - case toLowerCase - case encodeUri - case decodeUri - case padStart - case padEnd - case testRegex - - var function: Function { - switch self { - case .len: - FunctionUnary(impl: _len) - case .contains: - FunctionBinary(impl: _contains) - case .substring: - FunctionTernary(impl: _substring) - case .replaceAll: - FunctionTernary(impl: _replaceAll) - case .index: - FunctionBinary(impl: _index) - case .lastIndex: - FunctionBinary(impl: _lastIndex) - case .trim: - FunctionUnary(impl: _trim) - case .trimLeft: - FunctionUnary(impl: _trimLeft) - case .trimRight: - FunctionUnary(impl: _trimRight) - case .toUpperCase: - FunctionUnary(impl: _toUpperCase) - case .toLowerCase: - FunctionUnary(impl: _toLowerCase) - case .encodeUri: - FunctionUnary(impl: _encodeUri) - case .decodeUri: - FunctionUnary(impl: _decodeUri) - case .padStart: - OverloadedFunction( - functions: [ - FunctionTernary(impl: _padStart), - FunctionTernary(impl: _padStartInt), - ] - ) - case .padEnd: - OverloadedFunction( - functions: [ - FunctionTernary(impl: _padEnd), - FunctionTernary(impl: _padEndInt), - ] - ) - case .testRegex: - FunctionBinary(impl: _testRegex) - } +extension [String: Function] { + mutating func addStringFunctions() { + addFunction("contains", _contains) + addFunction("decodeUri", _decodeUri) + addFunction("encodeUri", _encodeUri) + addFunction("index", _index) + addFunction("len", _len) + addFunction("lastIndex", _lastIndex) + addFunction("padEnd", _padEnd) + addFunction("padStart", _padStart) + addFunction("replaceAll", _replaceAll) + addFunction("substring", _substring) + addFunction("testRegex", _testRegex) + addFunction("trim", _trim) + addFunction("trimLeft", _trimLeft) + addFunction("trimRight", _trimRight) + addFunction("toUpperCase", _toUpperCase) + addFunction("toLowerCase", _toLowerCase) } } @@ -71,46 +26,50 @@ private let dontNeedEncoding = CharacterSet(charactersIn: "a"..."z") .union(CharacterSet(charactersIn: "0"..."9")) .union(CharacterSet(charactersIn: "-_.*!~'()")) -private func _len(value: String) -> Int { - value.count +private var _len = FunctionUnary { + $0.count } -private func _contains(first: String, second: String) -> Bool { - guard !second.isEmpty else { return true } - return first.range(of: second) != nil +private var _contains = FunctionBinary { + $1.isEmpty || $0.range(of: $1) != nil } -private func _substring(first: String, second: Int, third: Int) throws -> String { - guard second <= third else { +private var _substring = FunctionTernary { + guard $1 <= $2 else { throw CalcExpression.Error.message("Indexes should be in ascending order.") } - guard second >= 0, third <= first.count else { + guard $1 >= 0, $2 <= $0.count else { throw CalcExpression.Error.message("Indexes are out of bounds.") } - return String(first[first.rangeOfCharsIn(second.. String { - first.replacingOccurrences(of: second, with: third) +private var _replaceAll = FunctionTernary { + $0.replacingOccurrences(of: $1, with: $2) } -private func _index(first: String, second: String) -> Int { - guard !second.isEmpty else { return 0 } - guard let range = first.range(of: second) else { return -1 } - return first.distance(to: range.lowerBound) +private var _index = FunctionBinary { + if $1.isEmpty { + return 0 + } + guard let range = $0.range(of: $1) else { + return -1 + } + return $0.distance(to: range.lowerBound) } -private func _lastIndex(first: String, second: String) -> Int { - guard let range = first.range(of: second, options: .backwards) - else { return -1 } - return first.distance(to: range.lowerBound) +private var _lastIndex = FunctionBinary { + guard let range = $0.range(of: $1, options: .backwards) else { + return -1 + } + return $0.distance(to: range.lowerBound) } -private func _trim(value: String) -> String { - value.trimmed +private var _trim = FunctionUnary { + $0.trimmed } -private func _trimLeft(value: String) -> String { +private var _trimLeft = FunctionUnary { value in for index in value.indices { if !value[index].isWhitespace { return String(value[index...]) @@ -119,7 +78,7 @@ private func _trimLeft(value: String) -> String { return "" } -private func _trimRight(value: String) -> String { +private var _trimRight = FunctionUnary { value in for index in value.indices.reversed() { if !value[index].isWhitespace { return String(value[...index]) @@ -128,47 +87,56 @@ private func _trimRight(value: String) -> String { return "" } -private func _toUpperCase(value: String) -> String { - value.uppercased() +private var _toUpperCase = FunctionUnary { + $0.uppercased() } -private func _toLowerCase(value: String) -> String { - value.lowercased() +private var _toLowerCase = FunctionUnary { + $0.lowercased() } -private func _encodeUri(value: String) throws -> String { - guard let encodedValue = value.addingPercentEncoding(withAllowedCharacters: dontNeedEncoding) - else { +private var _encodeUri = FunctionUnary { + guard let value = $0.addingPercentEncoding(withAllowedCharacters: dontNeedEncoding) else { throw CalcExpression.Error.message("String is empty after encoding.") } - return encodedValue + return value } -private func _decodeUri(value: String) throws -> String { - guard let decodedValue = value.removingPercentEncoding else { +private var _decodeUri = FunctionUnary { + guard let value = $0.removingPercentEncoding else { throw CalcExpression.Error.message("String is empty after decoding.") } - return decodedValue + return value } -private func _padStart(value: String, len: Int, pad: String) throws -> String { +private var _padStart = OverloadedFunction(functions: [ + FunctionTernary { + padStart(value: $0, len: $1, pad: $2) + }, + FunctionTernary { + padStart(value: String($0), len: $1, pad: $2) + }, +]) + +private var _padEnd = OverloadedFunction(functions: [ + FunctionTernary { + padEnd(value: $0, len: $1, pad: $2) + }, + FunctionTernary { + padEnd(value: String($0), len: $1, pad: $2) + }, +]) + +private func padStart(value: String, len: Int, pad: String) -> String { let prefix = calcPad(value: value, len: len, pad: pad) return prefix + value } -private func _padStartInt(value: Int, len: Int, pad: String) throws -> String { - try _padStart(value: String(value), len: len, pad: pad) -} - -private func _padEnd(value: String, len: Int, pad: String) throws -> String { +private func padEnd(value: String, len: Int, pad: String) -> String { let suffix = calcPad(value: value, len: len, pad: pad) return value + suffix } -private func _padEndInt(value: Int, len: Int, pad: String) throws -> String { - try _padEnd(value: String(value), len: len, pad: pad) -} - private func calcPad(value: String, len: Int, pad: String) -> String { var part = "" guard pad.count > 0 else { @@ -184,21 +152,7 @@ private func calcPad(value: String, len: Int, pad: String) -> String { return part } -private func cast(_ value: Any) -> String? { - value as? String -} - -private func castToInt(_ value: Any) -> Int? { - value as? Int -} - -extension String { - fileprivate func distance(to index: Index) -> Int { - distance(from: startIndex, to: index) - } -} - -private func _testRegex(text: String, regex: String) throws -> Bool { +private var _testRegex = FunctionBinary { text, regex in do { let regex = try NSRegularExpression(pattern: regex) let range = NSRange(text.startIndex..., in: text) @@ -207,3 +161,9 @@ private func _testRegex(text: String, regex: String) throws -> Bool { throw CalcExpression.Error.message("Invalid regular expression.") } } + +extension String { + fileprivate func distance(to index: Index) -> Int { + distance(from: startIndex, to: index) + } +} diff --git a/client/ios/DivKit/Expressions/Functions/ToStringFunctions.swift b/client/ios/DivKit/Expressions/Functions/ToStringFunctions.swift index f2dd54e64..597422266 100644 --- a/client/ios/DivKit/Expressions/Functions/ToStringFunctions.swift +++ b/client/ios/DivKit/Expressions/Functions/ToStringFunctions.swift @@ -5,39 +5,14 @@ import CommonCorePublic extension [String: Function] { mutating func addToStringFunctions() { self["toString"] = OverloadedFunction(functions: [ - arrayFunction, - boolFunction, - colorFunction, - dictFunction, - doubleFunction, - intFunction, - stringFunction, - urlFunction, + FunctionUnary<[AnyHashable], String> { CalcExpression.stringify($0) }, + FunctionUnary { $0.description }, + FunctionUnary { $0.argbString }, + FunctionUnary<[String: AnyHashable], String> { CalcExpression.stringify($0) }, + FunctionUnary { CalcExpression.stringify($0) }, + FunctionUnary { $0.description }, + FunctionUnary { $0 }, + FunctionUnary { $0.description }, ]) } } - -private let arrayFunction = FunctionUnary<[AnyHashable], String> { CalcExpression.stringify($0) } - -private let boolFunction = FunctionUnary { $0.description } - -private let colorFunction = FunctionUnary { $0.argbString } - -private let dictFunction = FunctionUnary<[String: AnyHashable], String> { CalcExpression.stringify($0) } - -private let doubleFunction = FunctionUnary { - let formatter = NumberFormatter() - formatter.minimumFractionDigits = 0 - formatter.maximumFractionDigits = 14 - formatter.decimalSeparator = "." - guard let string = formatter.string(from: NSNumber(value: $0)) else { - throw CalcExpression.Error.message("Unable to convert value to String.") - } - return string -} - -private let intFunction = FunctionUnary { $0.description } - -private let stringFunction = FunctionUnary { $0 } - -private let urlFunction = FunctionUnary { $0.description } diff --git a/client/ios/DivKit/Expressions/FunctionsProvider.swift b/client/ios/DivKit/Expressions/FunctionsProvider.swift index b2529eda4..b9045a60b 100644 --- a/client/ios/DivKit/Expressions/FunctionsProvider.swift +++ b/client/ios/DivKit/Expressions/FunctionsProvider.swift @@ -90,11 +90,10 @@ private func makeEvaluator( return try function.invoke(args: args) } catch let error as CalcExpression.Error { let message = "Failed to evaluate [\(symbol.formatExpression(args))]." - let correctedArgs: [Any] - if case .method = symbol { - correctedArgs = Array(args.dropFirst()) + let correctedArgs: [Any] = if case .method = symbol { + Array(args.dropFirst()) } else { - correctedArgs = args + args } if error == .noMatchingSignature { if correctedArgs.count == 0 { @@ -135,9 +134,9 @@ private let staticFunctions: [String: Function] = { DatetimeFunctions.allCases.forEach { functions[$0.rawValue] = $0.function } IntervalFunctions.allCases.forEach { functions[$0.rawValue] = $0.function } MathFunctions.allCases.forEach { functions[$0.rawValue] = $0.function } - StringFunctions.allCases.forEach { functions[$0.rawValue] = $0.function } functions.addArrayFunctions() functions.addDictFunctions() + functions.addStringFunctions() functions.addToStringFunctions() return functions }() @@ -153,7 +152,33 @@ private let operators: [CalcExpression.Symbol: Function] = { private let methods: [String: Function] = { var methods: [String: Function] = [:] - methods.addGetMethods() + methods.addArrayMethods() + methods.addDictMethods() methods.addToStringFunctions() return methods }() + +extension [String: Function] { + mutating func addFunction(_ name: String, _ function: Function) { + var functions: [SimpleFunction] = [] + if let existingFunction = self[name] { + functions.appendFunctions(existingFunction) + } + functions.appendFunctions(function) + if functions.count > 1 { + self[name] = OverloadedFunction(functions: functions) + } else if functions.count == 1 { + self[name] = functions[0] + } + } +} + +extension [SimpleFunction] { + fileprivate mutating func appendFunctions(_ function: Function) { + if let overloadedFunction = function as? OverloadedFunction { + append(contentsOf: overloadedFunction.functions) + } else if let simpleFunction = function as? SimpleFunction { + append(simpleFunction) + } + } +} diff --git a/test_data/expression_test_data/function_signatures_array.json b/test_data/expression_test_data/function_signatures_array.json index 6e5616fb3..2f7924e3a 100644 --- a/test_data/expression_test_data/function_signatures_array.json +++ b/test_data/expression_test_data/function_signatures_array.json @@ -749,7 +749,8 @@ "result_type": "integer", "platforms": [ "web", - "android" + "android", + "ios" ] } ] diff --git a/test_data/expression_test_data/functions_array.json b/test_data/expression_test_data/functions_array.json index f73213155..c18397a6b 100644 --- a/test_data/expression_test_data/functions_array.json +++ b/test_data/expression_test_data/functions_array.json @@ -885,7 +885,8 @@ ], "platforms": [ "web", - "android" + "android", + "ios" ] }, { @@ -906,7 +907,8 @@ ], "platforms": [ "web", - "android" + "android", + "ios" ] }, { @@ -929,7 +931,8 @@ ], "platforms": [ "web", - "android" + "android", + "ios" ] } ] diff --git a/test_data/expression_test_data/functions_dict.json b/test_data/expression_test_data/functions_dict.json index 9cff69ec5..2728e2c1c 100644 --- a/test_data/expression_test_data/functions_dict.json +++ b/test_data/expression_test_data/functions_dict.json @@ -1,25 +1,5 @@ { "cases": [ - { - "name": "unknownDictFunc(dict) => error", - "expression": "@{unknownDictFuncFromDict(dict)}", - "expected": { - "type": "error", - "value": "Failed to evaluate [unknownDictFuncFromDict()]. Unknown function name: unknownDictFuncFromDict." - }, - "variables": [ - { - "type": "dict", - "name": "dict", - "value": { - "prop": "val" - } - } - ], - "platforms": [ - "web" - ] - }, { "name": "getStringFromDict(dict, 'prop') => 'val'", "expression": "@{getStringFromDict(dict, 'prop')}", @@ -1310,6 +1290,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1329,6 +1310,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1350,6 +1332,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1371,6 +1354,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1390,6 +1374,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1411,6 +1396,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1432,6 +1418,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1452,6 +1439,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1477,6 +1465,7 @@ ], "platforms": [ "android", + "ios", "web" ] }, @@ -1497,6 +1486,7 @@ ], "platforms": [ "android", + "ios", "web" ] } diff --git a/test_data/expression_test_data/functions_dict_legacy.json b/test_data/expression_test_data/functions_dict_legacy.json index 6c0d006a2..c2964ac08 100644 --- a/test_data/expression_test_data/functions_dict_legacy.json +++ b/test_data/expression_test_data/functions_dict_legacy.json @@ -1,25 +1,5 @@ { "cases": [ - { - "name": "unknownDictFunc(dict) => error", - "expression": "@{unknownDictFunc(dict)}", - "expected": { - "type": "error", - "value": "Failed to evaluate [unknownDictFunc()]. Unknown function name: unknownDictFunc." - }, - "variables": [ - { - "type": "dict", - "name": "dict", - "value": { - "prop": "val" - } - } - ], - "platforms": [ - "web" - ] - }, { "name": "getDictString(dict, 'prop') => 'val'", "expression": "@{getDictString(dict, 'prop')}", diff --git a/test_data/expression_test_data/functions_to_boolean.json b/test_data/expression_test_data/functions_to_boolean.json index 2b9a85f9a..c702069d1 100644 --- a/test_data/expression_test_data/functions_to_boolean.json +++ b/test_data/expression_test_data/functions_to_boolean.json @@ -80,6 +80,7 @@ "variables": [], "platforms": [ "android", + "ios", "web" ] }, @@ -107,6 +108,7 @@ "variables": [], "platforms": [ "android", + "ios", "web" ] }, diff --git a/test_data/expression_test_data/functions_to_integer.json b/test_data/expression_test_data/functions_to_integer.json index e9bd37626..c181ea409 100644 --- a/test_data/expression_test_data/functions_to_integer.json +++ b/test_data/expression_test_data/functions_to_integer.json @@ -248,6 +248,7 @@ "variables": [], "platforms": [ "android", + "ios", "web" ] } diff --git a/test_data/expression_test_data/functions_to_number.json b/test_data/expression_test_data/functions_to_number.json index b3a4a9a8d..7e57edb89 100644 --- a/test_data/expression_test_data/functions_to_number.json +++ b/test_data/expression_test_data/functions_to_number.json @@ -332,6 +332,7 @@ "variables": [], "platforms": [ "android", + "ios", "web" ] }, @@ -359,6 +360,7 @@ "variables": [], "platforms": [ "android", + "ios", "web" ] } diff --git a/test_data/expression_test_data/functions_to_string.json b/test_data/expression_test_data/functions_to_string.json index c4f3b552c..9f9895741 100644 --- a/test_data/expression_test_data/functions_to_string.json +++ b/test_data/expression_test_data/functions_to_string.json @@ -84,7 +84,8 @@ ], "platforms": [ "web", - "android" + "android", + "ios" ] }, { diff --git a/test_data/expression_test_data/functions_unsupported.json b/test_data/expression_test_data/functions_unsupported.json index ae7874607..f2a1fce9d 100644 --- a/test_data/expression_test_data/functions_unsupported.json +++ b/test_data/expression_test_data/functions_unsupported.json @@ -13,6 +13,25 @@ "ios", "web" ] + }, + { + "name": "unknownFunc(dict) => error", + "expression": "@{unknownFunc(dict)}", + "expected": { + "type": "error", + "value": "Failed to evaluate [unknownFunc()]. Unknown function name: unknownFunc." + }, + "variables": [ + { + "type": "dict", + "name": "dict", + "value": {} + } + ], + "platforms": [ + "ios", + "web" + ] } ] }