From 90cc1883767c258d46e333d6dffcf0c9cc3b4dcf Mon Sep 17 00:00:00 2001 From: Miguel Bejar Date: Sun, 15 Jan 2017 08:10:31 -0500 Subject: [PATCH 1/4] Add Newline information to block tokens --- Sources/Lexer.swift | 37 ++++++++++++++++++++++--- Sources/Tokenizer.swift | 40 ++++++++++++++++++---------- Tests/StencilTests/IfNodeSpec.swift | 26 +++++++++--------- Tests/StencilTests/IncludeSpec.swift | 4 +-- Tests/StencilTests/LexerSpec.swift | 11 ++++++++ Tests/StencilTests/NowNodeSpec.swift | 4 +-- Tests/StencilTests/ParserSpec.swift | 4 +-- 7 files changed, 89 insertions(+), 37 deletions(-) diff --git a/Sources/Lexer.swift b/Sources/Lexer.swift index e7fcd7ce..38e3a6a7 100644 --- a/Sources/Lexer.swift +++ b/Sources/Lexer.swift @@ -5,17 +5,46 @@ struct Lexer { self.templateString = templateString } + private func whiteSpaceBehavior(string: String, tagLength: Int) -> WhitespaceBehavior { + func behavior(string: String) -> WhitespaceBehavior.Behavior { + switch string { + case "+": return .keep + case "-": return .trim + default: return .unspecified + } + } + let leftIndex = string.index(string.startIndex, offsetBy: tagLength, limitedBy: string.endIndex) + let rightIndex = string.index(string.endIndex, offsetBy: -(tagLength + 1), limitedBy: string.startIndex) + let leftIndicator = leftIndex.map { ind in string[ind...ind] } + let rightIndicator = rightIndex.map { ind in string[ind...ind] } + return WhitespaceBehavior( + left: behavior(string: leftIndicator ?? ""), + right: behavior(string: rightIndicator ?? "") + ) + + } + private static let TagLength = 2 func createToken(string:String) -> Token { - func strip() -> String { - let start = string.index(string.startIndex, offsetBy: 2) - let end = string.index(string.endIndex, offsetBy: -2) + func strip(length: (Int, Int) = (Lexer.TagLength, Lexer.TagLength)) -> String { + let start = string.index(string.startIndex, offsetBy: length.0) + let end = string.index(string.endIndex, offsetBy: -length.1) return string[start.. Int { + switch b { + case .keep, .trim: + return 1 + case .unspecified: + return 0 + } + } if string.hasPrefix("{{") { return .variable(value: strip()) } else if string.hasPrefix("{%") { - return .block(value: strip()) + let behavior = whiteSpaceBehavior(string: string, tagLength: Lexer.TagLength) + let stripLengths = (Lexer.TagLength + additionalTagLength(b: behavior.left),Lexer.TagLength + additionalTagLength(b: behavior.right)) + return .block(value: strip(length: stripLengths), newline: behavior) } else if string.hasPrefix("{#") { return .comment(value: strip()) } diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 18713bc4..486633f4 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -36,6 +36,23 @@ extension String { } } +public struct WhitespaceBehavior: Equatable { + enum Behavior { + case unspecified + case trim + case keep + } + let left: Behavior + let right: Behavior + static func defaultBehavior() -> WhitespaceBehavior { + return WhitespaceBehavior(left: .unspecified, right: .unspecified) + } +} + +public func == (lhs: WhitespaceBehavior, rhs: WhitespaceBehavior) -> Bool { + return (lhs.left == rhs.left) && (lhs.right == rhs.right) +} + public enum Token : Equatable { /// A token representing a piece of text. @@ -48,25 +65,20 @@ public enum Token : Equatable { case comment(value: String) /// A token representing a template block. - case block(value: String) + case block(value: String, newline: WhitespaceBehavior) /// Returns the underlying value as an array seperated by spaces public func components() -> [String] { - switch self { - case .block(let value): - return value.smartSplit() - case .variable(let value): - return value.smartSplit() - case .text(let value): - return value.smartSplit() - case .comment(let value): - return value.smartSplit() - } + return contents.smartSplit() + } + + public static func mkBlock(_ value: String) -> Token { + return .block(value: value, newline: WhitespaceBehavior.defaultBehavior()) } public var contents: String { switch self { - case .block(let value): + case .block(let value, _): return value case .variable(let value): return value @@ -85,8 +97,8 @@ public func == (lhs: Token, rhs: Token) -> Bool { return lhsValue == rhsValue case (.variable(let lhsValue), .variable(let rhsValue)): return lhsValue == rhsValue - case (.block(let lhsValue), .block(let rhsValue)): - return lhsValue == rhsValue + case (.block(let lhsValue, let lhsBehavior), .block(let rhsValue, let rhsBehavior)): + return (lhsValue == rhsValue) && (lhsBehavior == rhsBehavior) case (.comment(let lhsValue), .comment(let rhsValue)): return lhsValue == rhsValue default: diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index f0fae3c4..f664bcb1 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -7,11 +7,11 @@ func testIfNode() { $0.describe("parsing") { $0.it("can parse an if block") { let tokens: [Token] = [ - .block(value: "if value"), + Token.mkBlock("if value"), .text(value: "true"), - .block(value: "else"), + Token.mkBlock("else"), .text(value: "false"), - .block(value: "endif") + Token.mkBlock("endif") ] let parser = TokenParser(tokens: tokens, environment: Environment()) @@ -29,11 +29,11 @@ func testIfNode() { $0.it("can parse an if with complex expression") { let tokens: [Token] = [ - .block(value: "if value == \"test\" and not name"), + Token.mkBlock("if value == \"test\" and not name"), .text(value: "true"), - .block(value: "else"), + Token.mkBlock("else"), .text(value: "false"), - .block(value: "endif") + Token.mkBlock("endif") ] let parser = TokenParser(tokens: tokens, environment: Environment()) @@ -51,11 +51,11 @@ func testIfNode() { $0.it("can parse an ifnot block") { let tokens: [Token] = [ - .block(value: "ifnot value"), + Token.mkBlock("ifnot value"), .text(value: "false"), - .block(value: "else"), + Token.mkBlock("else"), .text(value: "true"), - .block(value: "endif") + Token.mkBlock("endif") ] let parser = TokenParser(tokens: tokens, environment: Environment()) @@ -73,7 +73,7 @@ func testIfNode() { $0.it("throws an error when parsing an if block without an endif") { let tokens: [Token] = [ - .block(value: "if value"), + Token.mkBlock("if value"), ] let parser = TokenParser(tokens: tokens, environment: Environment()) @@ -83,7 +83,7 @@ func testIfNode() { $0.it("throws an error when parsing an ifnot without an endif") { let tokens: [Token] = [ - .block(value: "ifnot value"), + Token.mkBlock("ifnot value"), ] let parser = TokenParser(tokens: tokens, environment: Environment()) @@ -106,9 +106,9 @@ func testIfNode() { $0.it("supports variable filters in the if expression") { let tokens: [Token] = [ - .block(value: "if value|uppercase == \"TEST\""), + Token.mkBlock("if value|uppercase == \"TEST\""), .text(value: "true"), - .block(value: "endif") + Token.mkBlock("endif") ] let parser = TokenParser(tokens: tokens, environment: Environment()) diff --git a/Tests/StencilTests/IncludeSpec.swift b/Tests/StencilTests/IncludeSpec.swift index 02978965..f3d586c4 100644 --- a/Tests/StencilTests/IncludeSpec.swift +++ b/Tests/StencilTests/IncludeSpec.swift @@ -11,7 +11,7 @@ func testInclude() { $0.describe("parsing") { $0.it("throws an error when no template is given") { - let tokens: [Token] = [ .block(value: "include") ] + let tokens: [Token] = [ Token.mkBlock("include") ] let parser = TokenParser(tokens: tokens, environment: Environment()) let error = TemplateSyntaxError("'include' tag takes one argument, the template file to be included") @@ -19,7 +19,7 @@ func testInclude() { } $0.it("can parse a valid include block") { - let tokens: [Token] = [ .block(value: "include \"test.html\"") ] + let tokens: [Token] = [ Token.mkBlock("include \"test.html\"") ] let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() diff --git a/Tests/StencilTests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift index 8cb83c04..425664f3 100644 --- a/Tests/StencilTests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -46,5 +46,16 @@ func testLexer() { try expect(tokens[0]) == Token.variable(value: "thing") try expect(tokens[1]) == Token.variable(value: "name") } + $0.it("can tokenize whitespace control characters") { + let fBlock = "if hello" + let sBlock = "ta da" + let lexer = Lexer(templateString: "{%+ \(fBlock) -%}{% \(sBlock) -%}") + let tokens = lexer.tokenize() + let newLineBehaviors = (WhitespaceBehavior(left: .keep, right: .trim), WhitespaceBehavior(left: .unspecified, right: .trim)) + + try expect(tokens.count) == 2 + try expect(tokens[0]) == Token.block(value: fBlock, newline: newLineBehaviors.0) + try expect(tokens[1]) == Token.block(value: sBlock, newline: newLineBehaviors.1) + } } } diff --git a/Tests/StencilTests/NowNodeSpec.swift b/Tests/StencilTests/NowNodeSpec.swift index 4adba361..60706041 100644 --- a/Tests/StencilTests/NowNodeSpec.swift +++ b/Tests/StencilTests/NowNodeSpec.swift @@ -8,7 +8,7 @@ func testNowNode() { describe("NowNode") { $0.describe("parsing") { $0.it("parses default format without any now arguments") { - let tokens: [Token] = [ .block(value: "now") ] + let tokens: [Token] = [ Token.mkBlock("now") ] let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() @@ -18,7 +18,7 @@ func testNowNode() { } $0.it("parses now with a format") { - let tokens: [Token] = [ .block(value: "now \"HH:mm\"") ] + let tokens: [Token] = [ Token.mkBlock("now \"HH:mm\"") ] let parser = TokenParser(tokens: tokens, environment: Environment()) let nodes = try parser.parse() let node = nodes.first as? NowNode diff --git a/Tests/StencilTests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift index b5c9bb29..1b61c5fa 100644 --- a/Tests/StencilTests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -44,7 +44,7 @@ func testTokenParser() { } let parser = TokenParser(tokens: [ - .block(value: "known"), + Token.mkBlock("known"), ], environment: Environment(extensions: [simpleExtension])) let nodes = try parser.parse() @@ -53,7 +53,7 @@ func testTokenParser() { $0.it("errors when parsing an unknown tag") { let parser = TokenParser(tokens: [ - .block(value: "unknown"), + Token.mkBlock("unknown"), ], environment: Environment()) try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'")) From 0990325586f30cf191ed5d9219f360d4010042ef Mon Sep 17 00:00:00 2001 From: Miguel Bejar Date: Sun, 15 Jan 2017 12:17:22 -0500 Subject: [PATCH 2/4] Adding TrimBehavior to text node The parser keeps track of the whitespace behavior from immediate neighboring Block tokens, and uses that information when creating TextNodes. TextNode at render-time strips leading or trailing whitespace as necessary. --- Sources/ForTag.swift | 2 +- Sources/IfTag.swift | 8 ++++---- Sources/Lexer.swift | 6 +++--- Sources/Node.swift | 26 ++++++++++++++++++++++++-- Sources/Parser.swift | 17 +++++++++++++++-- Sources/Tokenizer.swift | 16 +++++++++++----- Tests/StencilTests/LexerSpec.swift | 2 +- Tests/StencilTests/NodeSpec.swift | 15 +++++++++++++++ Tests/StencilTests/ParserSpec.swift | 20 ++++++++++++++++++++ Tests/StencilTests/TemplateSpec.swift | 15 +++++++++++++++ 10 files changed, 109 insertions(+), 18 deletions(-) diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 5628bbe3..74eb3ab3 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -16,7 +16,7 @@ class ForNode : NodeType { var emptyNodes = [NodeType]() - let forNodes = try parser.parse(until(["endfor", "empty"])) + let forNodes = try parser.parse(token.whitespace?.trailing, until(["endfor", "empty"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endfor` was not found.") diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index bd89b5e6..9476b393 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -171,14 +171,14 @@ class IfNode : NodeType { var trueNodes = [NodeType]() var falseNodes = [NodeType]() - trueNodes = try parser.parse(until(["endif", "else"])) + trueNodes = try parser.parse(token.whitespace?.trailing, until(["endif", "else"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endif` was not found.") } if token.contents == "else" { - falseNodes = try parser.parse(until(["endif"])) + falseNodes = try parser.parse(token.whitespace?.trailing, until(["endif"])) _ = parser.nextToken() } @@ -195,14 +195,14 @@ class IfNode : NodeType { var trueNodes = [NodeType]() var falseNodes = [NodeType]() - falseNodes = try parser.parse(until(["endif", "else"])) + falseNodes = try parser.parse(token.whitespace?.trailing, until(["endif", "else"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endif` was not found.") } if token.contents == "else" { - trueNodes = try parser.parse(until(["endif"])) + trueNodes = try parser.parse(token.whitespace?.trailing, until(["endif"])) _ = parser.nextToken() } diff --git a/Sources/Lexer.swift b/Sources/Lexer.swift index 38e3a6a7..b02c5907 100644 --- a/Sources/Lexer.swift +++ b/Sources/Lexer.swift @@ -18,8 +18,8 @@ struct Lexer { let leftIndicator = leftIndex.map { ind in string[ind...ind] } let rightIndicator = rightIndex.map { ind in string[ind...ind] } return WhitespaceBehavior( - left: behavior(string: leftIndicator ?? ""), - right: behavior(string: rightIndicator ?? "") + leading: behavior(string: leftIndicator ?? ""), + trailing: behavior(string: rightIndicator ?? "") ) } @@ -43,7 +43,7 @@ struct Lexer { return .variable(value: strip()) } else if string.hasPrefix("{%") { let behavior = whiteSpaceBehavior(string: string, tagLength: Lexer.TagLength) - let stripLengths = (Lexer.TagLength + additionalTagLength(b: behavior.left),Lexer.TagLength + additionalTagLength(b: behavior.right)) + let stripLengths = (Lexer.TagLength + additionalTagLength(b: behavior.leading),Lexer.TagLength + additionalTagLength(b: behavior.trailing)) return .block(value: strip(length: stripLengths), newline: behavior) } else if string.hasPrefix("{#") { return .comment(value: strip()) diff --git a/Sources/Node.swift b/Sources/Node.swift index 5b47177a..a49b1efa 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -40,17 +40,39 @@ public class SimpleNode : NodeType { public class TextNode : NodeType { + private static let leadingWhiteSpace = try! NSRegularExpression(pattern: "^\\s+", options: []) + private static let trailingWhiteSpace = try! NSRegularExpression(pattern: "\\s+$", options: []) + public struct TrimBehavior { + let trimLeft: Bool + let trimRight: Bool + } public let text:String + public let trimBehavior:TrimBehavior - public init(text:String) { + public init(text:String, tBehavior:TrimBehavior = TrimBehavior(trimLeft: false, trimRight: false)) { self.text = text + self.trimBehavior = tBehavior } public func render(_ context:Context) throws -> String { - return self.text + var string = self.text + if trimBehavior.trimLeft { + let range = NSMakeRange(0, string.characters.count) + string = TextNode.leadingWhiteSpace.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "") + } + if trimBehavior.trimRight { + let range = NSMakeRange(0, string.characters.count) + string = TextNode.trailingWhiteSpace.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "") + } + return string } } +extension TextNode.TrimBehavior: Equatable {} +public func == (lhs: TextNode.TrimBehavior, rhs: TextNode.TrimBehavior) -> Bool { + return (lhs.trimLeft == rhs.trimLeft) && (lhs.trimRight == rhs.trimRight) +} + public protocol Resolvable { func resolve(_ context: Context) throws -> Any? diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 1a59edba..7559f647 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -27,18 +27,26 @@ public class TokenParser { /// Parse the given tokens into nodes public func parse() throws -> [NodeType] { - return try parse(nil) + return try parse(nil, nil) } public func parse(_ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] { + return try parse(nil, parse_until) + } + + public func parse(_ leadingWhiteSpace: WhitespaceBehavior.Behavior?, _ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] { var nodes = [NodeType]() + var previousWhiteSpace:WhitespaceBehavior.Behavior? = leadingWhiteSpace while tokens.count > 0 { let token = nextToken()! switch token { case .text(let text): - nodes.append(TextNode(text: text)) + let leadingTrim = (previousWhiteSpace ?? .unspecified) == .trim + let trailingTrim = (peekWhitespace() ?? .unspecified) == .trim + let behavior = TextNode.TrimBehavior(trimLeft: leadingTrim, trimRight: trailingTrim) + nodes.append(TextNode(text: text, tBehavior: behavior)) case .variable: nodes.append(VariableNode(variable: try compileFilter(token.contents))) case .block: @@ -54,6 +62,7 @@ public class TokenParser { case .comment: continue } + previousWhiteSpace = token.whitespace?.trailing } return nodes @@ -67,6 +76,10 @@ public class TokenParser { return nil } + func peekWhitespace() -> WhitespaceBehavior.Behavior? { + return tokens.first?.whitespace?.leading + } + public func prependToken(_ token:Token) { tokens.insert(token, at: 0) } diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 486633f4..171a22d6 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -37,20 +37,20 @@ extension String { } public struct WhitespaceBehavior: Equatable { - enum Behavior { + public enum Behavior { case unspecified case trim case keep } - let left: Behavior - let right: Behavior + let leading: Behavior + let trailing: Behavior static func defaultBehavior() -> WhitespaceBehavior { - return WhitespaceBehavior(left: .unspecified, right: .unspecified) + return WhitespaceBehavior(leading: .unspecified, trailing: .unspecified) } } public func == (lhs: WhitespaceBehavior, rhs: WhitespaceBehavior) -> Bool { - return (lhs.left == rhs.left) && (lhs.right == rhs.right) + return (lhs.leading == rhs.leading) && (lhs.trailing == rhs.trailing) } @@ -88,6 +88,12 @@ public enum Token : Equatable { return value } } + public var whitespace: WhitespaceBehavior? { + switch self { + case .variable, .comment, .text: return nil + case .block(_, let ws): return ws + } + } } diff --git a/Tests/StencilTests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift index 425664f3..b5fb8aac 100644 --- a/Tests/StencilTests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -51,7 +51,7 @@ func testLexer() { let sBlock = "ta da" let lexer = Lexer(templateString: "{%+ \(fBlock) -%}{% \(sBlock) -%}") let tokens = lexer.tokenize() - let newLineBehaviors = (WhitespaceBehavior(left: .keep, right: .trim), WhitespaceBehavior(left: .unspecified, right: .trim)) + let newLineBehaviors = (WhitespaceBehavior(leading: .keep, trailing: .trim), WhitespaceBehavior(leading: .unspecified, trailing: .trim)) try expect(tokens.count) == 2 try expect(tokens[0]) == Token.block(value: fBlock, newline: newLineBehaviors.0) diff --git a/Tests/StencilTests/NodeSpec.swift b/Tests/StencilTests/NodeSpec.swift index 431d225d..0c8d528c 100644 --- a/Tests/StencilTests/NodeSpec.swift +++ b/Tests/StencilTests/NodeSpec.swift @@ -22,6 +22,21 @@ func testNode() { let node = TextNode(text: "Hello World") try expect(try node.render(context)) == "Hello World" } + $0.it("Trims leading whitespace") { + let text = " \nSome text " + let node = TextNode(text: text, tBehavior: TextNode.TrimBehavior(trimLeft: true, trimRight: false)) + try expect(try node.render(context)) == "Some text " + } + $0.it("Trims trailing whitespace") { + let text = " \nSome text " + let node = TextNode(text: text, tBehavior: TextNode.TrimBehavior(trimLeft: false, trimRight: true)) + try expect(try node.render(context)) == " \nSome text" + } + $0.it("Trims all whitespace") { + let text = " \nSome text " + let node = TextNode(text: text, tBehavior: TextNode.TrimBehavior(trimLeft: true, trimRight: true)) + try expect(try node.render(context)) == "Some text" + } } $0.describe("VariableNode") { diff --git a/Tests/StencilTests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift index 1b61c5fa..cb2e4797 100644 --- a/Tests/StencilTests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -58,5 +58,25 @@ func testTokenParser() { try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'")) } + + $0.it("Can trim whitespace") { + + let simpleExtension = Extension() + simpleExtension.registerSimpleTag("known") { _ in + return "" + } + + let parser = TokenParser(tokens: [ + Token.block(value: "known", newline: WhitespaceBehavior(leading: .unspecified, trailing: .trim)), + Token.text(value: " \nSome text "), + Token.block(value: "known", newline: WhitespaceBehavior(leading: .keep, trailing: .trim)) + ], environment: Environment(extensions: [simpleExtension])) + + let nodes = try parser.parse() + try expect(nodes.count) == 3 + let textNode = nodes[1] as? TextNode + try expect(textNode?.text) == " \nSome text " + try expect(textNode?.trimBehavior) == TextNode.TrimBehavior(trimLeft: true, trimRight: false) + } } } diff --git a/Tests/StencilTests/TemplateSpec.swift b/Tests/StencilTests/TemplateSpec.swift index ad03851e..f87d0187 100644 --- a/Tests/StencilTests/TemplateSpec.swift +++ b/Tests/StencilTests/TemplateSpec.swift @@ -15,5 +15,20 @@ func testTemplate() { let result = try template.render([ "name": "Kyle" ]) try expect(result) == "Hello World" } + $0.it("Respects whitespace control symbols in for tags") { + let template: Template = "{% for num in numbers -%}\n {{num}}\n{%- endfor %}" + let result = try template.render([ "numbers": Array(1...9) ]) + try expect(result) == "123456789" + } + $0.it("Respects whitespace control symbols in if tags") { + let template: Template = "{% if value -%}\n {{text}}\n{%- endif %}" + let result = try template.render([ "text": "hello", "value": true ]) + try expect(result) == "hello" + } + $0.it("Respects whitespace control symbols in ifnot tags") { + let template: Template = "{% ifnot value %}{% else -%}\n {{text}}\n{%- endif %}" + let result = try template.render([ "text": "hello", "value": true ]) + try expect(result) == "hello" + } } } From 3fc963bc939911d7e544d568d07881f53e938fcb Mon Sep 17 00:00:00 2001 From: Miguel Bejar Date: Tue, 17 Jan 2017 07:55:27 -0500 Subject: [PATCH 3/4] FIX: Typealias NSRegularExpression on Linux Swift 3.0.x versions --- Sources/Node.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/Node.swift b/Sources/Node.swift index a49b1efa..60a24d01 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -38,6 +38,13 @@ public class SimpleNode : NodeType { } } +#if os(Linux) + #if swift(>=3.1) + #else + typealias NSRegularExpression = RegularExpression + #endif +#endif + public class TextNode : NodeType { private static let leadingWhiteSpace = try! NSRegularExpression(pattern: "^\\s+", options: []) From 0a93d76e45cac4f96f673b9be4b6b7c0b7bfae28 Mon Sep 17 00:00:00 2001 From: Miguel Bejar Date: Wed, 18 Jan 2017 08:25:43 -0500 Subject: [PATCH 4/4] Keeping trailing whitespace state inside parser This simplifies the parsing code for each node and gives every block token automatic support for the "-" and "+" whitespace control characters. --- Sources/ForTag.swift | 2 +- Sources/IfTag.swift | 8 ++++---- Sources/Parser.swift | 10 +++------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 74eb3ab3..5628bbe3 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -16,7 +16,7 @@ class ForNode : NodeType { var emptyNodes = [NodeType]() - let forNodes = try parser.parse(token.whitespace?.trailing, until(["endfor", "empty"])) + let forNodes = try parser.parse(until(["endfor", "empty"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endfor` was not found.") diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index 9476b393..bd89b5e6 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -171,14 +171,14 @@ class IfNode : NodeType { var trueNodes = [NodeType]() var falseNodes = [NodeType]() - trueNodes = try parser.parse(token.whitespace?.trailing, until(["endif", "else"])) + trueNodes = try parser.parse(until(["endif", "else"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endif` was not found.") } if token.contents == "else" { - falseNodes = try parser.parse(token.whitespace?.trailing, until(["endif"])) + falseNodes = try parser.parse(until(["endif"])) _ = parser.nextToken() } @@ -195,14 +195,14 @@ class IfNode : NodeType { var trueNodes = [NodeType]() var falseNodes = [NodeType]() - falseNodes = try parser.parse(token.whitespace?.trailing, until(["endif", "else"])) + falseNodes = try parser.parse(until(["endif", "else"])) guard let token = parser.nextToken() else { throw TemplateSyntaxError("`endif` was not found.") } if token.contents == "else" { - trueNodes = try parser.parse(token.whitespace?.trailing, until(["endif"])) + trueNodes = try parser.parse(until(["endif"])) _ = parser.nextToken() } diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 7559f647..d612aeaa 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -19,6 +19,7 @@ public class TokenParser { fileprivate var tokens: [Token] fileprivate let environment: Environment + private var previousWhiteSpace: WhitespaceBehavior.Behavior? public init(tokens: [Token], environment: Environment) { self.tokens = tokens @@ -27,16 +28,11 @@ public class TokenParser { /// Parse the given tokens into nodes public func parse() throws -> [NodeType] { - return try parse(nil, nil) + return try parse(nil) } public func parse(_ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] { - return try parse(nil, parse_until) - } - - public func parse(_ leadingWhiteSpace: WhitespaceBehavior.Behavior?, _ parse_until:((_ parser:TokenParser, _ token:Token) -> (Bool))?) throws -> [NodeType] { var nodes = [NodeType]() - var previousWhiteSpace:WhitespaceBehavior.Behavior? = leadingWhiteSpace while tokens.count > 0 { let token = nextToken()! @@ -50,6 +46,7 @@ public class TokenParser { case .variable: nodes.append(VariableNode(variable: try compileFilter(token.contents))) case .block: + previousWhiteSpace = token.whitespace?.trailing if let parse_until = parse_until , parse_until(self, token) { prependToken(token) return nodes @@ -62,7 +59,6 @@ public class TokenParser { case .comment: continue } - previousWhiteSpace = token.whitespace?.trailing } return nodes