Created
March 27, 2019 18:16
-
-
Save chriseidhof/2a3756e3dee785fc4d34b1a61c4d6517 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ParseLib.swift | |
// JsxLib | |
// | |
// Created by Chris Eidhof on 19.02.19. | |
// | |
import Foundation | |
public struct ParseError: Error { | |
public var message: String | |
public var position: Substring.Index | |
var debugFile: StaticString | |
var debugLine: UInt | |
init(message: String, position: Substring.Index, debugFile: StaticString = #file, debugLine: UInt = #line) { | |
self.message = message | |
self.position = position | |
self.debugFile = debugFile | |
self.debugLine = debugLine | |
} | |
public var localizedDescription: String { | |
return "Parse Error: \(self)" | |
} | |
} | |
extension Substring { | |
func err(_ message: String, position: Index? = nil, file: StaticString = #file, line: UInt = #line) -> ParseError { | |
return ParseError(message: message, position: position ?? startIndex, debugFile: file, debugLine: line) | |
} | |
mutating func expect(_ c: Character, file: StaticString = #file, line: UInt = #line) throws { | |
guard eat(c) else { throw err("Expected \(c)", position: nil, file: file, line: line) } | |
} | |
mutating func eatSpace(required: Bool = false) throws { | |
let result = eat(many: { $0.isSpace }) | |
if required && result.isEmpty { | |
throw err("Expected whitespace") | |
} | |
} | |
mutating func many<A>(until end: (inout Substring) throws -> (), _ f: (inout Substring) throws -> A, separator: (inout Substring) throws -> Bool) throws -> [A] { | |
var result: [A] = [] | |
while (try? end(&self)) == nil { | |
result.append(try f(&self)) | |
if try separator(&self) { | |
continue | |
} else { | |
try end(&self) | |
return result | |
} | |
} | |
return result | |
} | |
mutating func many<A>(while cond: @escaping (Substring) -> Bool, _ f: (inout Substring) throws -> A, separator: (inout Substring) throws -> Bool) throws -> [A] { | |
return try many(until: { | |
guard !cond($0) else { throw $0.err("") } | |
return () | |
}, f, separator: separator) | |
} | |
mutating func many<A>(until end: Character, _ f: (inout Substring) throws -> A, separator: (inout Substring) throws -> Bool) throws -> [A] { | |
return try self.many(until: { try $0.eat(expecting: String(end)) }, f, separator: separator) | |
} | |
mutating func while_(_ cond: (Substring) -> Bool, do: (inout Substring) throws -> ()) throws { | |
var pos = startIndex | |
while cond(self) { | |
try `do`(&self) | |
guard pos != startIndex else { return } | |
pos = startIndex | |
} | |
} | |
mutating func while2_<A>(_ cond: (inout Substring) throws -> Bool, do: (inout Substring) throws -> A?) throws -> [A] { | |
var pos = startIndex | |
var result: [A] = [] | |
while try cond(&self) { | |
guard let x = try `do`(&self) else { | |
return result | |
} | |
result.append(x) | |
guard pos != startIndex else { return result } | |
pos = startIndex | |
} | |
return result | |
} | |
mutating func eat<S>(prefix: S) -> Bool where S: StringProtocol { | |
guard hasPrefix(prefix) else { return false } | |
try! eat(expecting: prefix) // probably no the most optimal | |
return true | |
} | |
mutating func eat<S>(expecting: S) throws where S: Sequence, S.Element == Element { | |
let startPos = startIndex | |
for s in expecting { | |
guard eat(s) else { | |
throw err("Expected \"\(expecting)\"", position: startPos) | |
} | |
} | |
} | |
mutating func comma() throws -> Bool { | |
try eatSpace() | |
if eat(",") { | |
try eatSpace() | |
return true | |
} else { | |
return false | |
} | |
} | |
} | |
fileprivate let xmlAttributeNameCharacters = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: ":_-.")) // todo there are more | |
extension CharacterSet { | |
func contains(_ character: Character) -> Bool { | |
return character.unicodeScalars.count == 1 && contains(character.unicodeScalars.first!) | |
} | |
} | |
extension Character { | |
var isNewLine: Bool { | |
return self == "\n" // todo should we have more things like \r and so on? | |
} | |
var isSpace: Bool { | |
return [" ", "\t", "\n"].contains(self) // todo | |
} | |
var isName: Bool { | |
return CharacterSet.alphanumerics.contains(self) | |
} | |
var isXMLAttribute: Bool { | |
return xmlAttributeNameCharacters.contains(self) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// Forked from https://gist.github.com/milseman/f9b5528345db3a36bbdd138af52c5cda | |
extension Collection where SubSequence == Self, Element: Equatable { | |
mutating func eat(_ element: Element) -> Bool { | |
guard let f = first, f == element else { return false } | |
_ = eat() | |
return true | |
} | |
mutating func eat(asserting on: Element) -> Element { | |
let e = eat() | |
assert(on == e) | |
return e | |
} | |
mutating func eat(until element: Element) -> SubSequence { | |
return eat(until: { $0 == element }) | |
} | |
func peek(is element: Element) -> Bool { | |
if let f = self.first, f == element { return true } | |
return false | |
} | |
} | |
extension Collection where SubSequence == Self { | |
mutating func eat() -> Element { | |
defer { self = self.dropFirst() } | |
return peek() | |
} | |
mutating func eat(_ n: Int) -> SubSequence { | |
let (pre, rest) = self.seek(n) | |
self = rest | |
return pre | |
} | |
mutating func eat(until f: (Element) -> Bool) -> SubSequence { | |
let (pre, rest) = self.seek(until: f) | |
self = rest | |
return pre | |
} | |
mutating func eat(many1 cond: (Element) -> Bool) -> SubSequence? { | |
guard cond(peek()) else { return nil } | |
let (result, newSelf) = seek(until: { !cond($0) }) | |
self = newSelf | |
return result | |
} | |
@discardableResult mutating func eat(many cond: (Element) -> Bool) -> SubSequence { | |
let (result, newSelf) = seek(until: { !cond($0) }) | |
self = newSelf | |
return result | |
} | |
mutating func eat(oneOf cond: (Element) -> Bool) -> Element? { | |
guard let element = first, cond(element) else { return nil } | |
self = dropFirst() | |
return element | |
} | |
func peek() -> Element { | |
return self.first! | |
} | |
func peek(_ n: Int) -> Element { | |
assert(n > 0 && self.count >= n) | |
return self.dropFirst(n).peek() | |
} | |
func seek(_ n: Int) -> (prefix: SubSequence, rest: SubSequence) { | |
return (self.prefix(n), self.dropFirst(n)) | |
} | |
func seek(until f: (Element) -> Bool) -> (prefix: SubSequence, rest: SubSequence) { | |
guard let point = self.index(where: f) else { | |
return (self[...], self[endIndex...]) | |
} | |
return (self[..<point], self[point...]) | |
} | |
// ... seek(until: Element), seek(through:), seek(until: Set<Element>), seek(through: Set<Element>) ... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment