// // AnyExpressionTests.swift // ExpressionTests // // Created by Nick Lockwood on 18/04/2017. // 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. // #if os(macOS) import CoreGraphics #else typealias CGFloat = Double #endif import XCTest @testable import Expression private struct HashableStruct: Hashable { let foo: Int var hashValue: Int { return foo.hashValue } } private struct EquatableStruct: Equatable { let foo: Int } final class AnyExpressionTests: XCTestCase { func testLinuxTestSuiteIncludesAllTests() { #if os(macOS) let thisClass = type(of: self) let linuxCount = thisClass.__allTests.count let darwinCount = thisClass.defaultTestSuite.testCaseCount XCTAssertEqual(linuxCount, darwinCount, "run swift test --generate-linuxmain") #endif } // MARK: Description func testDescriptionFormatting() { let expression = AnyExpression("a+b") XCTAssertEqual(expression.description, "a + b") } func testStringLiteralDescriptionNotMangled() { let expression = AnyExpression("foo('bar')") XCTAssertEqual(expression.description, "foo('bar')") } func testStringConstantDescriptionNotMangled() { let expression = AnyExpression("foo(bar)", constants: ["bar": "bar"]) XCTAssertEqual(expression.description, "foo(bar)") } // MARK: Error description func testPrivateTypeErrorDescription() { let error = Expression.Error.typeMismatch(.infix("=="), [EquatableStruct(foo: 5), HashableStruct(foo: 6)]) XCTAssert(error.description.contains("Arguments of type (EquatableStruct, HashableStruct) are not compatible with infix operator ==")) } func testEquateNonHashableTypesErrorDescription() { let error = Expression.Error.typeMismatch(.infix("=="), [EquatableStruct(foo: 5), EquatableStruct(foo: 6)]) XCTAssert(error.description.contains("Arguments for infix operator == must conform to the Hashable protocol")) } func testStringBoundsErrorDescription() { let error = Expression.Error.stringBounds("hello\nworld", "goodbye world".endIndex) XCTAssert(error.description.contains("Character index 13 out of bounds for string 'hello\\nworld'")) } func testCallNonFunctionErrorDescription() { let error = Expression.Error.typeMismatch(.infix("()"), ["foo", 3]) XCTAssertEqual(error.description, "Attempted to call non function type String") } func testCallNonEvaluatorFunctionErrorDescription() { let error = Expression.Error.typeMismatch(.infix("()"), [{ (_: Int) -> Bool in false }]) XCTAssertEqual(error.description, "Attempted to call non SymbolEvaluator function type (Int) -> Bool") } func testCallEvaluatorFunctionWithWrongArgumentsErrorDescription() { let error = Expression.Error.typeMismatch(.infix("()"), [ { (_: [Double]) throws -> Double in 0 }, 57, "foo", ]) XCTAssertEqual(error.description, "Attempted to call function with incompatible arguments (Double, String)") } func testSubscriptArraySymbolWithIncompatibleIndexTypeErrorDescription() { let error = Expression.Error.typeMismatch(.array("foo"), ["bar"]) XCTAssertEqual(error.description, "Attempted to subscript foo with incompatible index type String") } func testSubscriptArraySymbolWithIncompatibleTypeErrorDescription() { let error = Expression.Error.typeMismatch(.array("foo"), [5, "bar"]) XCTAssertEqual(error.description, "Attempted to subscript Int value foo") } func testSubscriptNonArrayValueErrorDescription() { let error = Expression.Error.typeMismatch(.infix("[]"), [5, "bar"]) XCTAssertEqual(error.description, "Attempted to subscript Int value") } func testSubscriptArrayValueWithIncompatibleIndexTypeErrorDescription() { let error = Expression.Error.typeMismatch(.infix("[]"), [["foo"], "bar"]) XCTAssertEqual(error.description, "Attempted to subscript Array with incompatible index type String") } // MARK: Arrays func testSubscriptArrayConstant() { let expression = AnyExpression("a[b]", constants: [ "a": ["hello", "world"], "b": 1, ]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate() as String, "world") } func testSubscriptArraySliceConstant() { let expression = AnyExpression("a[b]", constants: [ "a": ArraySlice(["hello", "world"]), "b": 1, ]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate() as String, "world") } func testArrayBounds() { let expression = AnyExpression("array[2]", constants: [ "array": ["hello", "world"], ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.array("array"), 2)) } } func testArraySliceBounds() { let expression = AnyExpression("array[2]", constants: [ "array": ArraySlice(["hello", "world"]), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.array("array"), 2)) } } func testSubscriptArrayWithString() { let expression = AnyExpression("a[b]", constants: [ "a": ["hello", "world"], "b": "oops", ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("a"), ["oops"])) } } func testCompareEqualArrays() { let expression = AnyExpression("a == b", constants: [ "a": ["hello", "world"], "b": ["hello", "world"], ]) XCTAssertTrue(try expression.evaluate()) } func testCompareUnequalArrays() { let expression = AnyExpression("a == b", constants: [ "a": ["hello", "world"], "b": ["world", "hello"], ]) XCTAssertFalse(try expression.evaluate()) } func testCustomArraySymbol() { let expression = AnyExpression("a[100000000]", symbols: [ .array("a"): { args in args[0] }, ]) XCTAssertEqual(try expression.evaluate(), 100000000) } func testSubscriptIntConstant() { let expression = AnyExpression("foo[0]", constants: [ "foo": 5, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("foo"), [5, 0.0])) } } func testSubscriptIntVariable() { let expression = AnyExpression("foo[0]", symbols: [ .variable("foo"): { _ in 5 }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("foo"), [5, 0.0])) } } func testSubscriptPureIntVariable() { let expression = AnyExpression(Expression.parse("foo[0]"), pureSymbols: { symbol in symbol == .variable("foo") ? { _ in 5 } : nil }) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("foo"), [5, 0.0])) } } func testSubscriptErrorThrowingVariable() { let expression = AnyExpression(Expression.parse("foo[0]"), pureSymbols: { symbol in symbol == .variable("foo") ? { _ in throw Expression.Error.message("Disabled") } : nil }) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("Disabled")) } } func testSubscriptIntLiteral() { let expression = AnyExpression("5[1]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [5.0, 0.0])) } } func testSubscriptIntExpressionLiteral() { let expression = AnyExpression("(2 + 3)[1]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [5.0, 0.0])) } } func testSubscriptNonexistentSymbol() { let expression = AnyExpression("foo[0]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.array("foo"))) } } func testStringArrayLiteral() { let expression = AnyExpression("['a','b','c']") XCTAssertEqual(try expression.evaluate(), ["a", "b", "c"]) } func testDoubleArrayLiteral() { let expression = AnyExpression("[1.5, 2.5, 3.5]") XCTAssertEqual(try expression.evaluate(), [1.5, 2.5, 3.5]) } func testIntArrayLiteral() { let expression = AnyExpression("[1,2,3]") XCTAssertEqual(try expression.evaluate(), [1, 2, 3]) } func testIntArraySliceLiteral() { let expression = AnyExpression("[1,2,3]") XCTAssertEqual(try expression.evaluate(), ArraySlice([1, 2, 3])) } func testSubscriptIntArrayLiteral() { let expression = AnyExpression("[1,2,3][1]") XCTAssertEqual(try expression.evaluate(), 2) } func testSubscriptStringArrayLiteral() { let expression = AnyExpression("['a','b','c'][1]") XCTAssertEqual(try expression.evaluate(), "b") } func testSubscriptStringArrayLiteralWithString() { let expression = AnyExpression("['a','b','c']['foo']") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), "foo"])) } } func testSubscriptStringArrayLiteralOutOfBounds() { let expression = AnyExpression("['a','b','c'][3]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 3)) } } func testConcatIntArrays() { let expression = AnyExpression("[1,2] + [3,4]") XCTAssertEqual(try expression.evaluate(), [1, 2, 3, 4]) } func testConcatIntArraySlices() { let expression = AnyExpression("a + b", constants: [ "a": ArraySlice([1, 2]), "b": ArraySlice([3, 4]), ]) XCTAssertEqual(try expression.evaluate(), [1, 2, 3, 4]) } func testConcatArrayAndArraySlice() { let expression = AnyExpression("a + [3, 4]", constants: [ "a": ArraySlice([1, 2]), ]) XCTAssertEqual(try expression.evaluate(), ArraySlice([1, 2, 3, 4])) } func testConcatMixedArrays() { let expression = AnyExpression("[1,2] + ['a', 'b']") XCTAssertEqual(try expression.evaluate() as [AnyHashable], [1.0, 2.0, "a", "b"]) } // MARK: Dictionaries func testSubscriptStringDictionaryWithString() { let expression = AnyExpression("a[b]", constants: [ "a": ["hello": "world"], "b": "hello", ]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate() as String, "world") } func testSubscriptStringNSDictionaryWithString() { let expression = AnyExpression("a[b]", constants: [ "a": ["hello": "world"] as NSDictionary, "b": "hello", ]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate() as String, "world") } func testSubscriptDoubleDictionaryWithInt() { let expression = AnyExpression("a[b]", constants: [ "a": [1.0: "world"], "b": 1, ]) XCTAssertEqual(try expression.evaluate(), "world") } func testSubscriptIntDictionaryWithDouble() { let expression = AnyExpression("a[b]", constants: [ "a": [1: "world"], "b": 1.0, ]) XCTAssertEqual(try expression.evaluate(), "world") } func testSubscriptStringDictionaryWithInt() { let expression = AnyExpression("a[b]", constants: [ "a": ["1": "world"], "b": 1, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("a"), [1.0])) } } func testSubscriptStringNSDictionaryWithInt() { let expression = AnyExpression("a[b]", constants: [ "a": ["1": "world"] as NSDictionary, "b": 1, ]) XCTAssertNil(try expression.evaluate() as Any?) } func testSubscriptStringDictionaryWithNonHashableType() { let expression = AnyExpression("a[b]", constants: [ "a": ["hello": "world"], "b": EquatableStruct(foo: 1), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("a"), [EquatableStruct(foo: 1)])) } } func testUndefinedTupleSubscript() { let expression = AnyExpression("foo[(2,3)]", symbols: [ .array("foo"): { _ in 0 }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .unexpectedToken(",")) } } func testTupleSubscript() { let expression = AnyExpression("foo[(2,3)]", symbols: [ .variable("foo"): { _ in [5] }, .infix(","): { $0 }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("foo"), [[Any]()])) } } // MARK: Ranges func testClosedIntRange() { let expression = AnyExpression("1 ... 3") XCTAssertEqual(try expression.evaluate(), 1 ... 3) } func testHalfOpenIntRange() { let expression = AnyExpression("1 ..< 3") XCTAssertEqual(try expression.evaluate(), 1 ..< 3) } func testClosedStringIndexRange() { let string = "foo" let expression = AnyExpression("start ... end", constants: [ "start": string.startIndex, "end": string.endIndex, ]) XCTAssertEqual(try expression.evaluate(), string.startIndex ... string.endIndex) } func testHalfOpenStringIndexRange() { let string = "foo" let expression = AnyExpression("start ..< end", constants: [ "start": string.startIndex, "end": string.endIndex, ]) XCTAssertEqual(try expression.evaluate(), string.startIndex ..< string.endIndex) } func testInvalidTypeClosedRange() { let expression = AnyExpression("'a' ... 'b'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("..."), ["a", "b"])) } } func testMixedTypeClosedRange() { let expression = AnyExpression("1 ... index", constants: [ "index": "foo".startIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("..."), [1.0, "foo".startIndex])) } } func testInvalidTypeHalfOpenRange() { let expression = AnyExpression("'a' ..< 'b'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("..<"), ["a", "b"])) } } func testMixedTypeHalfOpenRange() { let expression = AnyExpression("1 ..< index", constants: [ "index": "foo".startIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("..<"), [1.0, "foo".startIndex])) } } func testInvalidClosedRange() { let expression = AnyExpression("1 ... -1") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .invalidRange(1, -1)) } } func testInvalidClosedStringRange() { let range = "foo".startIndex ... "foo".endIndex let expression = AnyExpression("end ... start", constants: [ "start": range.lowerBound, "end": range.upperBound, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .invalidRange(range.upperBound, range.lowerBound)) } } func testInvalidHalfOpenRange() { let expression = AnyExpression("1 ..< 1") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .invalidRange(1, 1)) } } func testInvalidHalfOpenStringRange() { let index = "foo".startIndex let expression = AnyExpression("start ..< start", constants: [ "start": index, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .invalidRange(index, index)) } } func testInvalidTypeRangeFrom() { let expression = AnyExpression("'a'...") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.postfix("..."), ["a"])) } } func testInvalidTypeRangeUpTo() { let expression = AnyExpression("..<'a'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.prefix("..<"), ["a"])) } } func testInvalidTypeRangeThrough() { let expression = AnyExpression("...'a'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.prefix("..."), ["a"])) } } // MARK: Array range subscripting func testSubscriptArrayWithCountableClosedRange() { let expression = AnyExpression("[1,2,3,4][1...2]") XCTAssertEqual(try expression.evaluate(), [2, 3]) } func testSubscriptArrayWithClosedRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": 1 ... 2 as ClosedRange, ]) XCTAssertEqual(try expression.evaluate(), [2, 3]) } func testSubscriptArrayWithCountableHalfOpenRange() { let expression = AnyExpression("[1,2,3,4][1..<3]") XCTAssertEqual(try expression.evaluate(), [2, 3]) } func testSubscriptArrayWithHalfOpenRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": 1 ..< 3 as Range, ]) XCTAssertEqual(try expression.evaluate(), [2, 3]) } func testSubscriptArrayWithRangeFrom() { let expression = AnyExpression("[1,2,3,4][1...]") XCTAssertEqual(try expression.evaluate(), [2, 3, 4]) } func testSubscriptArrayWithRangeUpTo() { let expression = AnyExpression("[1,2,3,4][..<3]") XCTAssertEqual(try expression.evaluate(), [1, 2, 3]) } func testSubscriptArrayWithRangeThrough() { let expression = AnyExpression("[1,2,3,4][...2]") XCTAssertEqual(try expression.evaluate(), [1, 2, 3]) } func testSubscriptArrayWithWrongClosedRangeType() { let range = "foo".startIndex ... "foo".endIndex let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), range])) } } func testSubscriptArrayWithWrongHalfOpenRangeType() { let range = "foo".startIndex ..< "foo".endIndex let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), range])) } } func testSubscriptArrayWithWrongRangeFromType() { let range = "foo".endIndex... let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), range])) } } func testSubscriptArrayWithWrongRangeUpToType() { let range = ..<"foo".endIndex let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), range])) } } func testSubscriptArrayWithWrongRangeThroughType() { let range = ..."foo".endIndex let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), [[Any](), range])) } } func testSubscriptArrayWithCountableClosedLowerBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][-2...4]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -2)) } } func testSubscriptArrayWithClosedLowerBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": -2 ... 4 as ClosedRange, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -2)) } } func testSubscriptArrayWithCountableHalfOpenLowerBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][-2..<4]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -2)) } } func testSubscriptArrayWithHalfOpenLowerBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": -2 ..< 4 as Range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -2)) } } func testSubscriptArrayWithCountableClosedUpperBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][3...4]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithClosedUpperBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": 1 ... 4 as ClosedRange, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithCountableHalfOpenUpperBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][3..<5]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithHalfOpenUpperBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][range]", constants: [ "range": 1 ..< 5 as Range, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithRangeFromBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][4...]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithRangeFromBoundOutOfRange2() { let expression = AnyExpression("[1,2,3,4][-1...]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -1)) } } func testSubscriptArrayWithRangeUpToBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][..<5]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithRangeUpToBoundOutOfRange2() { let expression = AnyExpression("[1,2,3,4][..<0]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 0)) } } func testSubscriptArrayWithRangeThroughBoundOutOfRange() { let expression = AnyExpression("[1,2,3,4][...4]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), 4)) } } func testSubscriptArrayWithRangeThroughBoundOutOfRange2() { let expression = AnyExpression("[1,2,3,4][...-1]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arrayBounds(.infix("[]"), -1)) } } func testNSArraySubscripting() { let expression = AnyExpression( "foo[1...2]", constants: [ "foo": NSArray(array: [1, 2, 3]), ] ) XCTAssertEqual(try expression.evaluate(), [2, 3]) } // MARK: String subscripting func testSubscriptStringWithIndex() { let expression = AnyExpression("'foo'[index]", constants: [ "index": "foo".startIndex, ]) XCTAssertEqual(try expression.evaluate() as Character, "f") } func testSubscriptSubstringWithIndex() { let expression = AnyExpression("foo[index]", constants: [ "foo": Substring("foo"), "index": "foo".startIndex, ]) XCTAssertEqual(try expression.evaluate() as Character, "f") } func testSubscriptNSStringWithIndex() { let expression = AnyExpression("foo[index]", constants: [ "foo": "foo" as NSString, "index": "foo".startIndex, ]) XCTAssertEqual(try expression.evaluate() as Character, "f") } func testSubscriptStringWithOutOfRangeIndex() { let index = "food".endIndex let expression = AnyExpression("'foo'[index]", constants: [ "index": index, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", index)) } } func testSubscriptSubstringWithOutOfRangeIndex() { let index = "foobar".endIndex let expression = AnyExpression("foo[index]", constants: [ "foo": Substring("foo"), "index": index, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", index)) } } func testSubscriptNSStringWithOutOfRangeIndex() { let index = "foobarbaz".endIndex let expression = AnyExpression("foo[index]", constants: [ "foo": "foo" as NSString, "index": index, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", index)) } } func testSubscriptStringWithInt() { let expression = AnyExpression("'foo'[2]") XCTAssertEqual(try expression.evaluate() as Character, "o") } func testSubscriptSubstringWithInt() { let expression = AnyExpression("foo[2]", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate() as Character, "o") } func testSubscriptNSStringWithInt() { let expression = AnyExpression("foo[2]", constants: [ "foo": "foo" as NSString, ]) XCTAssertEqual(try expression.evaluate() as Character, "o") } func testSubscriptStringWithOutOfRangeInt() { let expression = AnyExpression("'foo'[5]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 5)) } } func testSubscriptSubstringWithOutOfRangeInt() { let expression = AnyExpression("foo[4]", constants: [ "foo": Substring("foo"), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 4)) } } func testSubscriptNSStringWithOutOfRangeInt() { let expression = AnyExpression("foo[9]", constants: [ "foo": "foo" as NSString, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 9)) } } func testSubscriptStringLiteralWithInvalidIndexType() { let expression = AnyExpression("'foo'[index]", constants: [ "index": NSObject(), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.array("'foo'"), [NSObject()])) } } func testSubscriptStringExpressionWithInvalidIndexType() { let expression = AnyExpression("('foo' + 'bar')[index]", constants: [ "index": NSObject(), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("[]"), ["foobar", NSObject()])) } } func testSubscriptStringWithHalfOpenIndexRange() throws { let expression = try AnyExpression("'foo'[range]", constants: [ "range": XCTUnwrap("foo".range(of: "fo")), ]) XCTAssertEqual(try expression.evaluate(), "fo") } func testSubscriptStringWithClosedIndexRange() { let expression = AnyExpression("'foo'[range]", constants: [ "range": "foo".startIndex ... "foo".index(after: "foo".startIndex), ]) XCTAssertEqual(try expression.evaluate(), "fo") } func testSubscriptSubstringWithHalfOpenIndexRange() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": Substring("foo"), "range": XCTUnwrap("foo".range(of: "fo")), ]) XCTAssertEqual(try expression.evaluate(), "fo") } func testSubscriptNSStringWithHalfOpenIndexRange() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "foo" as NSString, "range": XCTUnwrap("foo".range(of: "fo")), ]) XCTAssertEqual(try expression.evaluate(), "fo") } func testSubscriptStringWithInvalidHalfOpenIndexRange() throws { let expression = try AnyExpression("'foo'[range]", constants: [ "range": XCTUnwrap("foobar".range(of: "bar")), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringWithInvalidClosedIndexRange() throws { let expression = try AnyExpression("'foo'[range]", constants: [ "range": XCTUnwrap("foobar".range(of: "bar")?.lowerBound) ... "foobar".endIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptSubstringWithInvalidHalfOpenIndexRange() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "barfoo".suffix(3), "range": XCTUnwrap("barfoo".range(of: "bar")), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -3)) } } func testSubscriptNSStringWithInvalidHalfOpenIndexRangeLowerBound() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "foo" as NSString, "range": XCTUnwrap("foobarbaz".range(of: "baz")), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 6)) } } func testSubscriptNSStringWithInvalidHalfOpenIndexRangeUpperBound() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "foo" as NSString, "range": XCTUnwrap("foobar".range(of: "obar")), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 6)) } } func testSubscriptNSStringWithInvalidClosedIndexRangeLowerBound() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "foo" as NSString, "range": XCTUnwrap("foobarbaz".range(of: "baz")?.lowerBound) ... "foobarbaz".endIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 6)) } } func testSubscriptNSStringWithInvalidClosedIndexRangeUpperBound() throws { let expression = try AnyExpression("foo[range]", constants: [ "foo": "foo" as NSString, "range": XCTUnwrap("foobar".range(of: "obar")?.lowerBound) ... "foobar".endIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 6)) } } func testSubscriptStringWithIntRange() { let expression = AnyExpression("'foo'[1..<3]") XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptSubstringWithIntRange() { let expression = AnyExpression("foo[1..<3]", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptNSStringWithIntRange() { let expression = AnyExpression("foo[1..<3]", constants: [ "foo": "foo" as NSString, ]) XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptStringWithClosedIntRange() { let expression = AnyExpression("'foo'[1...2]") XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptSubstringWithClosedIntRange() { let expression = AnyExpression("foo[1...2]", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptNSStringWithClosedIntRange() { let expression = AnyExpression("foo[1...2]", constants: [ "foo": "foo" as NSString, ]) XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptNSStringWithInvalidClosedIntRangeLowerBound() { let expression = AnyExpression("foo[-1...2]", constants: [ "foo": "foo" as NSString, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -1)) } } func testSubscriptNSStringWithInvalidClosedIntRangeUpperBound() { let expression = AnyExpression("foo[1...3]", constants: [ "foo": "foo" as NSString, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringFromIntRange() { let expression = AnyExpression("'foo'[1...]") XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptStringFromInvalidIntRange() { let expression = AnyExpression("'foo'[-1...]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -1)) } } func testSubscriptStringFromInvalidIntRange2() { let expression = AnyExpression("'foo'[3...]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringUpToIntRange() { let expression = AnyExpression("'foo'[..<2]") XCTAssertEqual(try expression.evaluate(), "fo") } func testSubscriptStringUpToInvalidIntRange() { let expression = AnyExpression("'foo'[..<4]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringUpToInvalidIntRange2() { let expression = AnyExpression("'foo'[..<0]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 0)) } } func testSubscriptStringThroughIntRange() { let expression = AnyExpression("'foo'[...2]") XCTAssertEqual(try expression.evaluate(), "foo") } func testSubscriptStringThroughIntRangeEdgeCase() { let expression = AnyExpression("'foo'[...0]") XCTAssertEqual(try expression.evaluate(), "f") } func testSubscriptStringThroughInvalidIntRange() { let expression = AnyExpression("'foo'[...3]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringThroughInvalidIntRange2() { let expression = AnyExpression("'foo'[...-1]") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -1)) } } func testSubscriptStringFromIndexRange() throws { let expression = try AnyExpression("'foo'[index...]", constants: [ "index": XCTUnwrap("foo".index(of: "o")), ]) XCTAssertEqual(try expression.evaluate(), "oo") } func testSubscriptStringFromInvalidIndexRange() throws { let expression = try AnyExpression("'foo'[index...]", constants: [ "index": XCTUnwrap("food".index(of: "d")), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", 3)) } } func testSubscriptStringFromInvalidIndexRange2() throws { #if swift(>=4) let expression = try AnyExpression("foo[index...]", constants: [ "foo": "afoo"[XCTUnwrap("afoo".range(of: "foo"))], "index": "afoo".startIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -1)) } #endif } func testSubscriptStringUpToIndexRange() { let expression = AnyExpression("'foo'[..=4) let expression = try AnyExpression("foo[...index]", constants: [ "foo": "afoo"[XCTUnwrap("afoo".range(of: "foo"))], "index": "afoo".startIndex, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .stringBounds("foo", -1)) } #endif } // MARK: Functions func testCallExpressionSymbolEvaluatorConstant() { let expression = AnyExpression("foo(1, 2)", constants: [ "foo": { $0[0] + $0[1] } as Expression.SymbolEvaluator, ]) XCTAssertEqual(try expression.evaluate(), 3) XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testCallAnyExpressionSymbolEvaluatorConstant() { let expression = AnyExpression("foo('foo', 'bar')", constants: [ "foo": { ($0[0] as! String) + ($0[1] as! String) } as AnyExpression.SymbolEvaluator, ]) XCTAssertEqual(try expression.evaluate(), "foobar") XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testCallExpressionSymbolEvaluatorSymbol() { let expression = AnyExpression("foo(1, 2)", symbols: [ .variable("foo"): { _ in { $0[0] + $0[1] } as Expression.SymbolEvaluator }, ]) XCTAssertEqual(try expression.evaluate(), 3) XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testCallExpressionSymbolEvaluatorPureSymbol() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression(parsedExpression, pureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in { $0[0] + $0[1] } as Expression.SymbolEvaluator } default: return nil } }) XCTAssertEqual(try expression.evaluate(), 3) XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testCallExpressionSymbolEvaluatorPureSymbolError() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression(parsedExpression, pureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in throw Expression.Error.message("foo") } default: return nil } }) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("foo")) } } func testCallExpressionSymbolEvaluatorImpureSymbolError() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression(parsedExpression, impureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in throw Expression.Error.message("foo") } default: return nil } }) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("foo")) } } func testCallSymbolEvaluatorReturnedByFunction() { let bar: Expression.SymbolEvaluator = { $0[0] + $0[1] } let expression = AnyExpression( "foo()(1, 2)", options: .pureSymbols, symbols: [ .function("foo", arity: 0): { _ in bar }, ] ) XCTAssertEqual(try expression.evaluate(), 3) XCTAssertEqual(expression.symbols, [.infix("()")]) } func testCallNonSymbolEvaluatorReturnedByFunction() { let expression = AnyExpression( "foo()(1, 2)", options: .pureSymbols, symbols: [ .function("foo", arity: 0): { _ in "foo" }, ] ) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("()"), ["foo", 1, 2])) } } func testCallSymbolEvaluatorReturnedByAnyExpressionSymbolEvaluatorConstant() { let bar: AnyExpression.SymbolEvaluator = { ($0[0] as! String) + ($0[1] as! String) } let expression = AnyExpression("foo()('foo', 'bar')", constants: [ "foo": { _ in bar } as AnyExpression.SymbolEvaluator, ]) XCTAssertEqual(try expression.evaluate(), "foobar") XCTAssertEqual(expression.symbols, [.function("foo", arity: 0), .infix("()")]) } func testCallNonexistentSymbolEvaluatorConstant() { let expression = AnyExpression("foo(1, 2)") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.function("foo", arity: 2))) } } func testConstantThatIsNotASymbolEvaluatorDoesntConflictWithFunctionSymbol() { let expression = AnyExpression( "foo('foo', 'bar')", constants: [ "foo": "foo", ], symbols: [ .function("foo", arity: 2): { ($0[1] as! String) + ($0[0] as! String) }, ] ) XCTAssertEqual(try expression.evaluate(), "barfoo") } func testCallExpressionSymbolEvaluatorConstantWithNonNumericArgument() { let expression = AnyExpression("foo(1, 'foo')", constants: [ "foo": { $0[0] + $0[1] } as Expression.SymbolEvaluator, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.function("foo", arity: 2), [1.0, "foo"])) } } func testCallNonExpressionSymbolEvaluatorConstant() { let expression = AnyExpression("foo(1, 2)", constants: [ "foo": "foo", ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("()"), ["foo", 1.0, 2.0])) } } func testCallNonExpressionSymbolEvaluatorImpureSymbol() { let expression = AnyExpression("foo(1, 2)", symbols: [ .variable("foo"): { _ in "foo" }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("()"), ["foo", 1.0, 2.0])) } } func testCallNonExpressionSymbolEvaluatorPureSymbol() { let expression = AnyExpression("foo(1, 2)", options: .pureSymbols, symbols: [ .variable("foo"): { _ in "foo" }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("()"), ["foo", 1.0, 2.0])) } } func testCallNonExpressionSymbolEvaluatorPureSymbolWhenFuncOfDifferentArityExists() { let expression = AnyExpression("foo(1, 2)", options: .pureSymbols, symbols: [ .function("foo", arity: 1): { _ in 0 }, .variable("foo"): { _ in "foo" }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("()"), ["foo", 1.0, 2.0])) } } // MARK: Numeric types func testAddNumbers() { let expression = AnyExpression("4 + 5") XCTAssertEqual(try expression.evaluate(), 9) } func testMathConstants() { let expression = AnyExpression("pi") XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), Double.pi) } func testAddNumericConstants() { let expression = AnyExpression("a + b", constants: [ "a": UInt64(4), "b": 5, ]) XCTAssertEqual(try expression.evaluate(), 9) } func testPreserveNumericPrecision() { let expression = AnyExpression("true ? a : b", constants: [ "a": UInt64.max, "b": Int64.min, ]) XCTAssertEqual(try expression.evaluate(), UInt64.max) } func testAddVeryLargeNumericConstants() { let expression = AnyExpression("a + b", constants: [ "a": Int64.max, "b": Int64.max, ]) XCTAssertEqual(try expression.evaluate(), Double(Int64.max) + Double(Int64.max)) } func testNaN() { let expression = AnyExpression("NaN + 5", constants: ["NaN": Double.nan]) XCTAssertEqual(try (expression.evaluate() as Double).isNaN, true) } func testEvilEdgeCase() { let evilValue = (-Double.nan) // exactly matches mask let expression = AnyExpression("evil + 5", constants: ["evil": evilValue]) XCTAssertEqual(try (expression.evaluate() as Double).bitPattern, evilValue.bitPattern) } func testEvilEdgeCase2() { let evilValue = Double(bitPattern: (-Double.nan).bitPattern + 1 + 4) // outside range of stored variables let expression = AnyExpression("evil + 5", constants: ["evil": evilValue]) XCTAssertEqual(try (expression.evaluate() as Double).bitPattern, (evilValue + 5).bitPattern) } func testEvilEdgeCase3() { let evilValue = Double(bitPattern: (-Double.nan).bitPattern - 1) // outside range of stored variables let expression = AnyExpression("evil + 5", constants: ["evil": evilValue]) XCTAssertEqual(try (expression.evaluate() as Double).bitPattern, (evilValue + 5).bitPattern) } func testFloatNaN() { let expression = AnyExpression("NaN + 5", constants: ["NaN": Float.nan]) XCTAssertEqual(try (expression.evaluate() as Double).isNaN, true) } func testInfinity() { let expression = AnyExpression("1/0") XCTAssertEqual(try (expression.evaluate() as Double).isInfinite, true) } func testCGFloatTreatedAsDouble() throws { let expression = AnyExpression("foo + 5", constants: ["foo": CGFloat(5)]) let result: Any = try expression.evaluate() XCTAssertEqual("\(type(of: result))", "Double") XCTAssertEqual(result as? Double, 10) } func testFontWeightTypePreserved() throws { #if swift(>=4) #if os(macOS) let expression = AnyExpression("foo", constants: ["foo": NSFont.Weight(5)]) let result: Any = try expression.evaluate() XCTAssert(type(of: result) is NSFont.Weight.Type) #endif #endif } // MARK: String concatenation func testAddStringConstants() { let expression = AnyExpression("a + b", constants: [ "a": "foo", "b": "bar", ]) XCTAssertEqual(try expression.evaluate(), "foobar") } func testAddNumericConstantsWithString() { let expression = AnyExpression("a + b == 9 ? c : ''", constants: [ "a": 4, "b": 5, "c": "foo", ]) XCTAssertEqual(try expression.evaluate(), "foo") } func testAddStringLiterals() { let expression = AnyExpression("'foo' + 'bar'") XCTAssertEqual(try expression.evaluate(), "foobar") } func testAddNumberToString() { let expression = AnyExpression("5 + 'foo'") XCTAssertEqual(try expression.evaluate(), "5foo") } func testAddStringToInt() { let expression = AnyExpression("'foo' + 5") XCTAssertEqual(try expression.evaluate(), "foo5") } func testAddStringToBigInt() { let expression = AnyExpression("'foo' + bar", constants: ["bar": UInt64.max]) XCTAssertEqual(try expression.evaluate(), "foo\(UInt64.max)") } func testAddStringToDouble() { let expression = AnyExpression("'foo' + 5.1") XCTAssertEqual(try expression.evaluate(), "foo5.1") } func testAddStringToFalse() { let expression = AnyExpression("'foo' + false") XCTAssertEqual(try expression.evaluate(), "foofalse") } func testAddStringToTrue() { let expression = AnyExpression("'foo' + true") XCTAssertEqual(try expression.evaluate(), "footrue") } func testAddStringToArray() { let expression = AnyExpression("'foo' + [1,2]") XCTAssertEqual(try expression.evaluate(), "foo[1, 2]") } func testAddStringToDictionary() { let expression = AnyExpression("'foo' + bar", constants: [ "bar": [1.0: 2.0], ]) XCTAssertEqual(try expression.evaluate(), "foo[1: 2]") } func testAddStringToRange() { let expression = AnyExpression("'foo' + (1...2)") XCTAssertEqual(try expression.evaluate(), "foo1...2") } func testAddStringToHalfOpenRange() { let expression = AnyExpression("'foo' + (1..<2)") XCTAssertEqual(try expression.evaluate(), "foo1..<2") } func testAddStringToPartialRangeUpTo() { let expression = AnyExpression("'foo' + (..<2)") XCTAssertEqual(try expression.evaluate(), "foo..<2") } func testAddStringToPartialRangeThrough() { let expression = AnyExpression("'foo' + (...2)") XCTAssertEqual(try expression.evaluate(), "foo...2") } func testAddStringToPartialRangeFrom() { let expression = AnyExpression("'foo' + range", constants: [ "range": PartialRangeFrom(1), ]) XCTAssertEqual(try expression.evaluate(), "foo1...") } func testAddStringToCountablePartialRangeFrom() { let expression = AnyExpression("'foo' + (1...)") XCTAssertEqual(try expression.evaluate(), "foo1...") } func testAddStringVariables() { let expression = AnyExpression("a + b", symbols: [ .variable("a"): { _ in "foo" }, .variable("b"): { _ in "bar" }, ]) XCTAssertEqual(expression.symbols, [.variable("a"), .variable("b"), .infix("+")]) XCTAssertEqual(try expression.evaluate(), "foobar") } func testAddMixedConstantsAndVariables() { let expression = AnyExpression( "a + b + c", constants: [ "a": "foo", "b": "bar", ], symbols: [ .variable("c"): { _ in "baz" }, ] ) XCTAssertEqual(expression.symbols, [.variable("c"), .infix("+")]) XCTAssertEqual(try expression.evaluate(), "foobarbaz") } // MARK: Boolean logic func testMixedConstantsAndVariables() { let expression = AnyExpression( "foo ? #F00 : #00F", constants: [ "#F00": "red", "#00F": "blue", ], symbols: [ .variable("foo"): { _ in false }, ] ) XCTAssertEqual(expression.symbols, [.variable("foo"), .infix("?:")]) XCTAssertEqual(try expression.evaluate(), "blue") } func testEquateStrings() { let constants: [String: Any] = [ "a": "foo", "b": "bar", "c": "bar", ] let expression1 = AnyExpression("a == b", constants: constants) XCTAssertFalse(try expression1.evaluate()) let expression2 = AnyExpression("a != b", constants: constants) XCTAssertTrue(try expression2.evaluate()) let expression3 = AnyExpression("b == c", constants: constants) XCTAssertTrue(try expression3.evaluate()) let expression4 = AnyExpression("b != c", constants: constants) XCTAssertFalse(try expression4.evaluate()) } func testEquateNSObjects() { let object1 = NSObject() let object2 = NSObject() let constants: [String: Any] = [ "a": object1, "b": object2, "c": object2, ] let expression1 = AnyExpression("a == b", constants: constants) XCTAssertFalse(try expression1.evaluate()) let expression2 = AnyExpression("a != b", constants: constants) XCTAssertTrue(try expression2.evaluate()) let expression3 = AnyExpression("b == c", constants: constants) XCTAssertTrue(try expression3.evaluate()) } func testEquateArrays() { let constants: [String: Any] = [ "a": ["hello", "world"], "b": ["goodbye", "world"], "c": ["goodbye", "world"], ] let expression1 = AnyExpression("a == b", constants: constants) XCTAssertFalse(try expression1.evaluate()) let expression2 = AnyExpression("a != b", constants: constants) XCTAssertTrue(try expression2.evaluate()) let expression3 = AnyExpression("b == c", constants: constants) XCTAssertTrue(try expression3.evaluate()) } func testEquateDictionaries() { let constants: [String: Any] = [ "a": ["hello": "world"], "b": ["goodbye": "world"], "c": ["goodbye": "world"], ] let expression1 = AnyExpression("a == b", constants: constants) XCTAssertFalse(try expression1.evaluate()) let expression2 = AnyExpression("a != b", constants: constants) XCTAssertTrue(try expression2.evaluate()) let expression3 = AnyExpression("b == c", constants: constants) XCTAssertTrue(try expression3.evaluate()) } func testEquateHashableStructs() { let a = HashableStruct(foo: 4) let b = HashableStruct(foo: 5) let c = HashableStruct(foo: 5) let constants: [String: Any] = [ "a": a, "b": b, "c": c, ] let expression1 = AnyExpression("a == b", constants: constants) XCTAssertFalse(try expression1.evaluate()) let expression2 = AnyExpression("a != b", constants: constants) XCTAssertTrue(try expression2.evaluate()) let expression3 = AnyExpression("b == c", constants: constants) XCTAssertTrue(try expression3.evaluate()) } func testEquateTuples() { let tuples: [Any] = [ (1, 2), (1, 2, 3), (1, 2, 3, 4), (1, 2, 3, 4, 5), (1, 2, 3, 4, 5, 6), ] for tuple in tuples { let expression1 = AnyExpression("a == b", constants: [ "a": tuple, "b": tuple, ]) XCTAssertTrue(try expression1.evaluate()) let expression2 = AnyExpression("a == b", constants: [ "a": tuple, "b": (1, 3), ]) do { let result: Bool = try expression2.evaluate() XCTAssertFalse(result) } catch let error as Expression.Error { XCTAssertEqual(error, .typeMismatch(.infix("=="), [tuple, (1, 3)])) } catch { XCTFail() } } } func testGreaterThanWithLargeIntegers() { let expression = AnyExpression("a > b", constants: [ "a": Int64.max, "b": Int64.max - 1000, ]) XCTAssertTrue(try expression.evaluate()) } func testLessThanEqualWithLargeIntegers() { let expression = AnyExpression("a <= b", constants: [ "a": Int64.max, "b": Int64.max - 1000, ]) XCTAssertFalse(try expression.evaluate()) } func testAndOperatorReturnsBool() { let expression = AnyExpression("a && b", constants: [ "a": true, "b": true, ]) XCTAssertTrue(try expression.evaluate()) } func testOrOperatorReturnsBool() { let expression = AnyExpression("a || b", constants: [ "a": false, "b": false, ]) XCTAssertFalse(try expression.evaluate()) } func testNotNaNEqualsNan() { let expression = AnyExpression("NaN == NaN", constants: [ "NaN": Double.nan, ]) XCTAssertFalse(try expression.evaluate()) } func testNaNNotEqualToNan() { let expression = AnyExpression("NaN != NaN", constants: [ "NaN": Double.nan, ]) XCTAssertTrue(try expression.evaluate()) } func testNotFloatNaNEqualsNan() { let expression = AnyExpression("NaN == NaN", constants: [ "NaN": Float.nan, ]) XCTAssertFalse(try expression.evaluate()) } func testEqualsOperatorWhenBooleansDisabled() { let expression = AnyExpression("5 == 6", options: []) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.infix("=="))) } } func testCustomEqualsOperatorWhenBooleansDisabled() { let expression = AnyExpression("5 == 6", options: [], symbols: [ .infix("=="): { _ in true }, ]) XCTAssertTrue(try expression.evaluate()) } func testCustomEqualsOperatorWhenBooleansEnabled() { let expression = AnyExpression("5 == 6", symbols: [ .infix("=="): { _ in true }, ]) XCTAssertTrue(try expression.evaluate()) } // MARK: Optionals func testNilString() { let null: String? = nil let expression = AnyExpression("foo + 'bar'", constants: ["foo": null as Any]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), [nil as Any? as Any, "bar"])) } } func testNilString2() { let null: String? = nil let expression1 = AnyExpression("foo == nil ? 'bar' : 'foo'", constants: ["foo": null as Any]) XCTAssertEqual(try expression1.evaluate(), "bar") let expression2 = AnyExpression("foo == nil ? 'bar' : 'foo'", constants: ["foo": "notnull"]) XCTAssertEqual(try expression2.evaluate(), "foo") } func testIOUNilString() { let null: String! = nil let expression = AnyExpression("foo + 'bar'", constants: ["foo": null as Any]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), [NSNull(), "bar"])) } } func testOptionalOptionalNilString() { let null: String?? = nil let expression = AnyExpression("foo + 'bar'", constants: ["foo": null as Any]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), [nil as Any? as Any, "bar"])) } } func testOptionalOptionalNonnilString() { let foo: String?? = "foo" let expression = AnyExpression("foo + 'bar'", constants: ["foo": foo as Any]) XCTAssertEqual(try expression.evaluate(), "foobar") } func testNullCoalescing() { let null: String? = nil let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": null as Any]) XCTAssertEqual(try expression.evaluate(), "bar") } func testNullCoalescing2() { let foo: String? = "foo" let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": foo as Any]) XCTAssertEqual(try expression.evaluate(), "foo") } func testIUONullCoalescing() { let null: String! = nil let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": null as Any]) XCTAssertEqual(try expression.evaluate(), "bar") } func testIUONullCoalescing2() { let foo: String! = "foo" let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": foo as Any]) XCTAssertEqual(try expression.evaluate(), "foo") } func testNSNullCoalescing() { let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": NSNull()]) XCTAssertEqual(try expression.evaluate(), "bar") } func testNonNullCoalescing() { let expression = AnyExpression("foo ?? 'bar'", constants: ["foo": "foo"]) XCTAssertEqual(try expression.evaluate(), "foo") } func testNullEqualsString() { let null: String? = nil let expression = AnyExpression("foo == 'bar'", constants: ["foo": null as Any]) XCTAssertFalse(try expression.evaluate()) } func testOptionalStringEqualsString() { let null: String? = "bar" let expression = AnyExpression("foo == 'bar'", constants: ["foo": null as Any]) XCTAssertTrue(try expression.evaluate()) } func testNullEqualsDouble() { let null: Double? = nil let expression = AnyExpression("foo == 5.5", constants: ["foo": null as Any]) XCTAssertFalse(try expression.evaluate()) } func testOptionalDoubleEqualsDouble() { let null: Double? = 5.5 let expression = AnyExpression("foo == 5.5", constants: ["foo": null as Any]) XCTAssertTrue(try expression.evaluate()) } func testEvaluateNilAsString() { let expression = AnyExpression("nil") XCTAssertThrowsError(try expression.evaluate() as String) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(String.self, nil as Any? as Any)) } } func testEvaluateNilAsOptionalString() { let expression = AnyExpression("nil") XCTAssertNil(try expression.evaluate() as String?) } // MARK: Errors func testUnknownOperator() { let expression = AnyExpression("'foo' %% 'bar'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.infix("%%"))) } } func testUnknownVariable() { let expression = AnyExpression("foo") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.variable("foo"))) } } func testBinaryTernary() { let expression = AnyExpression("'foo' ?: 'bar'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .undefinedSymbol(.infix("?:"))) } } func testTernaryWithNonBooleanArgument() { let expression = AnyExpression("'foo' ? 1 : 2") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("?:"), ["foo", 1.0, 2.0])) } } func testNotOperatorWithNonBooleanArgument() { let expression = AnyExpression("!'foo'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.prefix("!"), ["foo"])) } } func testAddDates() { let expression = AnyExpression("a + b", constants: [ "a": Date(), "b": Date(), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), [Date(), Date()])) } } func testCompareObjects() { let expression = AnyExpression("a > b", constants: [ "a": NSObject(), "b": NSObject(), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix(">"), [NSObject(), NSObject()])) } } func testAddStringAndNil() { let expression = AnyExpression("a + b", constants: [ "a": "foo", "b": nil as Any? as Any, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), ["foo", nil as Any? as Any])) } } func testAddStringAndNSNull() { let expression = AnyExpression("a + b", constants: [ "a": "foo", "b": NSNull(), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), ["foo", NSNull()])) } } func testAddIntAndNil() { let expression = AnyExpression("a + b", constants: [ "a": 5, "b": nil as Any? as Any, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("+"), [5.0, nil as Any? as Any])) } } func testMultiplyIntAndNil() { let expression = AnyExpression("a * b", constants: [ "a": 5, "b": nil as Any? as Any, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("*"), [5.0, nil as Any? as Any])) } } func testAndBoolAndNil() { let expression = AnyExpression("a && b", constants: [ "a": true, "b": nil as Any? as Any, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("&&"), [true, nil as Any? as Any])) } } func testTypeMismatch() { let expression = AnyExpression("5 / 'foo'") XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .typeMismatch(.infix("/"), [5.0, "foo"])) } } func testCastStringAsDouble() { let expression = AnyExpression("'foo' + 'bar'") XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(Double.self, "foobar")) } } func testCastDoubleAsDate() { let expression = AnyExpression("5.6") XCTAssertThrowsError(try expression.evaluate() as NSDate) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(NSDate.self, 5.6)) } } func testCastNilAsDouble() { let expression = AnyExpression("nil") XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(Double.self, nil as Any? as Any)) } } func testCastDoubleAsStruct() { let expression = AnyExpression("5") XCTAssertThrowsError(try expression.evaluate() as HashableStruct) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(HashableStruct.self, 5.0)) } } func testCastNSNullAsDouble() { let expression = AnyExpression("null", constants: ["null": NSNull()]) XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch(Double.self, NSNull())) } } func testCastStringAsArray() { let expression = AnyExpression("'foo'") XCTAssertThrowsError(try expression.evaluate() as [Any]) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch([Any].self, "foo")) } } func testCastStringArrayAsIntArray() { let expression = AnyExpression("['foo']") XCTAssertThrowsError(try expression.evaluate() as [Int]) { error in XCTAssertEqual(error as? Expression.Error, .resultTypeMismatch([Int].self, [Any]())) } } func testDisableNullCoalescing() { let expression = AnyExpression("nil ?? 'foo'", symbols: [ .infix("??"): { _ in throw AnyExpression.Error.message("Disabled") }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("Disabled")) } } func testDisableVariableSymbol() { let expression = AnyExpression( Expression.parse("foo + pi"), pureSymbols: { symbol in if case .variable("foo") = symbol { return { _ in throw AnyExpression.Error.message("Disabled") } } return nil } ) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("Disabled")) } } func testDisableVariableSymbol2() { let expression = AnyExpression( Expression.parse("foo + pi"), impureSymbols: { symbol in if case .variable("foo") = symbol { return { _ in throw AnyExpression.Error.message("Disabled") } } return nil } ) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .message("Disabled")) } } func testCompareEquatableStructs() { let expression = AnyExpression("a == b", constants: [ "a": EquatableStruct(foo: 1), "b": EquatableStruct(foo: 1), ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssert("\(error)".contains("Hashable")) } } func testTooFewArguments() { let expression = AnyExpression("pow(4)") XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .arityMismatch(.function("pow", arity: 2))) } } func testTooFewArgumentsForCustomFunction() { let expression = AnyExpression("foo(4)", symbols: [ .function("foo", arity: 2): { $0 }, ]) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arityMismatch(.function("foo", arity: 2))) } } func testTooFewArgumentsWithAdvancedInitializer() { let expression = AnyExpression(Expression.parse("pow(4)"), pureSymbols: { _ in nil }) XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .arityMismatch(.function("pow", arity: 2))) } } func testTooFewArgumentsForCustomFunctionWithAdvancedInitializer() { let expression = AnyExpression(Expression.parse("foo(4)"), pureSymbols: { symbol in switch symbol { case .function("foo", arity: 2): return { $0 } default: return nil } }) XCTAssertThrowsError(try expression.evaluate() as Any) { error in XCTAssertEqual(error as? Expression.Error, .arityMismatch(.function("foo", arity: 2))) } } func testTooManyArguments() { let expression = AnyExpression("pow(4,5,6)") XCTAssertThrowsError(try expression.evaluate() as Double) { error in XCTAssertEqual(error as? Expression.Error, .arityMismatch(.function("pow", arity: 2))) } } // MARK: Return type casting func testCastBoolResultAsDouble() { let expression = AnyExpression("5 > 4") XCTAssertEqual(try expression.evaluate(), 1.0) } func testCastDoubleResultAsInt() { let expression = AnyExpression("57.5") XCTAssertEqual(try expression.evaluate(), 57) } func testCastDoubleResultAsInt8() { let expression = AnyExpression("57.5") XCTAssertEqual(try expression.evaluate() as Int8, 57) } func testCastDoubleResultAsOptionalInt8() { let expression = AnyExpression("57.5") XCTAssertEqual(try expression.evaluate() as Int8?, 57) } func testCastDoubleResultAsCGFloat() { let expression = AnyExpression("57.5") XCTAssertEqual(try expression.evaluate() as CGFloat, 57.5) } func testCastDoubleResultAsOptionalCGFloat() { let expression = AnyExpression("57.5") XCTAssertEqual(try expression.evaluate() as CGFloat?, 57.5) } func testCastNonzeroResultAsBool() { let expression = AnyExpression("0.6") XCTAssertEqual(try expression.evaluate(), true) } func testCastZeroResultAsBool() { let expression = AnyExpression("0") XCTAssertEqual(try expression.evaluate(), false) } func testCastZeroResultAsOptionalBool() { let expression = AnyExpression("0") XCTAssertEqual(try expression.evaluate() as Bool?, false) } func testCastDoubleAsString() { let expression = AnyExpression("5.6") XCTAssertEqual(try expression.evaluate(), "5.6") } func testCastDoubleAsSubstring() { let expression = AnyExpression("5.6") XCTAssertEqual(try expression.evaluate(), Substring("5.6")) } func testCastDoubleAsOptionalString() { let expression = AnyExpression("5.6") XCTAssertEqual(try expression.evaluate() as String?, "5.6") } func testCastDoubleAsOptionalSubstring() { let expression = AnyExpression("5.6") XCTAssertEqual(try expression.evaluate() as Substring?, "5.6") } func testCastDoubleResultAsOptionalDouble() { let expression = AnyExpression("5 + 4") XCTAssertEqual(try expression.evaluate() as Double?, 9) } func testCastInt8ResultAsDouble() { let expression = AnyExpression("foo", constants: ["foo": Int8(5)]) XCTAssertEqual(try expression.evaluate() as Double, 5) } func testCastNilResultAsOptionalDouble() { let expression = AnyExpression("nil") XCTAssertEqual(try expression.evaluate() as Double?, nil) } func testCastNSNullResultAsOptionalDouble() { let expression = AnyExpression("null", constants: ["null": NSNull()]) XCTAssertEqual(try expression.evaluate() as Double?, nil) } func testCastNilResultAsAny() { let expression = AnyExpression("nil") XCTAssertEqual("try \(expression.evaluate() as Any)", "nil") } func testCastNilResultAsOptionalAny() { let expression = AnyExpression("nil") XCTAssertNil(try expression.evaluate() as Any?) } func testCastNSNullResultAsAny() { let expression = AnyExpression("null", constants: ["null": NSNull()]) XCTAssertEqual("try \(expression.evaluate() as Any)", "nil") } func testCastNSNullResultAsOptionalAny() { let expression = AnyExpression("nil", constants: ["null": NSNull()]) XCTAssertNil(try expression.evaluate() as Any?) } func testCastBoolResultAsOptionalBool() { let expression = AnyExpression("5 > 4") XCTAssertEqual(try expression.evaluate() as Bool?, true) } func testCastBoolResultAsOptionalDouble() { let expression = AnyExpression("5 > 4") XCTAssertEqual(try expression.evaluate() as Double?, 1) } func testCastBoolResultAsImplicitlyUnwrappedOptionalDouble() { #if !swift(>=3.4) || (swift(>=4) && !swift(>=4.1.5)) let expression = AnyExpression("5 > 4") XCTAssertEqual(try expression.evaluate() as Double!, 1) #endif } func testCastStringAsSubstring() { let expression = AnyExpression("'foo'") XCTAssertEqual(try expression.evaluate(), Substring("foo")) } func testCastStringAsNSString() { let expression = AnyExpression("'foo'") XCTAssertEqual(try expression.evaluate(), "foo" as NSString) } func testCastStringAsOptionalNSString() { let expression = AnyExpression("'foo'") XCTAssertEqual(try expression.evaluate(), "foo" as NSString?) } func testCastSubstringAsString() { let expression = AnyExpression("foo", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate(), "foo") } func testCastSubstringAsNSString() { let expression = AnyExpression("foo", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate(), "foo" as NSString) } func testCastSubstringAsOptionalNSString() { let expression = AnyExpression("foo", constants: [ "foo": Substring("foo"), ]) XCTAssertEqual(try expression.evaluate(), "foo" as NSString?) } func testCastArrayAsArraySlice() { let expression = AnyExpression("[3, 4]") XCTAssertEqual(try expression.evaluate(), ArraySlice([3, 4])) } func testCastArrayAsNSArray() { let expression = AnyExpression("[3, 4]") XCTAssertEqual(try expression.evaluate(), [3, 4] as NSArray) } func testCastArraySliceAsNSArray() { let expression = AnyExpression("[3, 4]", constants: [ "array": ArraySlice([3.0, 4.0]), ]) XCTAssertEqual(try expression.evaluate(), [3, 4] as NSArray) } func testCastNSArrayAsArraySlice() { let expression = AnyExpression("array", constants: [ "array": [3, 4] as NSArray, ]) XCTAssertEqual(try expression.evaluate(), ArraySlice([3, 4])) } func testCastNSArrayAsArray() { let expression = AnyExpression("array", constants: [ "array": [3, 4] as NSArray, ]) XCTAssertEqual(try expression.evaluate(), [3, 4]) } // MARK: Optimization func testStringLiteralsInlined() { let expression = AnyExpression("foo('bar', 'baz')") XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testNumericConstantsInlined() { let expression = AnyExpression("foo(bar)", constants: ["bar": 5]) XCTAssertEqual(expression.symbols, [.function("foo", arity: 1)]) } func testStringConstantsInlined() { let expression = AnyExpression("foo(bar)", constants: ["bar": "bar"]) XCTAssertEqual(expression.symbols, [.function("foo", arity: 1)]) } func testVariableSymbolNotInlined() { var foo = 5 let expression = AnyExpression("foo", options: .pureSymbols, symbols: [ .variable("foo"): { _ in foo }, ]) XCTAssertEqual(expression.symbols, [.variable("foo")]) XCTAssertEqual(try expression.evaluate(), foo) foo += 1 XCTAssertEqual(try expression.evaluate(), foo) } func testArrayConstantsInlined() { let expression = AnyExpression("foo[bar]", constants: ["foo": ["baz"], "bar": 0]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), "baz") } func testArraySymbolNotInlined() { let expression = AnyExpression("foo[0]", options: .pureSymbols, symbols: [.array("foo"): { _ in 5 }]) XCTAssertEqual(expression.symbols, [.array("foo")]) XCTAssertEqual(expression.description, "foo[0]") } func testNullCoalescingOperatorInlined() { let expression = AnyExpression("maybe ?? 'foo'", constants: ["maybe": nil as Any? as Any]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), "foo") } func testTimesOperatorInlinedForDoubles() { let expression = AnyExpression("5 * foo", constants: ["foo": 5]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), 25) } func testPlusOperatorInlinedForDoubles() { let expression = AnyExpression("5 + foo", constants: ["foo": 5]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), 10) } func testPlusOperatorInlinedForStrings() { let expression = AnyExpression("5 + foo", constants: ["foo": "bar"]) XCTAssertEqual(expression.symbols, []) XCTAssertEqual(try expression.evaluate(), "5bar") } func testBooleanAndOperatorInlined() { let expression = AnyExpression("true && false") XCTAssertEqual(expression.symbols, []) XCTAssertFalse(try expression.evaluate()) } func testPureFunctionResultNotMangledByInlining() { let expression = AnyExpression("foo('bar')", options: .pureSymbols, symbols: [ .function("foo", arity: 1): { args in "foo\(args[0])" }, ]) XCTAssertEqual(try expression.evaluate(), "foobar") XCTAssertEqual(try expression.evaluate(), "foobar") } func testPureFunctionResultNotMangledByDeferredInlining() { let expression = AnyExpression("foo(5 + 6)", options: .pureSymbols, symbols: [ .function("foo", arity: 1): { args in "foo\(args[0])" }, ]) XCTAssertEqual(try expression.evaluate(), "foo11.0") XCTAssertEqual(try expression.evaluate(), "foo11.0") } func testOptimizerDisabled() { let expression = AnyExpression("3 * 5", options: .noOptimize) XCTAssertEqual(expression.symbols, [.infix("*")]) XCTAssertEqual(try expression.evaluate(), 15) } func testOptimizerDisabledWithPureSymbols() { let expression = AnyExpression("foo(3 * 5)", options: [.noOptimize, .pureSymbols], symbols: [ .function("foo", arity: 1): { args in args[0] }, ]) XCTAssertEqual(expression.symbols, [.infix("*"), .function("foo", arity: 1)]) XCTAssertEqual(try expression.evaluate(), 15) } // MARK: Symbol precedence func testConstantTakesPrecedenceOverSymbol() { let expression = AnyExpression( "foo", constants: ["foo": "foo"], symbols: [.variable("foo"): { _ in "bar" }] ) XCTAssertEqual(try expression.evaluate(), "foo") } func testArrayConstantTakesPrecedenceOverArraySymbol() { let expression = AnyExpression( "foo[0]", constants: [ "foo": [4], ], symbols: [ .array("foo"): { _ in 5 }, ] ) XCTAssertEqual(try expression.evaluate(), 4) } func testArrayConstantTakesPrecedenceOverIntSymbol() { let expression = AnyExpression( "foo[0]", constants: [ "foo": 4, ], symbols: [ .array("foo"): { _ in 5 }, ] ) XCTAssertEqual(try expression.evaluate(), 5) } func testArraySymbolTakesPrecedenceOverIntConstant() { let expression = AnyExpression( "foo[0]", options: .pureSymbols, symbols: [ .variable("foo"): { _ in 4 }, .array("foo"): { _ in 5 }, ] ) XCTAssertEqual(try expression.evaluate(), 5) } func testArraySymbolTakesPrecedenceOverVariable() { let expression = AnyExpression("foo[0]", symbols: [ .variable("foo"): { _ in 4 }, .array("foo"): { _ in 5 }, ]) XCTAssertEqual(try expression.evaluate(), 5) } func testArraySymbolTakesPrecedenceOverVariableWithPureSymbols() { let expression = AnyExpression( "foo[0]", options: .pureSymbols, symbols: [ .variable("foo"): { _ in 4 }, .array("foo"): { _ in 5 }, ] ) XCTAssertEqual(try expression.evaluate(), 5) } func testImpureFunctionSymbolTakesPrecedenceOverSymbolEvaluatorConstant() { let expression = AnyExpression( "foo('foo', 'bar')", constants: [ "foo": { ($0[0] as! String) + ($0[1] as! String) } as AnyExpression.SymbolEvaluator, ], symbols: [ .function("foo", arity: 2): { ($0[1] as! String) + ($0[0] as! String) }, ] ) XCTAssertEqual(try expression.evaluate(), "barfoo") XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testPureFunctionSymbolTakesPrecedenceOverSymbolEvaluatorConstant() { let expression = AnyExpression( "foo('foo', 'bar')", options: .pureSymbols, constants: [ "foo": { ($0[0] as! String) + ($0[1] as! String) } as AnyExpression.SymbolEvaluator, ], symbols: [ .function("foo", arity: 2): { ($0[1] as! String) + ($0[0] as! String) }, ] ) XCTAssertEqual(try expression.evaluate(), "barfoo") XCTAssertEqual(expression.symbols, []) } func testImpureFunctionSymbolTakesPrecedenceOverImpureSymbolEvaluatorSymbol() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression( parsedExpression, impureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in { _ in 3 } as Expression.SymbolEvaluator } case .function("foo", arity: 2): return { _ in 2 } default: return nil } } ) XCTAssertEqual(try expression.evaluate(), 2) XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } func testPureFunctionSymbolTakesPrecedenceOverPureSymbolEvaluatorSymbol() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression( parsedExpression, pureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in { _ in 3 } as Expression.SymbolEvaluator } case .function("foo", arity: 2): return { _ in 2 } default: return nil } } ) XCTAssertEqual(try expression.evaluate(), 2) XCTAssertEqual(expression.symbols, []) } func testImpureFunctionSymbolTakesPrecedenceOverPureSymbolEvaluatorSymbol() { let parsedExpression = Expression.parse("foo(1, 2)") let expression = AnyExpression( parsedExpression, impureSymbols: { symbol in switch symbol { case .function("foo", arity: 2): return { _ in 2 } default: return nil } }, pureSymbols: { symbol in switch symbol { case .variable("foo"): return { _ in { _ in 3 } as Expression.SymbolEvaluator } default: return nil } } ) XCTAssertEqual(try expression.evaluate(), 2) XCTAssertEqual(expression.symbols, [.function("foo", arity: 2)]) } // MARK: Memory func testUnusedConstantsNotRetained() throws { weak var weakObject: NSObject? weak var weakString: NSString? let expression: AnyExpression do { let object = NSObject() weakObject = object let string = "foo" as NSString weakString = string expression = AnyExpression("foo + 5", constants: [ "foo": string, "bar": object, ]) _ = try expression.evaluate() as String } XCTAssertNil(weakObject) XCTAssertNotNil(weakString) } func testUnusedSymbolsNotRetained() throws { weak var weakObject: NSObject? weak var weakString: NSString? let expression: AnyExpression do { let object = NSObject() weakObject = object let string = "foo" as NSString weakString = string expression = AnyExpression("foo + 5", symbols: [ .variable("foo"): { _ in string }, .variable("bar"): { _ in object }, ]) _ = try expression.evaluate() as String } XCTAssertNil(weakObject) XCTAssertNotNil(weakString) } }