Files
divkit/client/ios/DivKit/Expressions/Functions/StringFunctions.swift
T
pkurchatov 290a1febad reduced methods visibility
Немного причесал API в выражениях и разложил по папкам.
2022-09-13 16:18:22 +03:00

184 lines
4.8 KiB
Swift

import Foundation
enum StringFunctions: String, CaseIterable {
enum Error {
case cast
case indexesCast
case indexesOrder(String)
case indexesValue(String)
case encodeEmpty
case decodeEmpty
private var description: String {
switch self {
case .cast:
return "Argument couldn't be casted to String"
case .indexesCast:
return "Indexes couldn't be casted to Integer"
case let .indexesOrder(expression):
return "Failed to evaluate [\(expression)]. Indexes should be in ascending order."
case let .indexesValue(expression):
return "Failed to evaluate [\(expression)]. Indexes are out of bounds."
case .encodeEmpty:
return "String is empty after encoding."
case .decodeEmpty:
return "String is empty after decoding."
}
}
var message: AnyCalcExpression.Error {
AnyCalcExpression.Error.message(description)
}
}
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
var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] {
[.function(rawValue, arity: function.arity): function.symbolEvaluator]
}
var function: Function {
switch self {
case .len:
return FunctionUnary(impl: _len)
case .contains:
return FunctionBinary(impl: _contains)
case .substring:
return FunctionTernary(impl: _substring)
case .replaceAll:
return FunctionTernary(impl: _replaceAll)
case .index:
return FunctionBinary(impl: _index)
case .lastIndex:
return FunctionBinary(impl: _lastIndex)
case .trim:
return FunctionUnary(impl: _trim)
case .trimLeft:
return FunctionUnary(impl: _trimLeft)
case .trimRight:
return FunctionUnary(impl: _trimRight)
case .toUpperCase:
return FunctionUnary(impl: _toUpperCase)
case .toLowerCase:
return FunctionUnary(impl: _toLowerCase)
case .encodeUri:
return FunctionUnary(impl: _encodeUri)
case .decodeUri:
return FunctionUnary(impl: _decodeUri)
}
}
}
private let dontNeedEncoding = CharacterSet(charactersIn: "a"..."z")
.union(CharacterSet(charactersIn: "A"..."Z"))
.union(CharacterSet(charactersIn: "0"..."9"))
.union(CharacterSet(charactersIn: "-_.*!~'()"))
private func _len(value: String) -> Int {
value.count
}
private func _contains(first: String, second: String) -> Bool {
guard !second.isEmpty else { return true }
return first.range(of: second) != nil
}
private func _substring(first: String, second: Int, third: Int) throws -> String {
guard second <= third else {
throw StringFunctions.Error.indexesOrder("substring('\(first)', \(second), \(third))").message
}
guard second >= 0, third <= first.count
else {
throw StringFunctions.Error.indexesValue("substring('\(first)', \(second), \(third))").message
}
return String(first[first.rangeOfCharsIn(second..<third)])
}
private func _replaceAll(first: String, second: String, third: String) -> String {
first.replacingOccurrences(of: second, with: third)
}
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 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 func _trim(value: String) -> String {
value.trimmed
}
private func _trimLeft(value: String) -> String {
for index in value.indices {
if !value[index].isWhitespace {
return String(value[index...])
}
}
return ""
}
private func _trimRight(value: String) -> String {
for index in value.indices.reversed() {
if !value[index].isWhitespace {
return String(value[...index])
}
}
return ""
}
private func _toUpperCase(value: String) -> String {
value.uppercased()
}
private func _toLowerCase(value: String) -> String {
value.lowercased()
}
private func _encodeUri(value: String) throws -> String {
guard let encodedValue = value.addingPercentEncoding(withAllowedCharacters: dontNeedEncoding)
else {
throw StringFunctions.Error.encodeEmpty.message
}
return encodedValue
}
private func _decodeUri(value: String) throws -> String {
guard let decodedValue = value.removingPercentEncoding else {
throw StringFunctions.Error.decodeEmpty.message
}
return decodedValue
}
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)
}
}