import Foundation struct Lexer { let templateName: String? let templateString: String init(templateName: String? = nil, templateString: String) { self.templateName = templateName self.templateString = templateString } func createToken(string: String, at range: Range) -> Token { func strip() -> String { guard string.characters.count > 4 else { return "" } let start = string.index(string.startIndex, offsetBy: 2) let end = string.index(string.endIndex, offsetBy: -2) return String(string[start.. [Token] { var tokens: [Token] = [] let scanner = Scanner(templateString) let map = [ "{{": "}}", "{%": "%}", "{#": "#}", ] while !scanner.isEmpty { if let text = scanner.scan(until: ["{{", "{%", "{#"]) { if !text.1.isEmpty { tokens.append(createToken(string: text.1, at: scanner.range)) } let end = map[text.0]! let result = scanner.scan(until: end, returnUntil: true) tokens.append(createToken(string: result, at: scanner.range)) } else { tokens.append(createToken(string: scanner.content, at: scanner.range)) scanner.content = "" } } return tokens } } class Scanner { let originalContent: String var content: String var range: Range init(_ content: String) { self.originalContent = content self.content = content range = content.startIndex.. String { var index = content.startIndex if until.isEmpty { return "" } range = range.upperBound.. (String, String)? { if until.isEmpty { return nil } var index = content.startIndex range = range.upperBound.. String.Index? { var index = startIndex while index != endIndex { if character != self[index] { return index } index = self.index(after: index) } return nil } func findLastNot(character: Character) -> String.Index? { var index = self.index(before: endIndex) while index != startIndex { if character != self[index] { return self.index(after: index) } index = self.index(before: index) } return nil } func trim(character: Character) -> String { let first = findFirstNot(character: character) ?? startIndex let last = findLastNot(character: character) ?? endIndex return String(self[first..) -> RangeLine { var lineNumber: UInt = 0 var offset: Int = 0 var lineContent = "" for line in components(separatedBy: CharacterSet.newlines) { lineNumber += 1 lineContent = line if let rangeOfLine = self.range(of: line), rangeOfLine.contains(range.lowerBound) { offset = distance(from: rangeOfLine.lowerBound, to: range.lowerBound) break } } return (lineContent, lineNumber, offset) } } public typealias RangeLine = (content: String, number: UInt, offset: Int)