diff --git a/.travis.yml b/.travis.yml index 9240617..8639971 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,16 @@ matrix: include: + - os: osx + osx_image: xcode9.2 + env: SWIFT_VERSION=4.0.3 - os: osx osx_image: xcode9.4 env: SWIFT_VERSION=4.1 - os: osx osx_image: xcode10 env: SWIFT_VERSION=4.2 + - os: linux + env: SWIFT_VERSION=4.0.3 - os: linux env: SWIFT_VERSION=4.1 - os: linux diff --git a/CHANGELOG.md b/CHANGELOG.md index 6277a8d..316a015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,15 @@ _None_ ### Bug Fixes -_None_ +- Fixed using parenthesis in boolean expressions, they now can be used without spaces around them. + [Ilya Puchka](https://github.com/ilyapuchka) + [#254](https://github.com/stencilproject/Stencil/pull/254) ### Internal Changes -_None_ +- `Token` type converted to struct to allow computing token components only once. + [Ilya Puchka](https://github.com/ilyapuchka) + [#256](https://github.com/stencilproject/Stencil/pull/256) ## 0.13.1 diff --git a/Package.swift b/Package.swift index 2dd6977..b5fb304 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.1 +// swift-tools-version:4.0 import PackageDescription let package = Package( diff --git a/Sources/FilterTag.swift b/Sources/FilterTag.swift index 4cf9746..9371b3c 100644 --- a/Sources/FilterTag.swift +++ b/Sources/FilterTag.swift @@ -4,7 +4,7 @@ class FilterNode : NodeType { let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { - let bits = token.components() + let bits = token.components guard bits.count == 2 else { throw TemplateSyntaxError("'filter' tag takes one argument, the filter expression") diff --git a/Sources/ForTag.swift b/Sources/ForTag.swift index 3f2a753..89a65db 100644 --- a/Sources/ForTag.swift +++ b/Sources/ForTag.swift @@ -9,7 +9,7 @@ class ForNode : NodeType { let token: Token? class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { - let components = token.components() + let components = token.components func hasToken(_ token: String, at index: Int) -> Bool { return components.count > (index + 1) && components[index] == token diff --git a/Sources/IfTag.swift b/Sources/IfTag.swift index 16fd0ee..e9fe885 100644 --- a/Sources/IfTag.swift +++ b/Sources/IfTag.swift @@ -236,7 +236,7 @@ class IfNode : NodeType { let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { - var components = token.components() + var components = token.components components.removeFirst() let expression = try parser.compileExpression(components: components, token: token) @@ -247,7 +247,7 @@ class IfNode : NodeType { var nextToken = parser.nextToken() while let current = nextToken, current.contents.hasPrefix("elif") { - var components = current.components() + var components = current.components components.removeFirst() let expression = try parser.compileExpression(components: components, token: current) @@ -269,7 +269,7 @@ class IfNode : NodeType { } class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType { - var components = token.components() + var components = token.components guard components.count == 2 else { throw TemplateSyntaxError("'ifnot' statements should use the following syntax 'ifnot condition'.") } diff --git a/Sources/Include.swift b/Sources/Include.swift index d6287cc..9c82b68 100644 --- a/Sources/Include.swift +++ b/Sources/Include.swift @@ -7,7 +7,7 @@ class IncludeNode : NodeType { let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { - let bits = token.components() + let bits = token.components guard bits.count == 2 || bits.count == 3 else { throw TemplateSyntaxError(""" diff --git a/Sources/Inheritence.swift b/Sources/Inheritence.swift index c276d10..e512bfb 100644 --- a/Sources/Inheritence.swift +++ b/Sources/Inheritence.swift @@ -53,7 +53,7 @@ class ExtendsNode : NodeType { let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { - let bits = token.components() + let bits = token.components guard bits.count == 2 else { throw TemplateSyntaxError("'extends' takes one argument, the template file to be extended") @@ -124,7 +124,7 @@ class BlockNode : NodeType { let token: Token? class func parse(_ parser: TokenParser, token: Token) throws -> NodeType { - let bits = token.components() + let bits = token.components guard bits.count == 2 else { throw TemplateSyntaxError("'block' tag takes one argument, the block name") @@ -163,7 +163,7 @@ class BlockNode : NodeType { var childContext: [String: Any] = [BlockContext.contextKey: blockContext] if let blockSuperNode = child.nodes.first(where: { - if case .variable(let variable, _)? = $0.token, variable == "block.super" { return true } + if let token = $0.token, case .variable = token.kind, token.contents == "block.super" { return true } else { return false} }) { do { diff --git a/Sources/Node.swift b/Sources/Node.swift index 9094820..2805290 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -62,7 +62,7 @@ public class VariableNode : NodeType { let elseExpression: Resolvable? class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { - var components = token.components() + var components = token.components func hasToken(_ token: String, at index: Int) -> Bool { return components.count > (index + 1) && components[index] == token diff --git a/Sources/NowTag.swift b/Sources/NowTag.swift index 6d354ed..ac0ccfb 100644 --- a/Sources/NowTag.swift +++ b/Sources/NowTag.swift @@ -9,7 +9,7 @@ class NowNode : NodeType { class func parse(_ parser:TokenParser, token:Token) throws -> NodeType { var format:Variable? - let components = token.components() + let components = token.components guard components.count <= 2 else { throw TemplateSyntaxError("'now' tags may only have one argument: the format string.") } diff --git a/Sources/Parser.swift b/Sources/Parser.swift index c283774..41dd7b9 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -1,6 +1,6 @@ public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) { return { parser, token in - if let name = token.components().first { + if let name = token.components.first { for tag in tags { if name == tag { return true @@ -36,9 +36,9 @@ public class TokenParser { while tokens.count > 0 { let token = nextToken()! - switch token { - case .text(let text, _): - nodes.append(TextNode(text: text)) + switch token.kind { + case .text: + nodes.append(TextNode(text: token.contents)) case .variable: try nodes.append(VariableNode.parse(self, token: token)) case .block: @@ -47,7 +47,7 @@ public class TokenParser { return nodes } - if let tag = token.components().first { + if let tag = token.components.first { do { let parser = try environment.findTag(name: tag) let node = try parser(self, token) diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 54e6891..d86852f 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -17,6 +17,12 @@ extension String { components[components.count-1] += word } else if specialCharacters.contains(word) { components[components.count-1] += word + } else if word != "(" && word.first == "(" || word != ")" && word.first == ")" { + components.append(String(word.prefix(1))) + appendWord(String(word.dropFirst())) + } else if word != "(" && word.last == "(" || word != ")" && word.last == ")" { + appendWord(String(word.dropLast())) + components.append(String(word.suffix(1))) } else { components.append(word) } @@ -71,47 +77,53 @@ public struct SourceMap: Equatable { } } -public enum Token : Equatable { +public class Token: Equatable { + public enum Kind: Equatable { + /// A token representing a piece of text. + case text + /// A token representing a variable. + case variable + /// A token representing a comment. + case comment + /// A token representing a template block. + case block + } + + public let contents: String + public let kind: Kind + public let sourceMap: SourceMap + + /// Returns the underlying value as an array seperated by spaces + public private(set) lazy var components: [String] = self.contents.smartSplit() + + init(contents: String, kind: Kind, sourceMap: SourceMap) { + self.contents = contents + self.kind = kind + self.sourceMap = sourceMap + } + /// A token representing a piece of text. - case text(value: String, at: SourceMap) + public static func text(value: String, at sourceMap: SourceMap) -> Token { + return Token(contents: value, kind: .text, sourceMap: sourceMap) + } /// A token representing a variable. - case variable(value: String, at: SourceMap) + public static func variable(value: String, at sourceMap: SourceMap) -> Token { + return Token(contents: value, kind: .variable, sourceMap: sourceMap) + } /// A token representing a comment. - case comment(value: String, at: SourceMap) + public static func comment(value: String, at sourceMap: SourceMap) -> Token { + return Token(contents: value, kind: .comment, sourceMap: sourceMap) + } /// A token representing a template block. - case block(value: String, at: SourceMap) - - /// Returns the underlying value as an array seperated by spaces - public func components() -> [String] { - switch self { - case .block(let value, _), - .variable(let value, _), - .text(let value, _), - .comment(let value, _): - return value.smartSplit() - } + public static func block(value: String, at sourceMap: SourceMap) -> Token { + return Token(contents: value, kind: .block, sourceMap: sourceMap) + } + + public static func == (lhs: Token, rhs: Token) -> Bool { + return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap } - public var contents: String { - switch self { - case .block(let value, _), - .variable(let value, _), - .text(let value, _), - .comment(let value, _): - return value - } - } - - public var sourceMap: SourceMap { - switch self { - case .block(_, let sourceMap), - .variable(_, let sourceMap), - .text(_, let sourceMap), - .comment(_, let sourceMap): - return sourceMap - } - } } diff --git a/Sources/_SwiftSupport.swift b/Sources/_SwiftSupport.swift new file mode 100644 index 0000000..5333659 --- /dev/null +++ b/Sources/_SwiftSupport.swift @@ -0,0 +1,38 @@ +import Foundation + +#if !swift(>=4.1) + public extension Sequence { + func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { + return try flatMap(transform) + } + } +#endif + +#if !swift(>=4.1) + public extension Collection { + func index(_ i: Self.Index, offsetBy n: Int) -> Self.Index { + let indexDistance = Self.IndexDistance(n) + return index(i, offsetBy: indexDistance) + } + } +#endif + +#if !swift(>=4.1) +public extension TemplateSyntaxError { + public static func ==(lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool { + return lhs.reason == rhs.reason && + lhs.description == rhs.description && + lhs.token == rhs.token && + lhs.stackTrace == rhs.stackTrace && + lhs.templateName == rhs.templateName + } +} +#endif + +#if !swift(>=4.1) +public extension Variable { + public static func ==(lhs: Variable, rhs: Variable) -> Bool { + return lhs.variable == rhs.variable + } +} +#endif diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index c9d6ae7..1141b26 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -145,7 +145,7 @@ class IfNodeTests: XCTestCase { $0.it("can parse an if with complex expression") { let tokens: [Token] = [ - .block(value: "if value == \"test\" and not name", at: .unknown), + .block(value: "if value == \"test\" and (not name or not (name and surname) or( some )and other )", at: .unknown), .text(value: "true", at: .unknown), .block(value: "endif", at: .unknown) ] diff --git a/Tests/StencilTests/TokenSpec.swift b/Tests/StencilTests/TokenSpec.swift index 0722b6d..ee4be2b 100644 --- a/Tests/StencilTests/TokenSpec.swift +++ b/Tests/StencilTests/TokenSpec.swift @@ -7,7 +7,7 @@ class TokenTests: XCTestCase { describe("Token") { $0.it("can split the contents into components") { let token = Token.text(value: "hello world", at: .unknown) - let components = token.components() + let components = token.components try expect(components.count) == 2 try expect(components[0]) == "hello" @@ -16,7 +16,7 @@ class TokenTests: XCTestCase { $0.it("can split the contents into components with single quoted strings") { let token = Token.text(value: "hello 'kyle fuller'", at: .unknown) - let components = token.components() + let components = token.components try expect(components.count) == 2 try expect(components[0]) == "hello" @@ -25,7 +25,7 @@ class TokenTests: XCTestCase { $0.it("can split the contents into components with double quoted strings") { let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown) - let components = token.components() + let components = token.components try expect(components.count) == 2 try expect(components[0]) == "hello"