Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Created March 27, 2019 18:16
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriseidhof/2a3756e3dee785fc4d34b1a61c4d6517 to your computer and use it in GitHub Desktop.
Save chriseidhof/2a3756e3dee785fc4d34b1a61c4d6517 to your computer and use it in GitHub Desktop.
//
// 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)
}
}
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