import Foundation /// Reader object for parsing String buffers public struct HBParser { enum Error: Swift.Error { case overflow } /// internal storage used to store String private class Storage { init(_ buffer: String) { self.buffer = buffer } let buffer: String } private let _storage: Storage /// Create a Reader object /// - Parameter string: String to parse init(_ string: String) { self._storage = Storage(string) self.position = string.startIndex } var buffer: String { return self._storage.buffer } private(set) var position: String.Index } extension HBParser { /// Return current character /// - Throws: .overflow /// - Returns: Current character mutating func character() throws -> Character { guard !self.reachedEnd() else { throw HBParser.Error.overflow } let c = unsafeCurrent() unsafeAdvance() return c } /// Read the current character and return if it is as intended. If character test returns true then move forward 1 /// - Parameter char: character to compare against /// - Throws: .overflow /// - Returns: If current character was the one we expected mutating func read(_ char: Character) throws -> Bool { let c = try character() guard c == char else { unsafeRetreat(); return false } return true } /// Read the current character and return if it is as intended. If character test returns true then move forward 1 /// - Parameter char: character to compare against /// - Throws: .overflow /// - Returns: If current character was the one we expected mutating func read(string: String) throws -> Bool { let initialPosition = self.position guard string.count > 0 else { return true } let subString = try read(count: string.count) guard subString == string else { self.position = initialPosition return false } return true } /// Read the current character and check if it is in a set of characters If character test returns true then move forward 1 /// - Parameter characterSet: Set of characters to compare against /// - Throws: .overflow /// - Returns: If current character is in character set mutating func read(_ characterSet: Set) throws -> Bool { let c = try character() guard characterSet.contains(c) else { unsafeRetreat(); return false } return true } /// Read next so many characters from buffer /// - Parameter count: Number of characters to read /// - Throws: .overflow /// - Returns: The string read from the buffer mutating func read(count: Int) throws -> Substring { guard self.buffer.distance(from: self.position, to: self.buffer.endIndex) >= count else { throw HBParser.Error.overflow } let end = self.buffer.index(self.position, offsetBy: count) let subString = self.buffer[self.position.. Substring { let startIndex = self.position while !self.reachedEnd() { if unsafeCurrent() == until { return self.buffer[startIndex.. Substring { guard untilString.count > 0 else { return "" } let startIndex = self.position var foundIndex = self.position var untilIndex = untilString.startIndex while !self.reachedEnd() { if unsafeCurrent() == untilString[untilIndex] { if untilIndex == untilString.startIndex { foundIndex = self.position } untilIndex = untilString.index(after: untilIndex) if untilIndex == untilString.endIndex { unsafeAdvance() if skipToEnd == false { self.position = foundIndex } let result = self.buffer[startIndex.., throwOnOverflow: Bool = true) throws -> Substring { let startIndex = self.position while !self.reachedEnd() { if characterSet.contains(unsafeCurrent()) { return self.buffer[startIndex.., throwOnOverflow: Bool = true) throws -> Substring { let startIndex = self.position while !self.reachedEnd() { if current()[keyPath: keyPath] { return self.buffer[startIndex.. Bool, throwOnOverflow: Bool = true) throws -> Substring { let startIndex = self.position while !self.reachedEnd() { if cb(current()) { return self.buffer[startIndex.. Substring { let startIndex = self.position self.position = self.buffer.endIndex return self.buffer[startIndex.. Int { var count = 0 while !self.reachedEnd(), unsafeCurrent() == `while` { unsafeAdvance() count += 1 } return count } /// Read while keyPath on character at current position returns true is the one supplied /// - Parameter while: keyPath to check /// - Returns: String read from buffer @discardableResult mutating func read(while keyPath: KeyPath) -> Substring { let startIndex = self.position while !self.reachedEnd(), unsafeCurrent()[keyPath: keyPath] { unsafeAdvance() } return self.buffer[startIndex..) -> Substring { let startIndex = self.position while !self.reachedEnd(), characterSet.contains(unsafeCurrent()) { unsafeAdvance() } return self.buffer[startIndex.. Bool { return self.position == self.buffer.endIndex } /// Return whether we are at the start of the buffer /// - Returns: Are we are the start func atStart() -> Bool { return self.position == self.buffer.startIndex } } extension HBParser { /// context used in parser error public struct Context { public let line: String public let lineNumber: Int public let columnNumber: Int } /// Return context of current position (line, lineNumber, columnNumber) func getContext() -> Context { var parser = self var columnNumber = 0 while !parser.atStart() { try? parser.retreat() if parser.current() == "\n" { break } columnNumber += 1 } if parser.current() == "\n" { try? parser.advance() } // read line from parser let line = try! parser.read(until: Character("\n"), throwOnOverflow: false) // count new lines up to this current position let buffer = parser.buffer let textBefore = buffer[buffer.startIndex.. Character { guard !self.reachedEnd() else { return "\0" } return unsafeCurrent() } /// Move forward one character /// - Throws: .overflow mutating func advance() throws { guard !self.reachedEnd() else { throw HBParser.Error.overflow } return unsafeAdvance() } /// Move back one character /// - Throws: .overflow mutating func retreat() throws { guard self.position != self.buffer.startIndex else { throw HBParser.Error.overflow } return unsafeRetreat() } /// Move forward so many character /// - Parameter amount: number of characters to move forward /// - Throws: .overflow mutating func advance(by amount: Int) throws { guard self.buffer.distance(from: self.position, to: self.buffer.endIndex) >= amount else { throw HBParser.Error.overflow } return unsafeAdvance(by: amount) } /// Move back so many characters /// - Parameter amount: number of characters to move back /// - Throws: .overflow mutating func retreat(by amount: Int) throws { guard self.buffer.distance(from: self.buffer.startIndex, to: self.position) >= amount else { throw HBParser.Error.overflow } return unsafeRetreat(by: amount) } mutating func setPosition(_ position: String.Index) throws { guard position <= self.buffer.endIndex else { throw HBParser.Error.overflow } unsafeSetPosition(position) } } // unsafe versions without checks extension HBParser { func unsafeCurrent() -> Character { return self.buffer[self.position] } mutating func unsafeAdvance() { self.position = self.buffer.index(after: self.position) } mutating func unsafeRetreat() { self.position = self.buffer.index(before: self.position) } mutating func unsafeAdvance(by amount: Int) { self.position = self.buffer.index(self.position, offsetBy: amount) } mutating func unsafeRetreat(by amount: Int) { self.position = self.buffer.index(self.position, offsetBy: -amount) } mutating func unsafeSetPosition(_ position: String.Index) { self.position = position } }