Skip to content

Instantly share code, notes, and snippets.

@nighthawk
Created July 29, 2019 20:18
Show Gist options
  • Save nighthawk/060475d0ded20788e9269054e152d76d to your computer and use it in GitHub Desktop.
Save nighthawk/060475d0ded20788e9269054e152d76d to your computer and use it in GitHub Desktop.
Eval suffix issue
import XCTest
import Eval // https://github.com/tevelee/Eval
class MiniExpressionStandardLibraryTest: XCTestCase {
private func evaluate<R>(_ expression: String, inputs: [String: Any] = [:]) -> R? {
let context = Context(variables: inputs)
let interpreter = TypedInterpreter(dataTypes: MiniExpressionStandardLibrary.dataTypes, functions: MiniExpressionStandardLibrary.functions, context: context)
let result = interpreter.evaluate(expression)
return result as? R
}
func testComposition() {
// this doesn't work; depending on the ordering of functions either the first+third or the second fails
XCTAssertEqual(evaluate("(toggle == true) and (url exists)", inputs: ["toggle": true, "url": 1]), true)
XCTAssertEqual(evaluate("(toggle == true) and (url exists)", inputs: ["toggle": true]), false)
XCTAssertEqual(evaluate("(toggle == true) and (not(url exists))", inputs: ["toggle": true]), true)
}
func testComposition2() {
// this does work...
XCTAssertEqual(evaluate("(toggle == true) and (didset url)", inputs: ["toggle": true, "url": 1]), true)
XCTAssertEqual(evaluate("(toggle == true) and (didset url)", inputs: ["toggle": true]), false)
XCTAssertEqual(evaluate("(toggle == true) and (not(didset url))", inputs: ["toggle": true]), true)
}
}
class MiniExpressionStandardLibrary {
static var dataTypes: [DataTypeProtocol] {
return [
booleanType,
]
}
static var functions: [FunctionProtocol] {
return [
boolParentheses,
boolEqualsOperator,
andOperator,
existsOperator,
didsetOperator,
]
}
// MARK: - Types
static var booleanType: DataType<Bool> {
let trueLiteral = Literal("true", convertsTo: true)
let falseLiteral = Literal("false", convertsTo: false)
return DataType(type: Bool.self, literals: [trueLiteral, falseLiteral]) { $0.value ? "true" : "false" }
}
// MARK: - Functions
static var boolEqualsOperator: Function<Bool> {
return infixOperator("==") { (lhs: Bool, rhs: Bool) in lhs == rhs }
}
static var boolParentheses: Function<Bool> {
return Function([OpenKeyword("("), Variable<Bool>("body"), CloseKeyword(")")]) { $0.variables["body"] as? Bool }
}
static var andOperator: Function<Bool> {
return infixOperator("and") { (lhs: Bool, rhs: Bool) in lhs && rhs }
}
static var existsOperator: Function<Bool> {
return suffixOperator("exists") { (expression: Any?) in expression != nil }
}
static var didsetOperator: Function<Bool> {
return prefixOperator("didset") { (expression: Any?) in expression != nil }
}
// MARK: - Operator helpers
static func infixOperator<A, B, T>(_ symbol: String, body: @escaping (A, B) -> T) -> Function<T> {
return Function([Variable<A>("lhs"), Keyword(symbol), Variable<B>("rhs")], options: .backwardMatch) {
guard let lhs = $0.variables["lhs"] as? A, let rhs = $0.variables["rhs"] as? B else { return nil }
return body(lhs, rhs)
}
}
static func prefixOperator<A, T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T> {
return Function([Keyword(symbol), Variable<A>("value")]) {
guard let value = $0.variables["value"] as? A else { return nil }
return body(value)
}
}
static func suffixOperator<A, T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T> {
return Function([Variable<A>("value"), Keyword(symbol)]) {
guard let value = $0.variables["value"] as? A else { return nil }
return body(value)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment