refactor(if): Move expressions to separate file
This commit is contained in:
136
Sources/Expression.swift
Normal file
136
Sources/Expression.swift
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
protocol Expression: CustomStringConvertible {
|
||||||
|
func evaluate(context: Context) throws -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protocol InfixOperator: Expression {
|
||||||
|
init(lhs: Expression, rhs: Expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protocol PrefixOperator: Expression {
|
||||||
|
init(expression: Expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class StaticExpression: Expression, CustomStringConvertible {
|
||||||
|
let value: Bool
|
||||||
|
|
||||||
|
init(value: Bool) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(context: Context) throws -> Bool {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "\(value)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class VariableExpression: Expression, CustomStringConvertible {
|
||||||
|
let variable: Variable
|
||||||
|
|
||||||
|
init(variable: Variable) {
|
||||||
|
self.variable = variable
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "(variable: \(variable.variable))"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a variable in the given context as boolean
|
||||||
|
func resolve(context: Context, variable: Variable) throws -> Bool {
|
||||||
|
let result = try variable.resolve(context)
|
||||||
|
var truthy = false
|
||||||
|
|
||||||
|
if let result = result as? [Any] {
|
||||||
|
truthy = !result.isEmpty
|
||||||
|
} else if let result = result as? [String:Any] {
|
||||||
|
truthy = !result.isEmpty
|
||||||
|
} else if let result = result as? Bool {
|
||||||
|
truthy = result
|
||||||
|
} else if let result = result as? Int {
|
||||||
|
truthy = result > 0
|
||||||
|
} else if let result = result as? Float {
|
||||||
|
truthy = result > 0
|
||||||
|
} else if let result = result as? Double {
|
||||||
|
truthy = result > 0
|
||||||
|
} else if result != nil {
|
||||||
|
truthy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return truthy
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(context: Context) throws -> Bool {
|
||||||
|
return try resolve(context: context, variable: variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {
|
||||||
|
let expression: Expression
|
||||||
|
|
||||||
|
init(expression: Expression) {
|
||||||
|
self.expression = expression
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "not \(expression)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(context: Context) throws -> Bool {
|
||||||
|
return try !expression.evaluate(context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||||
|
let lhs: Expression
|
||||||
|
let rhs: Expression
|
||||||
|
|
||||||
|
init(lhs: Expression, rhs: Expression) {
|
||||||
|
self.lhs = lhs
|
||||||
|
self.rhs = rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "(\(lhs) or \(rhs))"
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(context: Context) throws -> Bool {
|
||||||
|
let lhs = try self.lhs.evaluate(context: context)
|
||||||
|
if lhs {
|
||||||
|
return lhs
|
||||||
|
}
|
||||||
|
|
||||||
|
return try rhs.evaluate(context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
|
||||||
|
let lhs: Expression
|
||||||
|
let rhs: Expression
|
||||||
|
|
||||||
|
init(lhs: Expression, rhs: Expression) {
|
||||||
|
self.lhs = lhs
|
||||||
|
self.rhs = rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "(\(lhs) and \(rhs))"
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(context: Context) throws -> Bool {
|
||||||
|
let lhs = try self.lhs.evaluate(context: context)
|
||||||
|
if !lhs {
|
||||||
|
return lhs
|
||||||
|
}
|
||||||
|
|
||||||
|
return try rhs.evaluate(context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +1,3 @@
|
|||||||
protocol Expression: CustomStringConvertible {
|
|
||||||
func evaluate(context: Context) throws -> Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protocol InfixOperator: Expression {
|
|
||||||
init(lhs: Expression, rhs: Expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protocol PrefixOperator: Expression {
|
|
||||||
init(expression: Expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class StaticExpression: Expression, CustomStringConvertible {
|
|
||||||
let value: Bool
|
|
||||||
|
|
||||||
init(value: Bool) {
|
|
||||||
self.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "\(value)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class VariableExpression: Expression, CustomStringConvertible {
|
|
||||||
let variable: Variable
|
|
||||||
|
|
||||||
init(variable: Variable) {
|
|
||||||
self.variable = variable
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "(variable: \(variable.variable))"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves a variable in the given context as boolean
|
|
||||||
func resolve(context: Context, variable: Variable) throws -> Bool {
|
|
||||||
let result = try variable.resolve(context)
|
|
||||||
var truthy = false
|
|
||||||
|
|
||||||
if let result = result as? [Any] {
|
|
||||||
truthy = !result.isEmpty
|
|
||||||
} else if let result = result as? [String:Any] {
|
|
||||||
truthy = !result.isEmpty
|
|
||||||
} else if let result = result as? Bool {
|
|
||||||
truthy = result
|
|
||||||
} else if let result = result as? Int {
|
|
||||||
truthy = result > 0
|
|
||||||
} else if let result = result as? Float {
|
|
||||||
truthy = result > 0
|
|
||||||
} else if let result = result as? Double {
|
|
||||||
truthy = result > 0
|
|
||||||
} else if result != nil {
|
|
||||||
truthy = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return truthy
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
|
||||||
return try resolve(context: context, variable: variable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {
|
|
||||||
let expression: Expression
|
|
||||||
|
|
||||||
init(expression: Expression) {
|
|
||||||
self.expression = expression
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "not \(expression)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
|
||||||
return try !expression.evaluate(context: context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
|
|
||||||
let lhs: Expression
|
|
||||||
let rhs: Expression
|
|
||||||
|
|
||||||
init(lhs: Expression, rhs: Expression) {
|
|
||||||
self.lhs = lhs
|
|
||||||
self.rhs = rhs
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "(\(lhs) or \(rhs))"
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
|
||||||
let lhs = try self.lhs.evaluate(context: context)
|
|
||||||
if lhs {
|
|
||||||
return lhs
|
|
||||||
}
|
|
||||||
|
|
||||||
return try rhs.evaluate(context: context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
|
|
||||||
let lhs: Expression
|
|
||||||
let rhs: Expression
|
|
||||||
|
|
||||||
init(lhs: Expression, rhs: Expression) {
|
|
||||||
self.lhs = lhs
|
|
||||||
self.rhs = rhs
|
|
||||||
}
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
return "(\(lhs) and \(rhs))"
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluate(context: Context) throws -> Bool {
|
|
||||||
let lhs = try self.lhs.evaluate(context: context)
|
|
||||||
if !lhs {
|
|
||||||
return lhs
|
|
||||||
}
|
|
||||||
|
|
||||||
return try rhs.evaluate(context: context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum Operator {
|
enum Operator {
|
||||||
case infix(String, Int, InfixOperator.Type)
|
case infix(String, Int, InfixOperator.Type)
|
||||||
case prefix(String, Int, PrefixOperator.Type)
|
case prefix(String, Int, PrefixOperator.Type)
|
||||||
|
|||||||
171
Tests/StencilTests/ExpressionSpec.swift
Normal file
171
Tests/StencilTests/ExpressionSpec.swift
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import Spectre
|
||||||
|
@testable import Stencil
|
||||||
|
|
||||||
|
|
||||||
|
func testExpressions() {
|
||||||
|
describe("Expression") {
|
||||||
|
$0.describe("VariableExpression") {
|
||||||
|
let expression = VariableExpression(variable: Variable("value"))
|
||||||
|
|
||||||
|
$0.it("evaluates to true when value is not nil") {
|
||||||
|
let context = Context(dictionary: ["value": "known"])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when value is unset") {
|
||||||
|
let context = Context()
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true when array variable is not empty") {
|
||||||
|
let items: [[String: Any]] = [["key":"key1","value":42],["key":"key2","value":1337]]
|
||||||
|
let context = Context(dictionary: ["value": [items]])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when array value is empty") {
|
||||||
|
let emptyItems = [[String: Any]]()
|
||||||
|
let context = Context(dictionary: ["value": emptyItems])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when dictionary value is empty") {
|
||||||
|
let emptyItems = [String:Any]()
|
||||||
|
let context = Context(dictionary: ["value": emptyItems])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when Array<Any> value is empty") {
|
||||||
|
let context = Context(dictionary: ["value": ([] as [Any])])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true when integer value is above 0") {
|
||||||
|
let context = Context(dictionary: ["value": 1])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when integer value is below 0 or below") {
|
||||||
|
let context = Context(dictionary: ["value": 0])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
|
||||||
|
let negativeContext = Context(dictionary: ["value": 0])
|
||||||
|
try expect(try expression.evaluate(context: negativeContext)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true when float value is above 0") {
|
||||||
|
let context = Context(dictionary: ["value": Float(0.5)])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when float is 0 or below") {
|
||||||
|
let context = Context(dictionary: ["value": Float(0)])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true when double value is above 0") {
|
||||||
|
let context = Context(dictionary: ["value": Double(0.5)])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false when double is 0 or below") {
|
||||||
|
let context = Context(dictionary: ["value": Double(0)])
|
||||||
|
try expect(try expression.evaluate(context: context)).to.beFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.describe("NotExpression") {
|
||||||
|
$0.it("returns truthy for positive expressions") {
|
||||||
|
let expression = NotExpression(expression: StaticExpression(value: true))
|
||||||
|
try expect(expression.evaluate(context: Context())).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("returns falsy for negative expressions") {
|
||||||
|
let expression = NotExpression(expression: StaticExpression(value: false))
|
||||||
|
try expect(expression.evaluate(context: Context())).to.beTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.describe("expression parsing") {
|
||||||
|
$0.it("can parse a variable expression") {
|
||||||
|
let expression = try parseExpression(components: ["value"])
|
||||||
|
try expect(expression.evaluate(context: Context())).to.beFalse()
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("can parse a not expression") {
|
||||||
|
let expression = try parseExpression(components: ["not", "value"])
|
||||||
|
try expect(expression.evaluate(context: Context())).to.beTrue()
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.describe("and expression") {
|
||||||
|
let expression = try! parseExpression(components: ["lhs", "and", "rhs"])
|
||||||
|
|
||||||
|
$0.it("evaluates to false with lhs false") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with rhs false") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with lhs and rhs false") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true with lhs and rhs true") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.describe("or expression") {
|
||||||
|
let expression = try! parseExpression(components: ["lhs", "or", "rhs"])
|
||||||
|
|
||||||
|
$0.it("evaluates to true with lhs true") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true with rhs true") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true with lhs and rhs true") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with lhs and rhs false") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": false]))).to.beFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.describe("multiple expression") {
|
||||||
|
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"])
|
||||||
|
|
||||||
|
$0.it("evaluates to true with one") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true with one and three") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["one": true, "three": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to true with two") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["two": true]))).to.beTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with two and three") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with two and three") {
|
||||||
|
try expect(expression.evaluate(context: Context(dictionary: ["two": true, "three": true]))).to.beFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("evaluates to false with nothing") {
|
||||||
|
try expect(expression.evaluate(context: Context())).to.beFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,12 @@ public func stencilTests() {
|
|||||||
testVariable()
|
testVariable()
|
||||||
testNode()
|
testNode()
|
||||||
testForNode()
|
testForNode()
|
||||||
|
testExpressions()
|
||||||
testIfNode()
|
testIfNode()
|
||||||
testNowNode()
|
testNowNode()
|
||||||
testInclude()
|
testInclude()
|
||||||
testInheritence()
|
testInheritence()
|
||||||
testStencil()
|
testStencil()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user