Cyborg/ParserPrimitives.swift (192 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
typealias Parser<T> = (XMLString, Int32) -> ParseResult<T>
indirect enum Failure: Equatable {
struct Metadata: Equatable {
let index: Int32
let stream: XMLString
}
case literalNotFoundAtIndex(XMLString, Metadata)
case noMatchesFound(Failure)
case noParsersMatchedFirstCharacter(XMLString.Char, Metadata)
case allParsersFailed([Failure], Metadata)
case tooFewNumbers(expected: Int, found: Int, Metadata)
case noFirstMemberInCoordinatePair(Metadata)
case noSecondMemberInCoordinatePair(Metadata)
case failedToParseNumber(Metadata)
var message: String {
func error(message: String, index: Int32, stream: XMLString) -> String {
return """
Error at \(index):
\(message)
\(stream[0..<index])\(stream[index..<min(stream.count, index + 30)])
\(String(repeating: "~", count: Int(index)) + "^")
"""
}
switch self {
case .literalNotFoundAtIndex(let literal, let metadata):
return error(message: "Expected \"\(String(copying: literal))\".",
index: metadata.index,
stream: metadata.stream)
case .noMatchesFound(let reason):
return "Failed before finding any matches. The error was: \(reason.message)"
case .noParsersMatchedFirstCharacter(let character, let metadata):
return error(message: "Expected one of the path commands, but found \"\(UnicodeScalar(character))\".",
index: metadata.index, stream:
metadata.stream)
case .allParsersFailed(let failures, let metadata):
return error(message: "Expected to consume all of the input, but all parsers failed with errors: \(failures.map { $0.message }.joined(separator: "\n"))",
index: metadata.index,
stream: metadata.stream)
case .tooFewNumbers(let expected, let found, let metadata):
return error(message: "Expected a multiple of \(expected) numbers, found \(found).",
index: metadata.index,
stream: metadata.stream)
case .noFirstMemberInCoordinatePair(let metadata):
return error(message: "Expected a coordinate pair, but couldn't find any numbers.",
index: metadata.index,
stream: metadata.stream)
case .noSecondMemberInCoordinatePair(let metadata):
return error(message: "Expected a coordinate pair, but only found one number.",
index: metadata.index,
stream: metadata.stream)
case .failedToParseNumber(let metadata):
return error(message: "Expected a number.",
index: metadata.index,
stream: metadata.stream)
}
}
var index: Int32 {
switch self {
case .literalNotFoundAtIndex(_, let metadata):
return metadata.index
case .noMatchesFound(let metadata):
return metadata.index
case .noParsersMatchedFirstCharacter(_, let metadata):
return metadata.index
case .allParsersFailed(_, let metadata):
return metadata.index
case .tooFewNumbers(_, _, let metadata):
return metadata.index
case .noFirstMemberInCoordinatePair(let metadata):
return metadata.index
case .noSecondMemberInCoordinatePair(let metadata):
return metadata.index
case .failedToParseNumber(let metadata):
return metadata.index
}
}
}
enum ParseResult<Wrapped> {
case ok(Wrapped, Int32)
case error(Failure)
func map<T>(_ transformer: (Wrapped, Int32) -> (ParseResult<T>)) -> ParseResult<T> {
switch self {
case .ok(let value, let index):
return transformer(value, index)
case .error(let error): return .error(error)
}
}
func chain<T>(into stream: XMLString, _ transformer: Parser<T>) -> ParseResult<T> {
switch self {
case .ok(_, let index):
return transformer(stream, index)
case .error(let error): return .error(error)
}
}
}
func oneOrMore<T>(of parser: @escaping Parser<T>) -> Parser<[T]> {
return { stream, index in
switch parser(stream, index) {
case .ok(let result, let index):
var nextIndex = index
var results = [result]
findMoreMatches: while true {
switch parser(stream, nextIndex) {
case .ok(let result, let index):
nextIndex = index
results.append(result)
case .error:
break findMoreMatches
}
}
return .ok(results, nextIndex)
case .error(let error):
return .error(.noMatchesFound(error))
}
}
}
func literal(_ text: XMLString) -> Parser<XMLString> {
return { (stream: XMLString, index: Int32) in
if stream.matches(text, at: index) {
return .ok(text, index + text.count)
} else {
return .error(.literalNotFoundAtIndex(text, .init(index: index, stream: stream)))
}
}
}
func consumeAll<T>(using parsers: [Parser<T>]) -> Parser<ContiguousArray<T>> {
return { (stream: XMLString, index: Int32) in
var index = index,
results: ContiguousArray<T> = [],
errors: ContiguousArray<Failure> = []
errors.reserveCapacity(parsers.count)
results.reserveCapacity(Int(stream.count) / 3)
var next = index
untilNoMatchFound:
while true {
next = index
while next != stream.count,
(stream[next] == .whitespace || stream[next] == .newline) {
next += 1
}
checkAllParsers:
for parser in parsers {
switch parser(stream, next) {
case .ok(let result, let currentIndex):
results.append(result)
index = currentIndex
errors.removeAll()
continue untilNoMatchFound
case .error(let error):
errors.append(error)
}
}
if index == stream.count {
return .ok(results, index)
} else {
if errors.first(where: { (error: Failure) -> Bool in
if case .literalNotFoundAtIndex = error {
return false
} else {
return true
}
}) == nil {
return .error(.noParsersMatchedFirstCharacter(stream[next], .init(index: next, stream: stream)))
} else {
var furthestError = errors.first
var foundUnequalIndex = false
for error in errors.dropFirst() {
let lastIndex = furthestError?.index ?? -1
if error.index != lastIndex {
foundUnequalIndex = true
if error.index > lastIndex {
furthestError = error
}
}
}
if let furthestError = furthestError,
foundUnequalIndex {
return .error(furthestError)
} else {
return .error(.allParsersFailed(Array(errors), .init(index: next, stream: stream)))
}
}
}
}
}
}
func empty() -> Parser<()> {
return { _, index in
.ok((), index)
}
}