feat: Support filters in if expressions

Closes #90
This commit is contained in:
Kyle Fuller
2017-01-05 20:56:09 +00:00
parent 39517b7514
commit 99efba56e9
5 changed files with 42 additions and 23 deletions

View File

@@ -59,6 +59,9 @@
- `Environment` allows you to provide a custom `Template` subclass, allowing - `Environment` allows you to provide a custom `Template` subclass, allowing
new template to use a specific subclass. new template to use a specific subclass.
- If expressions may now contain filters on variables. For example
`{% if name|uppercase == "TEST" %}` is now supported.
### Deprecations ### Deprecations
- `Template` initialisers have been deprecated in favour of using a template - `Template` initialisers have been deprecated in favour of using a template

View File

@@ -31,18 +31,18 @@ final class StaticExpression: Expression, CustomStringConvertible {
final class VariableExpression: Expression, CustomStringConvertible { final class VariableExpression: Expression, CustomStringConvertible {
let variable: Variable let variable: Resolvable
init(variable: Variable) { init(variable: Resolvable) {
self.variable = variable self.variable = variable
} }
var description: String { var description: String {
return "(variable: \(variable.variable))" return "(variable: \(variable))"
} }
/// Resolves a variable in the given context as boolean /// Resolves a variable in the given context as boolean
func resolve(context: Context, variable: Variable) throws -> Bool { func resolve(context: Context, variable: Resolvable) throws -> Bool {
let result = try variable.resolve(context) let result = try variable.resolve(context)
var truthy = false var truthy = false

View File

@@ -40,7 +40,7 @@ func findOperator(name: String) -> Operator? {
enum IfToken { enum IfToken {
case infix(name: String, bindingPower: Int, op: InfixOperator.Type) case infix(name: String, bindingPower: Int, op: InfixOperator.Type)
case prefix(name: String, bindingPower: Int, op: PrefixOperator.Type) case prefix(name: String, bindingPower: Int, op: PrefixOperator.Type)
case variable(Variable) case variable(Resolvable)
case end case end
var bindingPower: Int { var bindingPower: Int {
@@ -99,8 +99,8 @@ final class IfExpressionParser {
let tokens: [IfToken] let tokens: [IfToken]
var position: Int = 0 var position: Int = 0
init(components: [String]) { init(components: [String], tokenParser: TokenParser) throws {
self.tokens = components.map { component in self.tokens = try components.map { component in
if let op = findOperator(name: component) { if let op = findOperator(name: component) {
switch op { switch op {
case .infix(let name, let bindingPower, let cls): case .infix(let name, let bindingPower, let cls):
@@ -110,7 +110,7 @@ final class IfExpressionParser {
} }
} }
return .variable(Variable(component)) return .variable(try tokenParser.compileFilter(component))
} }
} }
@@ -154,8 +154,8 @@ final class IfExpressionParser {
} }
func parseExpression(components: [String]) throws -> Expression { func parseExpression(components: [String], tokenParser: TokenParser) throws -> Expression {
let parser = IfExpressionParser(components: components) let parser = try IfExpressionParser(components: components, tokenParser: tokenParser)
return try parser.parse() return try parser.parse()
} }
@@ -182,7 +182,7 @@ class IfNode : NodeType {
_ = parser.nextToken() _ = parser.nextToken()
} }
let expression = try parseExpression(components: components) let expression = try parseExpression(components: components, tokenParser: parser)
return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes)
} }
@@ -206,7 +206,7 @@ class IfNode : NodeType {
_ = parser.nextToken() _ = parser.nextToken()
} }
let expression = try parseExpression(components: components) let expression = try parseExpression(components: components, tokenParser: parser)
return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes) return IfNode(expression: expression, trueNodes: trueNodes, falseNodes: falseNodes)
} }

View File

@@ -4,6 +4,8 @@ import Spectre
func testExpressions() { func testExpressions() {
describe("Expression") { describe("Expression") {
let parser = TokenParser(tokens: [], environment: Environment())
$0.describe("VariableExpression") { $0.describe("VariableExpression") {
let expression = VariableExpression(variable: Variable("value")) let expression = VariableExpression(variable: Variable("value"))
@@ -103,19 +105,19 @@ func testExpressions() {
$0.describe("expression parsing") { $0.describe("expression parsing") {
$0.it("can parse a variable expression") { $0.it("can parse a variable expression") {
let expression = try parseExpression(components: ["value"]) let expression = try parseExpression(components: ["value"], tokenParser: parser)
try expect(expression.evaluate(context: Context())).to.beFalse() try expect(expression.evaluate(context: Context())).to.beFalse()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
} }
$0.it("can parse a not expression") { $0.it("can parse a not expression") {
let expression = try parseExpression(components: ["not", "value"]) let expression = try parseExpression(components: ["not", "value"], tokenParser: parser)
try expect(expression.evaluate(context: Context())).to.beTrue() try expect(expression.evaluate(context: Context())).to.beTrue()
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse() try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
} }
$0.describe("and expression") { $0.describe("and expression") {
let expression = try! parseExpression(components: ["lhs", "and", "rhs"]) let expression = try! parseExpression(components: ["lhs", "and", "rhs"], tokenParser: parser)
$0.it("evaluates to false with lhs false") { $0.it("evaluates to false with lhs false") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse() try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
@@ -135,7 +137,7 @@ func testExpressions() {
} }
$0.describe("or expression") { $0.describe("or expression") {
let expression = try! parseExpression(components: ["lhs", "or", "rhs"]) let expression = try! parseExpression(components: ["lhs", "or", "rhs"], tokenParser: parser)
$0.it("evaluates to true with lhs true") { $0.it("evaluates to true with lhs true") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
@@ -155,7 +157,7 @@ func testExpressions() {
} }
$0.describe("equality expression") { $0.describe("equality expression") {
let expression = try! parseExpression(components: ["lhs", "==", "rhs"]) let expression = try! parseExpression(components: ["lhs", "==", "rhs"], tokenParser: parser)
$0.it("evaluates to true with equal lhs/rhs") { $0.it("evaluates to true with equal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
@@ -191,7 +193,7 @@ func testExpressions() {
} }
$0.describe("inequality expression") { $0.describe("inequality expression") {
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"]) let expression = try! parseExpression(components: ["lhs", "!=", "rhs"], tokenParser: parser)
$0.it("evaluates to true with inequal lhs/rhs") { $0.it("evaluates to true with inequal lhs/rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
@@ -203,7 +205,7 @@ func testExpressions() {
} }
$0.describe("more than expression") { $0.describe("more than expression") {
let expression = try! parseExpression(components: ["lhs", ">", "rhs"]) let expression = try! parseExpression(components: ["lhs", ">", "rhs"], tokenParser: parser)
$0.it("evaluates to true with lhs > rhs") { $0.it("evaluates to true with lhs > rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
@@ -215,7 +217,7 @@ func testExpressions() {
} }
$0.describe("more than equal expression") { $0.describe("more than equal expression") {
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"]) let expression = try! parseExpression(components: ["lhs", ">=", "rhs"], tokenParser: parser)
$0.it("evaluates to true with lhs == rhs") { $0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
@@ -227,7 +229,7 @@ func testExpressions() {
} }
$0.describe("less than expression") { $0.describe("less than expression") {
let expression = try! parseExpression(components: ["lhs", "<", "rhs"]) let expression = try! parseExpression(components: ["lhs", "<", "rhs"], tokenParser: parser)
$0.it("evaluates to true with lhs < rhs") { $0.it("evaluates to true with lhs < rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
@@ -239,7 +241,7 @@ func testExpressions() {
} }
$0.describe("less than equal expression") { $0.describe("less than equal expression") {
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"]) let expression = try! parseExpression(components: ["lhs", "<=", "rhs"], tokenParser: parser)
$0.it("evaluates to true with lhs == rhs") { $0.it("evaluates to true with lhs == rhs") {
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
@@ -251,7 +253,7 @@ func testExpressions() {
} }
$0.describe("multiple expression") { $0.describe("multiple expression") {
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"]) let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"], tokenParser: parser)
$0.it("evaluates to true with one") { $0.it("evaluates to true with one") {
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue() try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()

View File

@@ -103,5 +103,19 @@ func testIfNode() {
try expect(try node.render(Context())) == "false" try expect(try node.render(Context())) == "false"
} }
} }
$0.it("supports variable filters in the if expression") {
let tokens: [Token] = [
.block(value: "if value|uppercase == \"TEST\""),
.text(value: "true"),
.block(value: "endif")
]
let parser = TokenParser(tokens: tokens, environment: Environment())
let nodes = try parser.parse()
let result = try renderNodes(nodes, Context(dictionary: ["value": "test"]))
try expect(result) == "true"
}
} }
} }