// // CalcExpression.swift // Expression // // Version 0.13.5 // // Created by Nick Lockwood on 15/09/2016. // Copyright © 2016 Nick Lockwood. All rights reserved. // // Distributed under the permissive MIT license // Get the latest version from here: // // https://github.com/nicklockwood/Expression // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // import Foundation import VGSL struct CalcExpression { private let root: Subexpression var variableNames: [String] { root.symbols.compactMap { if case let .variable(name) = $0 { return name } return nil } } static func parse(_ expression: String) throws -> CalcExpression { var unicodeScalarView = UnicodeScalarView(expression.unicodeScalars) return try CalcExpression(root: unicodeScalarView.parseSubexpression(upTo: [])) } func extractDynamicVariableNames(_ context: ExpressionContext) throws -> [String] { try root.extractDynamicVariableNames(context) } func evaluate(_ context: ExpressionContext) throws -> Any { try root.evaluate(context) } } enum Subexpression { case literal(Any) case symbol(CalcExpression.Symbol, [Subexpression]) var isOperand: Bool { switch self { case let .symbol(symbol, args) where args.isEmpty: switch symbol { case .infix, .prefix: false default: true } case .symbol, .literal: true } } var symbols: Set { switch self { case .literal: return [] case let .symbol(symbol, subexpressions): var symbols = Set([symbol]) for subexpression in subexpressions { symbols.formUnion(subexpression.symbols) } return symbols } } func evaluate(_ context: ExpressionContext) throws -> Any { switch self { case let .literal(value): return value case let .symbol(symbol, args): if let evaluator = context.evaluators(symbol) { return try evaluator.invoke(args: args, context: context) } throw ExpressionError("Undefined symbol: \(symbol.name).") } } func extractDynamicVariableNames(_ context: ExpressionContext) throws -> [String] { switch self { case .literal: return [] case let .symbol(symbol, subexpressions): var dynamicVariables = [String]() if let variableName = try? context.dynamicVariablesEvaluator(symbol)?.invoke( args: subexpressions, context: context ) as? String { dynamicVariables.append(variableName) } for subexpression in subexpressions { try dynamicVariables.append(contentsOf: subexpression.extractDynamicVariableNames(context)) } return dynamicVariables } } } // MARK: Expression parsing // Workaround for horribly slow Substring.UnicodeScalarView perf fileprivate struct UnicodeScalarView { typealias Index = String.UnicodeScalarView.Index private(set) var startIndex: Index private(set) var endIndex: Index private let characters: String.UnicodeScalarView /// Returns the remaining characters var unicodeScalars: Substring.UnicodeScalarView { characters[startIndex..= endIndex } init(_ unicodeScalars: String.UnicodeScalarView) { characters = unicodeScalars startIndex = characters.startIndex endIndex = characters.endIndex } func prefix(upTo index: Index) -> UnicodeScalarView { var view = UnicodeScalarView(characters) view.startIndex = startIndex view.endIndex = index return view } func suffix(from index: Index) -> UnicodeScalarView { var view = UnicodeScalarView(characters) view.startIndex = index view.endIndex = endIndex return view } private subscript(_ index: Index) -> UnicodeScalar { characters[index] } private func index(after index: Index) -> Index { characters.index(after: index) } private mutating func popFirst() -> UnicodeScalar? { if isEmpty { return nil } let char = characters[startIndex] startIndex = characters.index(after: startIndex) return char } } extension String { fileprivate init(_ unicodeScalarView: DivKit.UnicodeScalarView) { self.init(unicodeScalarView.unicodeScalars) } } extension UnicodeScalarView { private enum Number { case number(String) case integer(String) var value: String { switch self { case let .number(value): value case let .integer(value): value } } } private mutating func scanCharacters(_ matching: (UnicodeScalar) -> Bool) -> String? { var index = startIndex while index < endIndex { if !matching(self[index]) { break } index = self.index(after: index) } if index > startIndex { let string = String(prefix(upTo: index)) self = suffix(from: index) return string } return nil } private mutating func scanCharacter( _ matching: (UnicodeScalar) -> Bool = { _ in true } ) -> String? { if let c = first, matching(c) { self = suffix(from: index(after: startIndex)) return String(c) } return nil } private mutating func scanCharacter(_ character: UnicodeScalar) -> Bool { scanCharacter { $0 == character } != nil } private mutating func scanToEndOfToken() -> String? { scanCharacters { switch $0 { case " ", "\t", "\n", "\r", "(", ")": false default: true } } } private mutating func skipWhitespace() -> Bool { if let _ = scanCharacters({ switch $0 { case " ", "\t", "\n", "\r": true default: false } }) { return true } return false } private mutating func scanMethodName() -> String? { _ = skipWhitespace() var name = "" if let head = scanCharacter(isIdentifierHead) { name = head } else { return nil } if let tail = scanCharacters(isIdentifier) { name += tail } _ = skipWhitespace() return name } private mutating func parseDelimiter(_ delimiters: [String]) -> Bool { _ = skipWhitespace() outer: for delimiter in delimiters { let start = self for char in delimiter.unicodeScalars { guard scanCharacter(char) else { self = start continue outer } } self = start return true } return false } private mutating func parseToken(isOperandExpected: Bool) throws -> Subexpression? { if !isOperandExpected, let op = parseOperator() { return op } if let identifier = try parseNumericLiteral() ?? parseIdentifier() ?? parseStringLiteral() { return identifier } if isOperandExpected { return parseOperator() } return nil } private mutating func parseNumericLiteral() throws -> Subexpression? { func scanInteger() -> String? { scanCharacters { if case "0"..."9" = $0 { return true } return false } } func scanExponent() -> String? { let start = self if let e = scanCharacter({ $0 == "e" || $0 == "E" }) { let sign = scanCharacter { $0 == "-" || $0 == "+" } ?? "" if let exponent = scanInteger() { return e + sign + exponent } } self = start return nil } func scanNumber() throws -> Number? { var number: Number var endOfInt = self let sign = scanCharacter { $0 == "-" } ?? "" if let integer = scanInteger() { endOfInt = self if scanCharacter(".") { guard let fraction = scanInteger() else { self = endOfInt return .integer(sign + integer) } number = .number("\(sign)\(integer).\(fraction)") } else { number = .integer(sign + integer) } } else if scanCharacter(".") { guard let fraction = scanInteger() else { self = endOfInt return nil } number = .number("\(sign).\(fraction)") } else { self = endOfInt return nil } if let exponent = scanExponent() { number = .number(number.value + exponent) } return number } guard let number = try scanNumber() else { return nil } switch number { case let .integer(value): guard let intValue = Int(value) else { throw ExpressionError("Value \(value) can't be converted to Integer type.") } return .literal(intValue) case let .number(value): guard let doubleValue = Double(value) else { throw ExpressionError.unexpectedToken(value) } return .literal(doubleValue) } } private mutating func parseOperator() -> Subexpression? { guard let op1 = scanCharacter({ "+-*/%=<>!&|?:(.".unicodeScalars.contains($0) }) else { return nil } let op2: String? = switch op1 { case "=", "<", ">": scanCharacter { $0 == "=" } case "|": scanCharacter { $0 == "|" } case "&": scanCharacter { $0 == "&" } case "!": scanCharacter { "=:".unicodeScalars.contains($0) } case "+", "-": scanCharacter { "+-".unicodeScalars.contains($0) } default: nil } if let op2 { return .symbol(.infix(op1 + op2), []) } return .symbol(.infix(op1), []) } private mutating func parseIdentifier() throws -> Subexpression? { var identifier = "" if let head = scanCharacter(isIdentifierHead) { identifier = head } else { return nil } var prevChar: UInt32 = 0 if let tail = scanCharacters({ let char = $0.value if char == 0x2E, prevChar == 0x2E { return false } prevChar = $0.value switch char { case 0x2E: // . return true default: return isIdentifier($0) } }) { identifier += tail } if prevChar == 0x2E { throw ExpressionError.unexpectedToken(".") } return makeVariable(identifier) } private mutating func parseStringLiteral() throws -> Subexpression? { if !scanCharacter("'") { return nil } var string = "" var part: String? repeat { part = scanCharacters { $0 != "'" && $0 != "\\" } string += part ?? "" if scanCharacter("\\"), let c = popFirst() { switch c { case "'", "\\": string.append(Character(c)) case "@" where scanCharacter("{"): string += "@{" default: throw ExpressionError("Incorrect string escape.") } part = "" } } while part != nil if scanCharacter("'") { return .literal(string) } throw ExpressionError("Closing ' expected.") } mutating func parseSubexpression( upTo delimiters: [String] ) throws -> Subexpression { var stack: [Subexpression] = [] func collapseStack(from i: Int) throws { guard stack.count > i + 1 else { return } let lhs = stack[i] let rhs = stack[i + 1] if lhs.isOperand { if rhs.isOperand { throw ExpressionError.unexpectedToken("\(rhs)") } if case let .symbol(symbol, _) = rhs { let rhs = stack[i + 2] if rhs.isOperand { if stack.count > i + 3 { switch stack[i + 3] { case let .symbol(.infix(op2), _), let .symbol(.prefix(op2), _): guard stack.count > i + 4, takesPrecedence(symbol.name, over: op2) else { fallthrough } default: try collapseStack(from: i + 2) return } } stack[i...i + 2] = [.symbol(.infix(symbol.name), [lhs, rhs])] try collapseStack(from: 0) } else if case let .symbol(symbol2, _) = rhs { if case .prefix = symbol2 { try collapseStack(from: i + 2) } else { stack[i + 2] = .symbol(.prefix(symbol2.name), []) try collapseStack(from: i + 2) } } } } else if case let .symbol(symbol, _) = lhs { // Treat as prefix operator if rhs.isOperand { stack[i...i + 1] = [.symbol(.prefix(symbol.name), [rhs])] try collapseStack(from: 0) } else if case .symbol = rhs { // Nested prefix operator? try collapseStack(from: i + 1) } } } func collapseStackToSingleExpression() throws -> Subexpression { try collapseStack(from: 0) if let first = stack.first { if first.isOperand { return first } throw ExpressionError("Operand expected") } throw ExpressionError("Empty expression") } func scanArguments() throws -> [Subexpression] { let delimiter: Unicode.Scalar = ")" var args = [Subexpression]() if first != delimiter { let delimiters = [",", String(delimiter)] repeat { try args.append(parseSubexpression(upTo: delimiters)) } while scanCharacter(",") } guard scanCharacter(delimiter) else { throw ExpressionError("Closing ) expected.") } return args } _ = skipWhitespace() var operandPosition = true var precededByWhitespace = true while !parseDelimiter(delimiters), let expression = try parseToken(isOperandExpected: operandPosition) { // Prepare for next iteration var followedByWhitespace = skipWhitespace() || isEmpty switch expression { case let .symbol(.infix(name), _): switch name { // ternary ?: operator case "?": let arg0 = try collapseStackToSingleExpression() let arg1 = try parseSubexpression(upTo: [":"]) guard scanCharacter(":") else { throw ExpressionError(": expected.") } let arg2 = try parseSubexpression(upTo: delimiters) stack[stack.count - 1] = .symbol(.ternary, [arg0, arg1, arg2]) // method call case ".": guard let lastSymbol = stack.last else { throw ExpressionError.unexpectedToken(".") } guard let methodName = scanMethodName(), scanCharacter("(") else { throw ExpressionError("Method expected after .") } var args = try scanArguments() args.insert(lastSymbol, at: 0) stack[stack.count - 1] = makeMethod(methodName, args) // function or method args case "(": switch stack.last { case let .symbol(.variable(name), _)?: var args = try scanArguments() let parts = name.split(separator: ".") if parts.count == 1 { // function() stack[stack.count - 1] = makeFunction(name, args) } else { // variable.method() let variableName = parts.dropLast().joined(separator: ".") args.insert(makeVariable(variableName), at: 0) stack[stack.count - 1] = makeMethod(String(parts.last!), args) } default: let subexpression = try parseSubexpression(upTo: [")"]) stack.append(subexpression) guard scanCharacter(")") else { throw ExpressionError("Closing ) expected.") } } operandPosition = false followedByWhitespace = skipWhitespace() default: switch (precededByWhitespace, followedByWhitespace) { case (true, false): stack.append(.symbol(.prefix(name), [])) default: stack.append(expression) } operandPosition = true } default: operandPosition = false stack.append(expression) } // Next iteration precededByWhitespace = followedByWhitespace } // Check for trailing junk let start = self if !parseDelimiter(delimiters), let junk = scanToEndOfToken() { self = start throw ExpressionError.unexpectedToken(junk) } return try collapseStackToSingleExpression() } } private let operatorPrecedence: [String: ( precedence: Int, isRightAssociative: Bool )] = { var precedences = [ "[]": 100, "*": 1, "/": 1, "%": 1, // multiplication // +, -, |, ^, etc: 0 (also the default) "!:": -3, // comparison: -4 "&&": -5, // and "||": -6, // or ":": -8, // ternary ].mapValues { ($0, false) } precedences["?"] = (-7, true) // ternary for op in ["<", "<=", ">=", ">", "==", "!="] { // comparison precedences[op] = (-4, true) } return precedences }() private func takesPrecedence(_ lhs: String, over rhs: String) -> Bool { let (p1, rightAssociative) = operatorPrecedence[lhs] ?? (0, false) let (p2, _) = operatorPrecedence[rhs] ?? (0, false) if p1 == p2 { return !rightAssociative } return p1 > p2 } private func isIdentifierHead(_ c: UnicodeScalar) -> Bool { switch c.value { case 0x5F, // _ 0x41...0x5A, // A-Z 0x61...0x7A: // a-z true default: false } } private func isIdentifier(_ c: UnicodeScalar) -> Bool { switch c.value { case 0x2E, // . 0x30...0x39: // 0-9 true default: isIdentifierHead(c) } } private func makeVariable(_ name: String) -> Subexpression { .symbol(.variable(name), []) } private func makeFunction(_ name: String, _ args: [Subexpression]) -> Subexpression { .symbol(.function(name), args) } private func makeMethod(_ name: String, _ args: [Subexpression]) -> Subexpression { .symbol(.method(name), args) }