Parse variables as expressions

removed static boolean expressions
added test for rendering template with boolean expression
This commit is contained in:
Ilya Puchka
2017-12-23 22:01:04 +01:00
committed by David Jennes
parent 242bea54c3
commit 6649b7e716
5 changed files with 54 additions and 24 deletions

View File

@@ -1,7 +1,13 @@
public protocol Expression: CustomStringConvertible { public protocol Expression: CustomStringConvertible, Resolvable {
func evaluate(context: Context) throws -> Bool func evaluate(context: Context) throws -> Bool
} }
extension Expression {
func resolve(_ context: Context) throws -> Any? {
try "\(evaluate(context: context))"
}
}
protocol InfixOperator: Expression { protocol InfixOperator: Expression {
init(lhs: Expression, rhs: Expression) init(lhs: Expression, rhs: Expression)
} }
@@ -37,8 +43,12 @@ final class VariableExpression: Expression, CustomStringConvertible {
"(variable: \(variable))" "(variable: \(variable))"
} }
func resolve(_ context: Context) throws -> Any? {
try variable.resolve(context)
}
/// Resolves a variable in the given context as boolean /// Resolves a variable in the given context as boolean
func resolve(context: Context, variable: Resolvable) throws -> Bool { func evaluate(context: Context) throws -> Bool {
let result = try variable.resolve(context) let result = try variable.resolve(context)
var truthy = false var truthy = false
@@ -58,10 +68,6 @@ final class VariableExpression: Expression, CustomStringConvertible {
return truthy return truthy
} }
func evaluate(context: Context) throws -> Bool {
try resolve(context: context, variable: variable)
}
} }
final class NotExpression: Expression, PrefixOperator, CustomStringConvertible { final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {

View File

@@ -93,30 +93,36 @@ public class VariableNode: NodeType {
func hasToken(_ token: String, at index: Int) -> Bool { func hasToken(_ token: String, at index: Int) -> Bool {
components.count > (index + 1) && components[index] == token components.count > (index + 1) && components[index] == token
} }
func compileResolvable(_ components: [String], containedIn token: Token) throws -> Resolvable {
try (try? parser.compileExpression(components: components, token: token)) ??
parser.compileFilter(components.joined(separator: " "), containedIn: token)
}
let variable: Resolvable
let condition: Expression? let condition: Expression?
let elseExpression: Resolvable? let elseExpression: Resolvable?
if hasToken("if", at: 1) { if hasToken("if", at: 1) {
variable = try compileResolvable([components[0]], containedIn: token)
let components = components.suffix(from: 2) let components = components.suffix(from: 2)
if let elseIndex = components.firstIndex(of: "else") { if let elseIndex = components.firstIndex(of: "else") {
condition = try parser.compileExpression(components: Array(components.prefix(upTo: elseIndex)), token: token) condition = try parser.compileExpression(components: Array(components.prefix(upTo: elseIndex)), token: token)
let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ") let elseToken = Array(components.suffix(from: elseIndex.advanced(by: 1)))
elseExpression = try parser.compileResolvable(elseToken, containedIn: token) elseExpression = try compileResolvable(elseToken, containedIn: token)
} else { } else {
condition = try parser.compileExpression(components: Array(components), token: token) condition = try parser.compileExpression(components: Array(components), token: token)
elseExpression = nil elseExpression = nil
} }
} else { } else if !components.isEmpty {
variable = try compileResolvable(components, containedIn: token)
condition = nil condition = nil
elseExpression = nil elseExpression = nil
} } else {
guard let resolvable = components.first else {
throw TemplateSyntaxError(reason: "Missing variable name", token: token) throw TemplateSyntaxError(reason: "Missing variable name", token: token)
} }
let filter = try parser.compileResolvable(resolvable, containedIn: token)
return VariableNode(variable: filter, token: token, condition: condition, elseExpression: elseExpression) return VariableNode(variable: variable, token: token, condition: condition, elseExpression: elseExpression)
} }
public init(variable: Resolvable, token: Token? = nil) { public init(variable: Resolvable, token: Token? = nil) {

View File

@@ -115,12 +115,12 @@ final class ExpressionsTests: XCTestCase {
func testNotExpression() { func testNotExpression() {
it("returns truthy for positive expressions") { it("returns truthy for positive expressions") {
let expression = NotExpression(expression: StaticExpression(value: true)) let expression = NotExpression(expression: VariableExpression(variable: Variable("true")))
try expect(expression.evaluate(context: Context())).to.beFalse() try expect(expression.evaluate(context: Context())).to.beFalse()
} }
it("returns falsy for negative expressions") { it("returns falsy for negative expressions") {
let expression = NotExpression(expression: StaticExpression(value: false)) let expression = NotExpression(expression: VariableExpression(variable: Variable("false")))
try expect(expression.evaluate(context: Context())).to.beTrue() try expect(expression.evaluate(context: Context())).to.beTrue()
} }
} }

View File

@@ -200,8 +200,8 @@ final class IfNodeTests: XCTestCase {
func testRendering() { func testRendering() {
it("renders a true expression") { it("renders a true expression") {
let node = IfNode(conditions: [ let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "1")]), IfCondition(expression: VariableExpression(variable: Variable("true")), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), IfCondition(expression: VariableExpression(variable: Variable("true")), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]) IfCondition(expression: nil, nodes: [TextNode(text: "3")])
]) ])
@@ -210,8 +210,8 @@ final class IfNodeTests: XCTestCase {
it("renders the first true expression") { it("renders the first true expression") {
let node = IfNode(conditions: [ let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), IfCondition(expression: VariableExpression(variable: Variable("false")), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: true), nodes: [TextNode(text: "2")]), IfCondition(expression: VariableExpression(variable: Variable("true")), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]) IfCondition(expression: nil, nodes: [TextNode(text: "3")])
]) ])
@@ -220,8 +220,8 @@ final class IfNodeTests: XCTestCase {
it("renders the empty expression when other conditions are falsy") { it("renders the empty expression when other conditions are falsy") {
let node = IfNode(conditions: [ let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), IfCondition(expression: VariableExpression(variable: Variable("false")), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]), IfCondition(expression: VariableExpression(variable: Variable("false")), nodes: [TextNode(text: "2")]),
IfCondition(expression: nil, nodes: [TextNode(text: "3")]) IfCondition(expression: nil, nodes: [TextNode(text: "3")])
]) ])
@@ -230,8 +230,8 @@ final class IfNodeTests: XCTestCase {
it("renders empty when no truthy conditions") { it("renders empty when no truthy conditions") {
let node = IfNode(conditions: [ let node = IfNode(conditions: [
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "1")]), IfCondition(expression: VariableExpression(variable: Variable("false")), nodes: [TextNode(text: "1")]),
IfCondition(expression: StaticExpression(value: false), nodes: [TextNode(text: "2")]) IfCondition(expression: VariableExpression(variable: Variable("false")), nodes: [TextNode(text: "2")])
]) ])
try expect(try node.render(Context())) == "" try expect(try node.render(Context())) == ""

View File

@@ -90,4 +90,22 @@ final class NodeTests: XCTestCase {
try expect(try renderNodes(nodes, self.context)).toThrow(TemplateSyntaxError("Custom Error")) try expect(try renderNodes(nodes, self.context)).toThrow(TemplateSyntaxError("Custom Error"))
} }
} }
func testRenderingBooleans() {
it("can render true & false") {
try expect(Template(templateString: "{{ true }}").render()) == "true"
try expect(Template(templateString: "{{ false }}").render()) == "false"
}
it("can resolve variable") {
let template = Template(templateString: "{{ value == \"known\" }}")
try expect(template.render(["value": "known"])) == "true"
try expect(template.render(["value": "unknown"])) == "false"
}
it("can render a boolean expression") {
try expect(Template(templateString: "{{ 1 > 0 }}").render()) == "true"
try expect(Template(templateString: "{{ 1 == 2 }}").render()) == "false"
}
}
} }