Add SwiftTL

This commit is contained in:
Isaac 2026-03-27 19:22:57 +08:00
parent 2907ddbe46
commit 22e58eb5e4
72 changed files with 13931 additions and 0 deletions

View file

@ -0,0 +1,15 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "SwiftTL",
dependencies: [
],
targets: [
.executableTarget(
name: "SwiftTL",
dependencies: []),
]
)

View file

@ -0,0 +1,3 @@
# SwiftTL

View file

@ -0,0 +1,986 @@
import Foundation
private let reservedIdentifiers: [String] = [
"protocol",
"private"
]
private extension String {
var camelCased: String {
var result = ""
var capitalizeNext = false
for c in self {
if c == "_" {
capitalizeNext = true
} else {
if capitalizeNext {
capitalizeNext = false
result.append(c.uppercased())
} else {
result.append(c)
}
}
}
return result
}
var camelCasedAndEscaped: String {
var result = self.camelCased
if reservedIdentifiers.contains(result) {
result = "`\(result)`"
}
return result
}
}
private struct CodeWriter {
private var code: String = ""
private var indentLevel: Int = 0
private let indentString: String = " "
private var currentIndent: String {
String(repeating: indentString, count: indentLevel)
}
mutating func indent() {
indentLevel += 1
}
mutating func dedent() {
indentLevel = max(0, indentLevel - 1)
}
mutating func line(_ text: String = "") {
if text.isEmpty {
code += "\n"
} else {
code += currentIndent + text + "\n"
}
}
mutating func lines(_ text: String) {
for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
code += "\n"
} else {
code += currentIndent + line + "\n"
}
}
}
func output() -> String {
return code
}
}
/*private extension QualifiedName {
var camelCased: String {
if let namespace = self.namespace {
return "\(namespace).\(self.value.camelCased)"
} else {
return self.value.camelCased
}
}
}*/
private func typeReferenceRepresentation(_ type: Resolver.TypeReference) -> String {
switch type {
case .int32:
return "Int32"
case .int64:
return "Int64"
case .int256:
return "Int256"
case .double:
return "Double"
case .bytes:
return "Buffer"
case .string:
return "String"
case .bool:
return "Bool"
case .boolTrue:
return "bool"
case let .bareVector(elementType):
return "[\(typeReferenceRepresentation(elementType))]"
case let .boxedVector(elementType):
return "[\(typeReferenceRepresentation(elementType))]"
case let .bareConstructor(typeName, _):
return "Api.\(typeName)"
case let .boxedType(typeName):
return "Api.\(typeName)"
}
}
private extension Resolver.SumType {
func hasDirectReference(to otherTypes: [Resolver.SumType], typeMap: [QualifiedName: Resolver.SumType]) throws -> Bool {
for (_, constructor) in self.constructors {
for argument in constructor.arguments {
switch argument.type {
case .int32:
break
case .int64:
break
case .int256:
break
case .double:
break
case .bytes:
break
case .string:
break
case .bool:
break
case .boolTrue:
break
case .bareVector:
break
case .boxedVector:
break
case .bareConstructor(let typeName, _), .boxedType(let typeName):
for otherType in otherTypes {
if typeName == otherType.name {
return true
}
}
guard let referencedType = typeMap[typeName] else {
throw CodeGenerator.CodeGenerationError(text: "Type \(typeName) not found")
}
var mergedTypes = otherTypes
if !mergedTypes.contains(where: { $0.name == self.name }) {
mergedTypes.append(self)
}
if try referencedType.hasDirectReference(to: mergedTypes, typeMap: typeMap) {
return true
}
}
}
}
return false
}
}
private extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var seen: Set<Iterator.Element> = []
return filter { seen.insert($0).inserted }
}
}
enum CodeGenerator {
struct CodeGenerationError: Error, CustomStringConvertible {
var text: String
var description: String {
return self.text
}
}
static func generate(types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)], typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])], stubFunctions: Bool = false) throws -> [String: String] {
var files: [String: String] = [:]
var functions = functions
functions.append(Resolver.Function(name: QualifiedName(namespace: "help", value: "test"), id: UInt32(bitPattern: -1058929929), arguments: [], result: .boxedType(QualifiedName(namespace: nil, value: "Bool"))))
files["Api0.swift"] = try generateMainFile(types: types, functions: functions, constructorOrder: constructorOrder)
for index in 0 ..< typeOrder.count {
files["Api\(index + 1).swift"] = try generateImplFile(types: types, functions: functions, typeOrder: typeOrder[index], stubFunctions: stubFunctions)
}
return files
}
private static func generateMainFile(types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)]) throws -> String {
var writer = CodeWriter()
writer.line()
var namespaces = Set<String>()
for type in types {
if let namespace = type.name.namespace {
namespaces.insert(namespace)
}
}
var functionNamespaces = Set<String>()
for function in functions {
if let namespace = function.name.namespace {
functionNamespaces.insert(namespace)
}
}
writer.line("public enum Api {")
writer.indent()
for namespace in namespaces.sorted(by: { $0 < $1 }) {
writer.line("public enum \(namespace) {}")
}
writer.line("public enum functions {")
writer.indent()
for namespace in functionNamespaces.sorted(by: { $0 < $1 }) {
writer.line("public enum \(namespace) {}")
}
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line()
var typeMap: [QualifiedName: Resolver.SumType] = [:]
for type in types {
typeMap[type.name] = type
}
writer.line("fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {")
writer.indent()
writer.line("var dict: [Int32 : (BufferReader) -> Any?] = [:]")
writer.line("dict[-1471112230] = { return $0.readInt32() }")
writer.line("dict[570911930] = { return $0.readInt64() }")
writer.line("dict[571523412] = { return $0.readDouble() }")
writer.line("dict[0x0929C32F] = { return parseInt256($0) }")
writer.line("dict[-1255641564] = { return parseString($0) }")
for (typeName, constructorName) in constructorOrder {
guard let type = typeMap[typeName] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
var found = false
for (_, constructor) in type.constructors {
if constructor.name.value == constructorName {
found = true
writer.line("dict[\(Int32(bitPattern: constructor.id))] = { return Api.\(type.name).parse_\(constructor.name.value)($0) }")
break
}
}
if !found {
throw CodeGenerationError(text: "Constructor \(constructorName) not found")
}
}
writer.line("return dict")
writer.dedent()
writer.line("}()")
writer.line()
writer.line("public extension Api {")
writer.indent()
writer.line("static func parse(_ buffer: Buffer) -> Any? {")
writer.indent()
writer.line("let reader = BufferReader(buffer)")
writer.line("if let signature = reader.readInt32() {")
writer.indent()
writer.line("return parse(reader, signature: signature)")
writer.dedent()
writer.line("}")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.line()
writer.line("static func parse(_ reader: BufferReader, signature: Int32) -> Any? {")
writer.indent()
writer.line("if let parser = parsers[signature] {")
writer.indent()
writer.line("return parser(reader)")
writer.dedent()
writer.line("} else {")
writer.indent()
writer.line("telegramApiLog(\"Type constructor \\(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found\")")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line()
writer.line("static func parseVector<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {")
writer.indent()
writer.line("if let count = reader.readInt32() {")
writer.indent()
writer.line("var array = [T]()")
writer.line("var i: Int32 = 0")
writer.line("while i < count {")
writer.indent()
writer.line("var signature = elementSignature")
writer.line("if elementSignature == 0 {")
writer.indent()
writer.line("if let unboxedSignature = reader.readInt32() {")
writer.indent()
writer.line("signature = unboxedSignature")
writer.dedent()
writer.line("} else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line("if elementType == Buffer.self {")
writer.indent()
writer.line("if let item = parseBytes(reader) as? T {")
writer.indent()
writer.line("array.append(item)")
writer.dedent()
writer.line("} else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("} else {")
writer.indent()
writer.line("if let item = Api.parse(reader, signature: signature) as? T {")
writer.indent()
writer.line("array.append(item)")
writer.dedent()
writer.line("} else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line("i += 1")
writer.dedent()
writer.line("}")
writer.line("return array")
writer.dedent()
writer.line("}")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.line()
writer.line("static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {")
writer.indent()
writer.line("switch object {")
let typeOrder = constructorOrder.map(\.typeName).unique()
for typeName in typeOrder {
guard let type = typeMap[typeName] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
writer.line("case let _1 as Api.\(type.name):")
writer.indent()
writer.line("_1.serialize(buffer, boxed)")
writer.dedent()
}
writer.line("default:")
writer.indent()
writer.line("break")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
return writer.output()
}
private static func generateImplFile(types: [Resolver.SumType], functions: [Resolver.Function], typeOrder: (types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName]), stubFunctions: Bool) throws -> String {
var writer = CodeWriter()
var typeMap: [QualifiedName: Resolver.SumType] = [:]
for type in types {
typeMap[type.name] = type
}
for (typeName, constructorNames) in typeOrder.types {
writer.line("public extension Api\(typeName.namespace.flatMap { "." + $0 } ?? "") {")
writer.indent()
guard let type = typeMap[typeName] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
let indirectPrefix = try type.hasDirectReference(to: [type], typeMap: typeMap) ? "indirect " : ""
writer.line("\(indirectPrefix)enum \(typeName.value): TypeConstructorDescription {")
writer.indent()
var sortedConstructors: [Resolver.SumType.Constructor] = []
for constructorName in constructorNames {
var foundConstructor: Resolver.SumType.Constructor?
for (_, constructor) in type.constructors {
if constructor.name.value == constructorName {
foundConstructor = constructor
break
}
}
guard let constructor = foundConstructor else {
throw CodeGenerationError(text: "Constructor \(constructorName) -> \(typeName) not found")
}
sortedConstructors.append(constructor)
}
let useStructPattern = true
if useStructPattern {
for constructor in sortedConstructors {
var fieldsString = ""
var initParamsString = ""
var initBodyString = ""
var descriptionFieldsString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
let fieldName = argument.name.camelCasedAndEscaped
let fieldType = typeReferenceRepresentation(argument.type) + (argument.condition != nil ? "?" : "")
if !fieldsString.isEmpty {
fieldsString.append("\n")
}
fieldsString.append("public var \(fieldName): \(fieldType)")
if !initParamsString.isEmpty {
initParamsString.append(", ")
}
initParamsString.append("\(fieldName): \(fieldType)")
if !initBodyString.isEmpty {
initBodyString.append("\n")
}
initBodyString.append("self.\(fieldName) = \(fieldName)")
if !descriptionFieldsString.isEmpty {
descriptionFieldsString.append(", ")
}
descriptionFieldsString.append("(\"\(fieldName)\", ConstructorParameterDescription(self.\(fieldName)))")
}
if !fieldsString.isEmpty {
writer.line("public class Cons_\(constructor.name.value): TypeConstructorDescription {")
writer.indent()
writer.lines(fieldsString)
writer.line("public init(\(initParamsString)) {")
writer.indent()
writer.lines(initBodyString)
writer.dedent()
writer.line("}")
writer.line("public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) {")
writer.indent()
writer.line("return (\"\(constructor.name.value)\", [\(descriptionFieldsString)])")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
}
}
}
for constructor in sortedConstructors {
let hasFields = constructor.arguments.contains { if case .boolTrue = $0.type { return false } else { return true } }
if useStructPattern && hasFields {
writer.line("case \(constructor.name.value)(Cons_\(constructor.name.value))")
} else {
var argumentsString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
if !argumentsString.isEmpty {
argumentsString.append(", ")
}
argumentsString.append(argument.name.camelCased)
argumentsString.append(": ")
argumentsString.append(typeReferenceRepresentation(argument.type))
if argument.condition != nil {
argumentsString.append("?")
}
}
writer.line("case \(constructor.name.value)\(argumentsString.isEmpty ? "" : "(\(argumentsString))")")
}
}
writer.line()
writer.line("public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {")
writer.indent()
if stubFunctions {
writer.line("#if DEBUG")
writer.line("preconditionFailure()")
writer.line("#else")
writer.line("error")
writer.line("#endif")
} else {
writer.line("switch self {")
for constructor in sortedConstructors {
let hasFields = constructor.arguments.contains { if case .boolTrue = $0.type { return false } else { return true } }
if useStructPattern && hasFields {
writer.line("case .\(constructor.name.value)(let _data):")
writer.indent()
writer.line("if boxed {")
writer.indent()
writer.line("buffer.appendInt32(\(Int32(bitPattern: constructor.id)))")
writer.dedent()
writer.line("}")
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
var argumentAccessor = "_data.\(argument.name.camelCasedAndEscaped)"
if let condition = argument.condition {
writer.line("if Int(_data.\(condition.fieldName.camelCasedAndEscaped)) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
argumentAccessor.append("!")
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
writer.dedent()
writer.line("}")
} else {
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
}
}
writer.line("break")
writer.dedent()
} else {
var argumentsString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
if !argumentsString.isEmpty {
argumentsString.append(", ")
}
argumentsString.append("let ")
argumentsString.append(argument.name.camelCasedAndEscaped)
}
writer.line("case .\(constructor.name.value)\(argumentsString.isEmpty ? "" : "(\(argumentsString))"):")
writer.indent()
writer.line("if boxed {")
writer.indent()
writer.line("buffer.appendInt32(\(Int32(bitPattern: constructor.id)))")
writer.dedent()
writer.line("}")
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
var argumentAccessor = "\(argument.name.camelCasedAndEscaped)"
if let condition = argument.condition {
writer.line("if Int(\(condition.fieldName)) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
argumentAccessor.append("!")
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
writer.dedent()
writer.line("}")
} else {
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
}
}
writer.line("break")
writer.dedent()
}
}
writer.line("}")
} // end if !stubFunctions
writer.dedent()
writer.line("}")
writer.line()
writer.line("public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) {")
writer.indent()
if stubFunctions {
writer.line("#if DEBUG")
writer.line("preconditionFailure()")
writer.line("#else")
writer.line("error")
writer.line("#endif")
} else {
writer.line("switch self {")
for constructor in sortedConstructors {
let hasFields = constructor.arguments.contains { if case .boolTrue = $0.type { return false } else { return true } }
if useStructPattern && hasFields {
var argumentSerializationString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
if !argumentSerializationString.isEmpty {
argumentSerializationString.append(", ")
}
argumentSerializationString.append("(\"\(argument.name.camelCasedAndEscaped)\", ConstructorParameterDescription(_data.\(argument.name.camelCasedAndEscaped)))")
}
writer.line("case .\(constructor.name.value)(let _data):")
writer.indent()
writer.line("return (\"\(constructor.name.value)\", [\(argumentSerializationString)])")
writer.dedent()
} else {
var argumentsString = ""
var argumentSerializationString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
if !argumentsString.isEmpty {
argumentsString.append(", ")
}
if !argumentSerializationString.isEmpty {
argumentSerializationString.append(", ")
}
argumentsString.append("let ")
argumentsString.append(argument.name.camelCasedAndEscaped)
argumentSerializationString.append("(\"\(argument.name.camelCasedAndEscaped)\", \(argument.name.camelCasedAndEscaped) as Any)")
}
writer.line("case .\(constructor.name.value)\(argumentsString.isEmpty ? "" : "(\(argumentsString))"):")
writer.indent()
writer.line("return (\"\(constructor.name.value)\", [\(argumentSerializationString)])")
writer.dedent()
}
}
writer.line("}")
} // end if !stubFunctions for descriptionFields
writer.dedent()
writer.line("}")
writer.line()
for constructor in sortedConstructors {
writer.line("public static func parse_\(constructor.name.value)(_ reader: BufferReader) -> \(typeName.value)? {")
writer.indent()
if stubFunctions {
writer.line("#if DEBUG")
writer.line("preconditionFailure()")
writer.line("#else")
writer.line("error")
writer.line("#endif")
} else {
if constructor.arguments.contains(where: { if case .boolTrue = $0.type { return false } else { return true } }) {
var argumentIndex = 0
var argumentCheckString = ""
var argumentCollectionString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
writer.line("var _\(argumentIndex + 1): \(typeReferenceRepresentation(argument.type))?")
if let condition = argument.condition {
guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else {
throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found")
}
writer.line("if Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)")
writer.dedent()
writer.line("}")
} else {
try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)")
}
if !argumentCheckString.isEmpty {
argumentCheckString.append(" && ")
}
argumentCheckString.append("_c\(argumentIndex + 1)")
if !argumentCollectionString.isEmpty {
argumentCollectionString.append(", ")
}
argumentCollectionString.append("\(argument.name.camelCased): _\(argumentIndex + 1)")
if argument.condition == nil {
argumentCollectionString.append("!")
}
argumentIndex += 1
}
var checkIndex = 0
for argument in constructor.arguments {
if case .boolTrue = argument.type {
continue
}
if let condition = argument.condition {
guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else {
throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found")
}
writer.line("let _c\(checkIndex + 1) = (Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) == 0) || _\(checkIndex + 1) != nil")
} else {
writer.line("let _c\(checkIndex + 1) = _\(checkIndex + 1) != nil")
}
checkIndex += 1
}
writer.line("if \(argumentCheckString) {")
writer.indent()
if useStructPattern && !argumentCollectionString.isEmpty {
writer.line("return Api.\(typeName).\(constructor.name.value)(Cons_\(constructor.name.value)(\(argumentCollectionString)))")
} else {
writer.line("return Api.\(typeName).\(constructor.name.value)\(argumentCollectionString.isEmpty ? "" : "(\(argumentCollectionString))")")
}
writer.dedent()
writer.line("}")
writer.line("else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
} else {
writer.line("return Api.\(typeName).\(constructor.name.value)")
}
} // end if !stubFunctions
writer.dedent()
writer.line("}")
}
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
}
if !typeOrder.functions.isEmpty {
for functionName in typeOrder.functions {
writer.line("public extension Api.functions\(functionName.namespace.flatMap { "." + $0 } ?? "") {")
writer.indent()
var foundFunction: Resolver.Function?
for function in functions {
if function.name == functionName {
foundFunction = function
break
}
}
guard let function = foundFunction else {
throw CodeGenerationError(text: "Function \(functionName) not found")
}
var argumentsString = ""
for argument in function.arguments {
if case .boolTrue = argument.type {
continue
}
if !argumentsString.isEmpty {
argumentsString.append(", ")
}
argumentsString.append(argument.name.camelCasedAndEscaped)
argumentsString.append(": ")
argumentsString.append(typeReferenceRepresentation(argument.type))
if argument.condition != nil {
argumentsString.append("?")
}
}
writer.line("static func \(function.name.value)(\(argumentsString)) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<\(typeReferenceRepresentation(function.result))>) {")
writer.indent()
writer.line("let buffer = Buffer()")
writer.line("buffer.appendInt32(\(Int32(bitPattern: function.id)))")
var argumentSerializationString = ""
for argument in function.arguments {
if case .boolTrue = argument.type {
continue
}
var argumentAccessor = "\(argument.name.camelCasedAndEscaped)"
if let condition = argument.condition {
guard let _ = function.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else {
throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found")
}
writer.line("if Int(\(condition.fieldName)) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
argumentAccessor.append("!")
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
writer.dedent()
writer.line("}")
} else {
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
}
if !argumentSerializationString.isEmpty {
argumentSerializationString.append(", ")
}
argumentSerializationString.append("(\"\(argument.name.camelCasedAndEscaped)\", ConstructorParameterDescription(\(argument.name.camelCasedAndEscaped)))")
}
writer.line("return (FunctionDescription(name: \"\(function.name)\", parameters: [\(argumentSerializationString)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> \(typeReferenceRepresentation(function.result))? in")
writer.indent()
writer.line("let reader = BufferReader(buffer)")
writer.line("var result: \(typeReferenceRepresentation(function.result))?")
try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: Resolver.Argument(name: "result", type: function.result, condition: nil), argumentAccessor: "result")
writer.line("return result")
writer.dedent()
writer.line("})")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
}
}
return writer.output()
}
private static func generateFieldSerialization(writer: inout CodeWriter, argument: Resolver.Argument, argumentAccessor: String) {
switch argument.type {
case .int32:
writer.line("serializeInt32(\(argumentAccessor), buffer: buffer, boxed: false)")
case .int64:
writer.line("serializeInt64(\(argumentAccessor), buffer: buffer, boxed: false)")
case .int256:
writer.line("serializeInt256(\(argumentAccessor), buffer: buffer, boxed: false)")
case .double:
writer.line("serializeDouble(\(argumentAccessor), buffer: buffer, boxed: false)")
case .bytes:
writer.line("serializeBytes(\(argumentAccessor), buffer: buffer, boxed: false)")
case .string:
writer.line("serializeString(\(argumentAccessor), buffer: buffer, boxed: false)")
case .bool:
preconditionFailure()
case .boolTrue:
preconditionFailure()
case .bareVector(let elementType), .boxedVector(let elementType):
if case .boxedVector = argument.type {
writer.line("buffer.appendInt32(481674261)")
}
writer.line("buffer.appendInt32(Int32(\(argumentAccessor).count))")
writer.line("for item in \(argumentAccessor) {")
writer.indent()
generateFieldSerialization(writer: &writer, argument: Resolver.Argument(name: "item", type: elementType, condition: nil), argumentAccessor: "item")
writer.dedent()
writer.line("}")
case .bareConstructor:
writer.line("\(argumentAccessor).serialize(buffer, false)")
case .boxedType:
writer.line("\(argumentAccessor).serialize(buffer, true)")
}
}
private static func generateFieldParsing(writer: inout CodeWriter, typeMap: [QualifiedName: Resolver.SumType], argument: Resolver.Argument, argumentAccessor: String) throws {
switch argument.type {
case .int32:
writer.line("\(argumentAccessor) = reader.readInt32()")
case .int64:
writer.line("\(argumentAccessor) = reader.readInt64()")
case .int256:
writer.line("\(argumentAccessor) = parseInt256(reader)")
case .double:
writer.line("\(argumentAccessor) = reader.readDouble()")
case .bytes:
writer.line("\(argumentAccessor) = parseBytes(reader)")
case .string:
writer.line("\(argumentAccessor) = parseString(reader)")
case .bool:
preconditionFailure()
case .boolTrue:
preconditionFailure()
case .bareVector(let elementType), .boxedVector(let elementType):
var elementSignature: Int32 = 0
switch elementType {
case .int32:
elementSignature = -1471112230
case .int64:
elementSignature = 570911930
case .int256:
elementSignature = 0x0929C32F
case .double:
elementSignature = 571523412
case .bytes:
elementSignature = -1255641564
case .string:
elementSignature = -1255641564
case .bool:
elementSignature = 0
case .boolTrue:
elementSignature = 0
case .bareVector:
elementSignature = 0
case .boxedVector:
elementSignature = 0
case let .bareConstructor(typeName, name):
guard let type = typeMap[typeName] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
guard let constructor = type.constructors[name] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
elementSignature = Int32(bitPattern: constructor.id)
case .boxedType:
elementSignature = 0
}
if case .boxedVector = argument.type {
writer.line("if let _ = reader.readInt32() {")
writer.indent()
writer.line("\(argumentAccessor) = Api.parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(elementType)).self)")
writer.dedent()
writer.line("}")
} else {
writer.line("\(argumentAccessor) = Api.parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(elementType)).self)")
}
case let .bareConstructor(typeName, name):
guard let type = typeMap[typeName] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
guard let constructor = type.constructors[name] else {
throw CodeGenerationError(text: "Type \(typeName) not found")
}
writer.line("\(argumentAccessor) = Api.parse(reader, signature: \(Int32(bitPattern: constructor.id)) as? \(typeReferenceRepresentation(argument.type))")
case .boxedType:
writer.line("if let signature = reader.readInt32() {")
writer.indent()
writer.line("\(argumentAccessor) = Api.parse(reader, signature: signature) as? \(typeReferenceRepresentation(argument.type))")
writer.dedent()
writer.line("}")
}
}
}

View file

@ -0,0 +1,228 @@
import Foundation
enum DescriptionParser {
enum TypeReferenceDescription {
case generic(name: String, argumentType: QualifiedName)
case type(name: QualifiedName)
}
struct ArgumentDescription {
struct ConditionDescription {
var fieldName: String
var bitIndex: Int
}
var name: String
var type: TypeReferenceDescription
var condition: ConditionDescription?
}
struct ConstructorDescription {
var name: QualifiedName
var explicitId: UInt32?
var arguments: [ArgumentDescription]
var type: TypeReferenceDescription
}
static func parse(data: String) throws -> (constructors: [ConstructorDescription], functions: [ConstructorDescription]) {
let lines = data.components(separatedBy: "\n")
var typeLines: [String] = []
var functionLines: [String] = []
let skipPrefixes: [String] = [
//"boolFalse#bc799737 = Bool;",
//"boolTrue#997275b5 = Bool;",
"true#3fedd339 = True;",
"vector#1cb5c415 {t:Type} # [ t ] = Vector t;",
"error#c4b9f9bb code:int text:string = Error;",
"null#56730bcc = Null;"
]
let skipContains: [String] = [
"{X:Type}"
]
var isParsingFunctions = false
loop: for line in lines {
if line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty {
// skip
} else if line == "---functions---" {
isParsingFunctions = true
} else {
for string in skipPrefixes {
if line.hasPrefix(string) {
continue loop
}
}
for string in skipContains {
if line.contains(string) {
continue loop
}
}
if isParsingFunctions {
functionLines.append(line)
} else {
typeLines.append(line)
}
}
}
var constructors: [ConstructorDescription] = []
var functions: [ConstructorDescription] = []
for line in typeLines {
do {
let constructor = try self.parseConstructor(string: line)
constructors.append(constructor)
} catch let e {
print("Error while parsing line:\n\(line)\n")
print("\(e)")
throw e
}
}
for line in functionLines {
do {
let constructor = try parseConstructor(string: line)
functions.append(constructor)
} catch let e {
print("Error while parsing line:\n\(line)\n")
print("\(e)")
throw e
}
}
return (constructors, functions)
}
private static func parseConstructor(string: String) throws -> ConstructorDescription {
let parseIdentifier = Parse {
Prefix<Substring>(minLength: 1, while: { $0.isLetter || $0.isNumber || $0 == "_" })
}.map { String($0) }
let parseConditionDescription = Parse {
parseIdentifier
"."
Int.parser(of: Substring.self)
"?"
}.map { fieldName, bitIndex -> ArgumentDescription.ConditionDescription in
ArgumentDescription.ConditionDescription(fieldName: fieldName, bitIndex: bitIndex)
}
let parseQualifiedName = Parse {
parseIdentifier
Optionally {
"."
parseIdentifier
}
}.map { first, second -> QualifiedName in
if let second = second {
return QualifiedName(namespace: first, value: second)
} else {
return QualifiedName(namespace: nil, value: first)
}
}
let parseGenericTypeReference = Parse {
parseIdentifier
"<"
parseQualifiedName
">"
}.map { name, argumentType -> TypeReferenceDescription in
return .generic(name: name, argumentType: argumentType)
}
let parseDirectTypeReference = Parse {
parseQualifiedName
}.map { name -> TypeReferenceDescription in
return .type(name: name)
}
let parseFlagsTypeReference = Parse {
"#"
}.map { () -> TypeReferenceDescription in
return .type(name: QualifiedName(namespace: nil, value: "int"))
}
let parseTypeReference = Parse {
OneOf {
parseFlagsTypeReference
parseGenericTypeReference
parseDirectTypeReference
}
}
let parseArgument = Parse {
parseIdentifier
":"
Optionally {
parseConditionDescription
}
parseTypeReference
}.map { name, condition, type -> ArgumentDescription in
return ArgumentDescription(name: name, type: type, condition: condition)
}
let parseExplicitId = Parse {
"#"
Prefix<Substring> { $0.isHexDigit }
}.map { UInt32($0, radix: 16)! }
let optionalExplicitId = Optionally {
parseExplicitId
}
let manyArguments = Many {
parseArgument
} separator: {
Whitespace()
}
let nameAndConstructor = Parse {
parseQualifiedName
optionalExplicitId
Whitespace()
}.map { name, explicitId, _ -> (name: QualifiedName, explicitId: UInt32?) in
return (name, explicitId)
}
let typeSeparator = Parse {
Whitespace()
"="
Whitespace()
}
let trailerParser = Parse {
Whitespace()
";"
Whitespace()
End()
}.map { _ -> Void in
}
let parseConstructor = Parse {
nameAndConstructor
manyArguments
typeSeparator
parseTypeReference
trailerParser
}.map { nameAndConstructor, arguments, _, type -> ConstructorDescription in
return ConstructorDescription(
name: nameAndConstructor.name,
explicitId: nameAndConstructor.explicitId,
arguments: arguments,
type: type
)
}
var data = string[...]
let result = try parseConstructor.parse(&data)
return result
}
}

View file

@ -0,0 +1,128 @@
import Foundation
enum LegacyOrderParser {
struct LegacyOrderParsingError: Error, CustomStringConvertible {
var text: String
var description: String {
return self.text
}
}
static func parseConstructorOrder(data: String) throws -> [(typeName: QualifiedName, constructorName: String)] {
let lines = data.split(separator: "\n")
var result: [(typeName: QualifiedName, constructorName: String)] = []
for line in lines {
if let startRange = line.range(of: " = { return Api."), let endRange = line.range(of: "($0) }", options: [.backwards], range: nil) {
let parseString = line[startRange.upperBound ..< endRange.lowerBound]
let components = parseString.components(separatedBy: ".parse_")
if components.count != 2 {
continue
}
result.append((QualifiedName(string: components[0]), components[1]))
}
}
return result
}
static func parseTypeOrder(data: String) throws -> (types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName]) {
var resultTypes: [(typeName: QualifiedName, constructorNames: [String])] = []
var resultFunctions: [QualifiedName] = []
let namespaces = data.components(separatedBy: "public extension Api {\n")
enum ParseSection {
case types
case functions
}
for namespaceData in namespaces {
if namespaceData.isEmpty {
continue
}
guard let firstNewline = namespaceData.range(of: "\n") else {
throw LegacyOrderParsingError(text: "No newline in the beginning of the namespace section")
}
let namespaceName: String?
let namespaceContentData: String
let parseSection: ParseSection
if let prefixRange = namespaceData.range(of: " public enum ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex {
namespaceName = nil
namespaceContentData = namespaceData
parseSection = .types
} else if let prefixRange = namespaceData.range(of: " indirect public enum ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex {
namespaceName = nil
namespaceContentData = namespaceData
parseSection = .types
} else if let prefixRange = namespaceData.range(of: " public struct functions {", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.upperBound == firstNewline.lowerBound {
namespaceName = nil
namespaceContentData = namespaceData
parseSection = .functions
} else {
guard let prefixRange = namespaceData.range(of: "public struct ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex else {
throw LegacyOrderParsingError(text: "Missing header prefix in the beginning of the namespace section")
}
guard let trailerRange = namespaceData.range(of: " {", options: [], range: prefixRange.upperBound ..< firstNewline.lowerBound) else {
throw LegacyOrderParsingError(text: "Missing trailing suffix in the beginning of the namespace section")
}
namespaceName = String(namespaceData[prefixRange.upperBound ..< trailerRange.lowerBound])
namespaceContentData = String(namespaceData[firstNewline.upperBound...])
parseSection = .types
}
let namespaceContentLines = namespaceContentData.split(separator: "\n")
switch parseSection {
case .types:
var currentType: (typeName: QualifiedName, constructorNames: [String])?
for line in namespaceContentLines {
if let typePrefixRange = line.range(of: " public enum "), typePrefixRange.lowerBound == line.startIndex, let typeSuffixRange = line.range(of: ": TypeConstructorDescription {"), typeSuffixRange.upperBound == line.endIndex {
let typeName = String(line[typePrefixRange.upperBound ..< typeSuffixRange.lowerBound])
if let currentType = currentType {
resultTypes.append(currentType)
}
currentType = (QualifiedName(namespace: namespaceName, value: typeName), [])
} else if let typePrefixRange = line.range(of: " indirect public enum "), typePrefixRange.lowerBound == line.startIndex, let typeSuffixRange = line.range(of: ": TypeConstructorDescription {"), typeSuffixRange.upperBound == line.endIndex {
let typeName = String(line[typePrefixRange.upperBound ..< typeSuffixRange.lowerBound])
if let currentType = currentType {
resultTypes.append(currentType)
}
currentType = (QualifiedName(namespace: namespaceName, value: typeName), [])
} else if currentType != nil, let constructorPrefixRange = line.range(of: " case "), constructorPrefixRange.lowerBound == line.startIndex {
let constructorName: String
if let bracketRange = line.range(of: "(") {
constructorName = String(line[constructorPrefixRange.upperBound ..< bracketRange.lowerBound])
} else {
constructorName = String(line[constructorPrefixRange.upperBound...])
}
currentType?.constructorNames.append(constructorName)
}
}
if let currentType = currentType {
resultTypes.append(currentType)
}
case .functions:
var currentNamespace: String?
for line in namespaceContentLines {
if let namespacePrefixRange = line.range(of: " public struct "), namespacePrefixRange.lowerBound == line.startIndex, let namespaceSuffixRange = line.range(of: " {"), namespaceSuffixRange.upperBound == line.endIndex {
currentNamespace = String(line[namespacePrefixRange.upperBound ..< namespaceSuffixRange.lowerBound])
} else if let functionPrefixRange = line.range(of: " public static func "), functionPrefixRange.lowerBound == line.startIndex {
let functionName: String
if let bracketRange = line.range(of: "(") {
functionName = String(line[functionPrefixRange.upperBound ..< bracketRange.lowerBound])
} else {
functionName = String(line[functionPrefixRange.upperBound...])
}
resultFunctions.append(QualifiedName(namespace: currentNamespace, value: functionName))
}
}
}
}
return (resultTypes, resultFunctions)
}
}

View file

@ -0,0 +1,146 @@
/// A custom parameter attribute that constructs parsers from closures. The constructed parser
/// runs each parser in the closure, one after another, till one succeeds with an output.
///
/// The ``OneOf`` parser acts as an entry point into `@OneOfBuilder` syntax where you can list
/// all of the parsers you want to try. For example, to parse a currency symbol into an enum
/// of supported currencies:
///
/// ```swift
/// enum Currency { case eur, gbp, usd }
/// let currency = OneOf {
/// "".map { Currency.eur }
/// "£".map { Currency.gbp }
/// "$".map { Currency.usd }
/// }
/// let money = Parse { currency; Int.parser() }
///
/// try currency.parse("$100") // (.usd, 100)
/// ```
@resultBuilder
public enum OneOfBuilder {
/// Provides support for `for`-`in` loops in ``OneOfBuilder`` blocks.
///
/// Useful for building up a parser from a dynamic source, like for a case-iterable enum:
///
/// ```swift
/// enum Role: String, CaseIterable {
/// case admin
/// case guest
/// case member
/// }
///
/// let roleParser = Parse {
/// for role in Role.allCases {
/// status.rawValue.map { role }
/// }
/// }
/// ```
@inlinable
public static func buildArray<P>(_ parsers: [P]) -> Parsers.OneOfMany<P> {
.init(parsers)
}
/// Provides support for specifying a parser in ``OneOfBuilder`` blocks.
@inlinable
static public func buildBlock<P: Parser>(_ parser: P) -> P {
parser
}
/// Provides support for `if`-`else` statements in ``OneOfBuilder`` blocks, producing a
/// conditional parser for the `if` branch.
///
/// ```swift
/// OneOf {
/// if useLegacyParser {
/// LegacyParser()
/// } else {
/// NewParser()
/// }
/// }
/// ```
@inlinable
public static func buildEither<TrueParser, FalseParser>(
first parser: TrueParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
.first(parser)
}
/// Provides support for `if`-`else` statements in ``OneOfBuilder`` blocks, producing a
/// conditional parser for the `if` branch.
///
/// ```swift
/// OneOf {
/// if useLegacyParser {
/// LegacyParser()
/// } else {
/// NewParser()
/// }
/// }
/// ```
@inlinable
public static func buildEither<TrueParser, FalseParser>(
second parser: FalseParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
.second(parser)
}
/// Provides support for `if` statements in ``OneOfBuilder`` blocks, producing an optional parser.
///
/// ```swift
/// let whitespace = OneOf {
/// if shouldParseNewlines {
/// "\r\n"
/// "\r"
/// "\n"
/// }
///
/// " "
/// "\t"
/// }
/// ```
@inlinable
public static func buildIf<P>(_ parser: P?) -> OptionalOneOf<P> {
.init(wrapped: parser)
}
/// Provides support for `if #available` statements in ``OneOfBuilder`` blocks, producing an
/// optional parser.
@inlinable
public static func buildLimitedAvailability<P>(_ parser: P?) -> OptionalOneOf<P> {
.init(wrapped: parser)
}
/// A parser that parses output from an optional parser.
///
/// You won't typically construct this parser directly, but instead will use standard `if`
/// statements in a ``OneOfBuilder`` block to automatically build optional parsers:
///
/// ```swift
/// let whitespace = OneOf {
/// if shouldParseNewlines {
/// "\r\n"
/// "\r"
/// "\n"
/// }
///
/// " "
/// "\t"
/// }
/// ```
public struct OptionalOneOf<Wrapped: Parser>: Parser {
@usableFromInline
let wrapped: Wrapped?
@usableFromInline
init(wrapped: Wrapped?) {
self.wrapped = wrapped
}
@inlinable
public func parse(_ input: inout Wrapped.Input) throws -> Wrapped.Output {
guard let wrapped = self.wrapped
else { throw ParsingError.manyFailed([], at: input) }
return try wrapped.parse(&input)
}
}
}

View file

@ -0,0 +1,103 @@
/// A custom parameter attribute that constructs parsers from closures. The constructed parser
/// runs a number of parsers, one after the other, and accumulates their outputs.
///
/// The ``Parse`` parser acts as an entry point into `@ParserBuilder` syntax, where you can list
/// all of the parsers you want to run. For example, to parse two comma-separated integers:
///
/// ```swift
/// try Parse {
/// Int.parser()
/// ","
/// Int.parser()
/// }
/// .parse("123,456") // (123, 456)
/// ```
@resultBuilder
public enum ParserBuilder {
/// Provides support for specifying a parser in ``ParserBuilder`` blocks.
@inlinable
public static func buildBlock<P: Parser>(_ parser: P) -> P {
parser
}
/// Provides support for `if`-`else` statements in ``ParserBuilder`` blocks, producing a
/// conditional parser for the `if` branch.
///
/// ```swift
/// Parse {
/// "Hello"
/// if shouldParseComma {
/// ", "
/// } else {
/// " "
/// }
/// Rest()
/// }
/// ```
@inlinable
public static func buildEither<TrueParser, FalseParser>(
first parser: TrueParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
.first(parser)
}
/// Provides support for `if`-`else` statements in ``ParserBuilder`` blocks, producing a
/// conditional parser for the `else` branch.
///
/// ```swift
/// Parse {
/// "Hello"
/// if shouldParseComma {
/// ", "
/// } else {
/// " "
/// }
/// Rest()
/// }
/// ```
@inlinable
public static func buildEither<TrueParser, FalseParser>(
second parser: FalseParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
.second(parser)
}
/// Provides support for `if` statements in ``ParserBuilder`` blocks, producing an optional
/// parser.
@inlinable
public static func buildIf<P: Parser>(_ parser: P?) -> P? {
parser
}
/// Provides support for `if` statements in ``ParserBuilder`` blocks, producing a void parser for
/// a given void parser.
///
/// ```swift
/// Parse {
/// "Hello"
/// if shouldParseComma {
/// ","
/// }
/// " "
/// Rest()
/// }
/// ```
@inlinable
public static func buildIf<P>(_ parser: P?) -> Parsers.OptionalVoid<P> {
.init(wrapped: parser)
}
/// Provides support for `if #available` statements in ``ParserBuilder`` blocks, producing an
/// optional parser.
@inlinable
public static func buildLimitedAvailability<P: Parser>(_ parser: P?) -> P? {
parser
}
/// Provides support for `if #available` statements in ``ParserBuilder`` blocks, producing a void
/// parser for a given void parser.
@inlinable
public static func buildLimitedAvailability<P>(_ parser: P?) -> Parsers.OptionalVoid<P> {
.init(wrapped: parser)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,132 @@
# Backtracking
Learn what backtracking is, how it affects the performance of your parsers, and how to avoid it when
unnecessary.
## Overview
Backtracking is the process of restoring an input to its original value when parsing fails. While it
can be very useful, backtracking can lead to more complicated parser logic than necessary, and
backtracking too often can lead to performance issues. For this reason, most parsers are not
required to backtrack, and can therefore fail _and_ still consume from the input.
The primary way to make use of backtracking in your parsers is through the ``OneOf`` parser, which
tries many parsers on an input and chooses the first that succeeds. This allows you to try many
parsers on the same input, regardless of how much each parser consumes:
```swift
enum Currency { case eur, gbp, usd }
let currency = OneOf {
"€".map { Currency.eur }
"£".map { Currency.gbp }
"$".map { Currency.usd }
}
```
## When to backtrack in your parsers?
If you only use the parsers and operators that ship with this library, and in particular you do not
create custom conformances to the ``Parser`` protocol, then you never need to worry about explicitly
backtracking your input because it will be handled for you automatically. The primary way to allow
for backtracking is via the ``OneOf`` parser, but there are a few other parsers that also backtrack
internally.
One such example is the ``Optionally`` parser, which transforms any parser into one that cannot fail
by catching any thrown errors and returning `nil`:
```swift
let parser = Parse {
"Hello,"
Optionally { " "; Bool.parser() }
" world!"
}
try parser.parse("Hello, world!") // nil
try parser.parse("Hello, true world!") // true
```
If the parser captured inside ``Optionally`` fails then it backtracks the input to its state before
the parser ran. In particular, if the `Bool.parser()` fails then it will make sure to undo
consuming the leading space " " so that later parsers can try.
Another example of a parser that internally backtracks is the ``Parser/replaceError(with:)``
operator, which coalesces any error thrown by a parser into a default output value:
```swift
let parser = Parse {
"Hello,"
Optionally { " "; Bool.parser() }
.replaceError(with: false)
" world!"
}
try parser.parse("Hello, world!") // false
try parser.parse("Hello, true world!") // true
```
It backtracks the input to its original value when the parser fails so that later parsers can try.
The only time you need to worry about explicitly backtracking input is when making your own
``Parser`` conformances. As a general rule of thumb, if your parser recovers from all failures
in the `parse` method then it should backtrack the input to its state before the error was thrown.
This is exactly how ``OneOf``, ``Optionally`` and ``Parser/replaceError(with:)`` work.
## Performance
If used naively, backtracking can lead to less performant parsing code. For example, if we wanted to
parse two integers from a string that were separated by either a dash "-" or slash "/", then we
could write this as:
```swift
OneOf {
Parse { Int.parser(); "-"; Int.parser() } // 1
Parse { Int.parser(); "/"; Int.parser() } // 2
}
```
However, parsing slash-separated integers is not going to be performant because it will first run
the entire 1⃣ parser until it fails, then backtrack to the beginning, and run the 2⃣ parser. In
particular, the first integer will get parsed twice, unnecessarily repeating that work.
On the other hand, we can factor out the common work of the parser and localize the backtracking
``OneOf`` work to make a much more performant parser:
```swift
Parse {
Int.parser()
OneOf { "-"; "/" }
Int.parser()
}
```
We can even write a benchmark to measure the performance difference:
```swift
let first = OneOf {
Parse { Int.parser(); "-"; Int.parser() }
Parse { Int.parser(); "/"; Int.parser() }
}
benchmark("First") {
precondition(try! first.parse("100/200") == (100, 200))
}
let second = Parse {
Int.parser()
OneOf { "-"; "/" }
Int.parser()
}
benchmark("Second") {
precondition(try! second.parse("100/200") == (100, 200))
}
```
Running this produces the following results:
```
name time std iterations
----------------------------------------
First 1500.000 ns ± 19.75 % 856753
Second 917.000 ns ± 15.89 % 1000000
```
The second parser takes only 60% of the time to run that the first parser does.

View file

@ -0,0 +1,131 @@
# Design
Learn how the library is designed, including its use of protocols, result builders and operators.
## Protocol
The design of the library is largely inspired by the Swift standard library and Apple's Combine
framework. A parser is represented as a protocol that many types conform to, and then parser
transformations (also known as "combinators") are methods that return concrete types conforming to
the parser protocol.
For example, to parse all the characters from the beginning of a substring until you encounter a
comma you can use the `Prefix` parser:
```swift
let parser = Prefix { $0 != "," }
var input = "Hello,World"[...]
try parser.parse(&input) // "Hello"
input // ",World"
```
The type of this parser is:
```swift
Prefix<Substring>
```
We can `.map` on this parser in order to transform its output, which in this case is the string
"Hello":
```swift
let parser = Prefix { $0 != "," }
.map { $0 + "!!!" }
var input = "Hello,World"[...]
try parser.parse(&input) // "Hello!!!"
input // ",World"
```
The type of this parser is now:
```swift
Parsers.Map<Prefix<Substring>, Substring>
```
Notice that the type of the parser encodes the operations that we performed. This adds a bit of
complexity when using these types, but comes with some performance benefits because Swift can
usually inline and optimize away the creation of those nested types.
## Result Builders
The library takes advantage of Swift's `@resultBuilder` feature to make constructing complex parsers
as fluent as possible, and should be reminiscent of how views are constructed in SwiftUI. The main
entry point into building a parser is the `Parse` builder:
```swift
Parse {
}
```
In this builder block you can specify parsers that will be run one after another. For example, if
you wanted to parse an integer, then a comma, and then a boolean from a string, you can simply do:
```swift
Parse {
Int.parser()
","
Bool.parser()
}
```
Note that the `String` type conforms to the ``Parser`` protocol, and represents a parser that
consumes that exact string from the beginning of an input if it matches, and otherwise fails.
Many of the parsers and operators that come with the library are configured with parser builders
to maximize readability of the parsers. For example, to parse accounting syntax of numbers, where
parenthesized numbers are negative, we can use the ``OneOf`` parser builder:
```swift
let digits = Prefix { $0 >= "0" && $0 <= "9" }.compactMap(Int.init)
let accountingNumber = OneOf {
digits
Parse {
"("; digits; ")"
}
.map { -$0 }
}
try accountingNumber.parse("100") // 100
try accountingNumber.parse("(100)") // -100
```
## Operators
Parser operators (also called "combinators") are methods defined on the ``Parser`` protocol that
return a parser. For example, the ``Parser/map(_:)`` operator is a method that returns something
called a ``Parsers/Map``:
```swift
extension Parser {
public func map<NewOutput>(
_ transform: @escaping (Output) -> NewOutput
) -> Parsers.Map<Self, NewOutput> {
.init(upstream: self, transform: transform)
}
}
```
And ``Parsers/Map`` is a dedicated type that implements the logic of the map operation. In
particular, in runs the upstream parser and then transforms its output:
```swift
extension Parsers {
public struct Map<Upstream: Parser, NewOutput>: Parser {
public let upstream: Upstream
public let transform: (Upstream.Output) -> NewOutput
public func parse(_ input: inout Upstream.Input) rethrows -> NewOutput {
self.transform(try self.upstream.parse(&input))
}
}
}
```
Types that conform to the ``Parser`` protocol but are not constructed directly, and instead are
constructed via operators, are housed in the ``Parsers`` type. It's just an empty enum that
serves as a namespace for such parsers.

View file

@ -0,0 +1,239 @@
# Error messages
Learn how the library reports parsing errors and how to integrate your own custom error messages
into parsers.
## Overview
When a parser fails it throws an error containing information about what went wrong. The actual
error thrown by the parsers shipped with this library is internal, and so it should be considered
opaque. To get a human-readable debug description of the error message you can stringify the error.
For example, the following `UInt8` parser fails to parse a string that would cause it to overflow:
```swift
do {
var input = "1234 Hello"[...]
let number = try UInt8.parser().parse(&input))
} catch {
print(error)
// error: failed to process "UInt8"
// --> input:1:1-4
// 1 | 1234 Hello
// | ^^^^ overflowed 255
}
```
When the ``OneOf`` parser is used and fails, there are multiple errors that can be shown. ``OneOf``
prioritizes the error messages based on which parser got the furthest along. For example, consider
a parser that can parse accounting style of numbers, i.e. plain numbers are considered positive
and numbers in parentheses are considered negative:
```swift
let digits = Prefix { $0 >= "0" && $0 <= "9" }.compactMap(Int.init)
let accountingNumber = OneOf {
digits
Parse {
"("; digits; ")"
}
.map { -$0 }
}
try accountingNumber.parse("100") // 100
try accountingNumber.parse("(100)") // -100
```
If we try parsing something erroneous, such as "(100]", we get multiple error messages, but the
second parser's error shows first since it was able to get the furthest:
```swift
do {
try accountingNumber.parse("(100]")
} catch {
print(error)
// error: multiple failures occurred
//
// error: unexpected input
// --> input:1:5
// 1 | (100]
// | ^ expected ")"
//
// error: unexpected input
// --> input:1:1
// 1 | (100]
// | ^ expected integer
}
```
## Improving error messages
The quality of error messages emitted by a parser can depend on the manner in which the parser was
constructed. Some parser operators are powerful and convenient, but can cause the quality of error
messaging to degrade.
For example, we could construct a parser that consumes a single uncommented line from an input
(_i.e._, a line that does not begin with "//") by using ``Parser/compactMap(_:)`` to check the line
for a prefix:
```swift
let uncommentedLine = Prefix { $0 != "\n" }
.compactMap { $0.starts(with: "//") ? nil : $0 }
try uncommentedLine.parse("// let x = 1")
// error: failed to process "Substring" from "// let x = 1"
// --> input:1:1-12
// 1 | // let x = 1
// | ^^^^^^^^^^^^
```
However, when this parser fails it can only highlight the entire line as having a problem because
it cannot know that the only thing that failed was that the first two characters were slashes.
We can rewrite this parser in a different, but equivalent, way by using the ``Not`` parser to first
confirm that the line does not begin with "//", and then consume the entire line:
```swift
let uncommentedLine = Parse {
Not { "//" }
Prefix { $0 != "\n" }
}
try uncommentedLine.parse("// let x = 1")
// error: unexpected input
// --> input:1:1-2
// 1 | // let x = 1
// | ^^ expected not to be processed
```
This provides better error messaging because ``Not`` knows exactly what matched that we did not want
to match, and so it can highlight those specific characters.
When using the `Many` parser you can improve error messaging by supplying a "terminator" parser,
which is an optional argument. The terminator parser is run after the element and separator
parsers have consumed as much as they can, and allows you to assert on exactly what is left
afterwards.
For example, if a parser is run on an input that has a typo in the last row of data, and a
terminator is not specified, the parser will succeed without consuming that last row and we won't
know what went wrong:
```swift
struct User {
var id: Int
var name: String
var isAdmin: Bool
}
let user = Parse(User.init(id:name:isAdmin:)) {
Int.parser()
","
Prefix { $0 != "," }.map(String.init)
","
Bool.parser()
}
let users = Many {
user
} separator: {
"\n"
}
var input = """
1,Blob,true
2,Blob Jr.,false
3,Blob Sr.,tru
"""[...]
let output = try users.parse(&input)
output.count // 2
input // "\n3,Blob Sr.,tru"
```
However, by adding a terminator to this `users` parser an error will be throw that points to the
exact spot where the typo occurred:
```swift
let users = Many {
user
} separator: {
"\n"
} terminator: {
End()
}
let output = try users.parse(&input)
// error: unexpected input
// --> input:3:11
// 3 | 3,Blob Jr,tru
// | ^ expected "true" or "false"
```
## Throwing your own errors
Although the error type thrown by the parsers that ship in this library is currently internal, and
so should be thought of as opaque, it is still possible to throw your own errors. Your errors will
automatically be reformatted and contextualized to show exactly where the error occurred.
For example, suppose we wanted a parser that only parsed the digits 0-9 from the beginning of a
string and transformed it into an integer. This is subtly different from `Int.parser()` which
supports negative numbers, exponential formatting, and more.
Constructing a `Digits` parser is easy enough, and we can introduce a custom struct error for
customizing the message displayed:
```swift
struct DigitsError: Error {
let message = "Expected a prefix of digits 0-9"
}
struct Digits: Parser {
func parse(_ input: inout Substring) throws -> Int {
let digits = input.prefix { $0 >= "0" && $0 <= "9" }
guard let output = Int(digits)
else {
throw DigitsError()
}
input.removeFirst(digits.count)
return output
}
}
```
If we swap out the `Int.parser()` for a `Digits` parser in `user`:
```swift
let user = Parse(User.init) {
Digits()
","
Prefix { $0 != "," }.map(String.init)
","
Bool.parser()
}
```
And we introduce an incorrect value into the input:
```swift
let input = """
1,Blob,true
-2,Blob Jr.,false
3,Blob Sr.,true
"""[...]
```
Then when running the parser we get a nice error message that shows exactly what went wrong:
```swift
try user.parse(&input)
// error: DigitsError(message: "Expected a prefix of digits 0-9")
// --> input:2:1
// 2 | -2,Blob Sr,false
// | ^
```

View file

@ -0,0 +1,303 @@
# Getting Started
Learn how to integrate Parsing into your project and write your first parser.
## Adding Parsing as a dependency
To use the Parsing library in a SwiftPM project, add it to the dependencies of your Package.swift
and specify the `Parsing` product in any targets that need access to the library:
```swift
let package = Package(
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-parsing", from: "0.7.0"),
],
targets: [
.target(
name: "<target-name>",
dependencies: [.product(name: "Parsing", package: "swift-parsing")]
)
]
)
```
## Your first parser
Suppose you have a string that holds some user data that you want to parse into an array of `User`s:
```swift
let input = """
1,Blob,true
2,Blob Jr.,false
3,Blob Sr.,true
"""
struct User {
var id: Int
var name: String
var isAdmin: Bool
}
```
A naive approach to this would be a nested use of `.split(separator:)`, and then a little bit of
extra work to convert strings into integers and booleans:
```swift
let users = input
.split(separator: "\n")
.compactMap { row -> User? in
let fields = row.split(separator: ",")
guard
fields.count == 3,
let id = Int(fields[0]),
let isAdmin = Bool(String(fields[2]))
else { return nil }
return User(id: id, name: String(fields[1]), isAdmin: isAdmin)
}
```
Not only is this code a little messy, but it is also inefficient since we are allocating arrays for
the `.split` and then just immediately throwing away those values.
It would be more straightforward and efficient to instead describe how to consume bits from the
beginning of the input and convert that into users. This is what this parser library excels at 😄.
We can start by describing what it means to parse a single row, first by parsing an integer off the
front of the string, and then parsing a comma. We can do this by using the ``Parse`` type, which acts
as an entry point into describing a list of parsers that you want to run one after the other to
consume from an input:
```swift
let user = Parse {
Int.parser()
","
}
```
Already this can consume the leading integer and comma from the beginning of the input:
```swift
// Use a mutable substring to verify what is consumed
var input = input[...]
try user.parse(&input) // 1
input // "Blob,true\n2,Blob Jr.,false\n3,Blob Sr.,true"
```
Next we want to take everything up until the next comma for the user's name, and then consume the
comma:
```swift
let user = Parse {
Int.parser()
","
Prefix { $0 != "," }
","
}
```
And then we want to take the boolean at the end of the row for the user's admin status:
```swift
let user = Parse {
Int.parser()
","
Prefix { $0 != "," }
","
Bool.parser()
}
```
Currently this will parse a tuple `(Int, Substring, Bool)` from the input, and we can `.map` on
that to turn it into a `User`:
```swift
let user = Parse {
Int.parser()
","
Prefix { $0 != "," }
","
Bool.parser()
}
.map { User(id: $0, name: String($1), isAdmin: $2) }
```
To make the data we are parsing to more prominent, we can instead pass the transform closure as the
first argument to `Parse`:
```swift
let user = Parse {
User(id: $0, name: String($1), isAdmin: $2)
} with: {
Int.parser()
","
Prefix { $0 != "," }
","
Bool.parser()
}
```
Or we can pass the `User` initializer to `Parse` in a point-free style by first transforming the
`Prefix` parser's output from a `Substring` to a `String`:
```swift
let user = Parse(User.init(id:name:isAdmin:)) {
Int.parser()
","
Prefix { $0 != "," }.map(String.init)
","
Bool.parser()
}
```
That is enough to parse a single user from the input string, leaving behind a newline and the final
two users:
```swift
try user.parse(&input) // User(id: 1, name: "Blob", isAdmin: true)
input // "\n2,Blob Jr.,false\n3,Blob Sr.,true"
```
To parse multiple users from the input we can use the `Many` parser to run the user parser many
times:
```swift
let users = Many {
user
} separator: {
"\n"
}
try users.parse(&input) // [User(id: 1, name: "Blob", isAdmin: true), ...]
input // ""
```
Now this parser can process an entire document of users, and the code is simpler and more
straightforward than the version that uses `.split` and `.compactMap`.
Even better, it's more performant. We've written [benchmarks][benchmarks-readme] for these two
styles of parsing, and the `.split`-style of parsing is more than twice as slow:
```
name time std iterations
------------------------------------------------------------------
README Example.Parser: Substring 3426.000 ns ± 63.40 % 385395
README Example.Ad hoc 7631.000 ns ± 47.01 % 169332
```
Further, if you are willing write your parsers against `UTF8View` instead of `Substring`, you can
eke out even more performance, more than doubling the speed:
```
name time std iterations
------------------------------------------------------------------
README Example.Parser: Substring 3693.000 ns ± 81.76 % 349763
README Example.Parser: UTF8 1272.000 ns ± 128.16 % 999150
README Example.Ad hoc 8504.000 ns ± 59.59 % 151417
```
See the article <doc:StringAbstractions> for more info on how to write parsers against different
string abstraction levels.
We can also compare these times to a tool that Apple's Foundation gives us: `Scanner`. It's a type
that allows you to consume from the beginning of strings in order to produce values, and provides
a nicer API than using `.split`:
```swift
var users: [User] = []
while scanner.currentIndex != input.endIndex {
guard
let id = scanner.scanInt(),
let _ = scanner.scanString(","),
let name = scanner.scanUpToString(","),
let _ = scanner.scanString(","),
let isAdmin = scanner.scanBool()
else { break }
users.append(User(id: id, name: name, isAdmin: isAdmin))
_ = scanner.scanString("\n")
}
```
However, the `Scanner` style of parsing is more than 5 times as slow as the substring parser written
written above, and more than 15 times slower than the UTF-8 parser:
```
name time std iterations
-------------------------------------------------------------------
README Example.Parser: Substring 3481.000 ns ± 65.04 % 376525
README Example.Parser: UTF8 1207.000 ns ± 110.96 % 1000000
README Example.Ad hoc 8029.000 ns ± 44.44 % 163719
README Example.Scanner 19786.000 ns ± 35.26 % 62125
```
Not only are parsers built with the library more succinct and many times more performant than ad hoc
parsers, but they can also be easier to evolve to accommodate more features. For example, right now
our parser does not work correctly when the user's name contains a comma, such as "Blob, Esq.":
```swift
try user.parse("1,Blob, Esq.,true")
// error: unexpected input
// --> input:1:8
// 1 | 1,Blob, Esq.,true
// | ^ expected "true" or "false"
```
The problem is that we are using the comma as a reserved identifier for delineating between fields,
and so a field cannot contain a comma. We can enhance the CSV format to allow for quoting fields
so that they can contain quotes:
```
1,"Blob, Esq.",true
```
To parse quoted fields we can first try parsing a quote, then everything up to the next quote, and
then the trailing quote:
```swift
let quotedField = Parse {
"\""
Prefix { $0 != "\"" }
"\""
}
```
And then to parse a field, in general, we can first try parsing a quoted field, and if that fails we
will just take everything until the next comma. We can do this using the ``OneOf`` parser, which
allows us to run multiple parsers on the same input, and it will take the first that succeeds:
```swift
let field = OneOf {
quotedField
Prefix { $0 != "," }
}
.map(String.init)
```
We can use this parser in the `user` parser, and now it properly handles quoted and non-quoted
fields:
```swift
let user = Parse(User.init) {
Int.parser()
","
field
","
Bool.parser()
}
try user.parse("1,\"Blob, Esq.\",true") // User(id: 1, name: "Blob, Esq.", admin: true)
```
It was quite straightforward to improve the `user` parser to handle quoted fields. Doing the same
with our ad hoc `split`/`compactMap` parser, and even the `Scanner`-based parser, would be a lot
more difficult.
That's the basics of parsing a simple string format, but there's a lot more operators and tricks to
learn in order to performantly parse larger inputs. View the [benchmarks][benchmarks] for examples
of real-life parsing scenarios.
[benchmarks-readme]: https://github.com/pointfreeco/swift-parsing/blob/main/Sources/swift-parsing-benchmark/ReadmeExample.swift
[benchmarks]: https://github.com/pointfreeco/swift-parsing/tree/main/Sources/swift-parsing-benchmark

View file

@ -0,0 +1,69 @@
# Bool
A parser that consumes a Boolean value from the beginning of a string.
This parser only recognizes the literal `"true"` and `"false"` sequence of characters:
```swift
// Parses "true":
var input = "true Hello"[...]
try Bool.parser().parse(&input) // true
input // " Hello"
// Parses "false":
input = "false Hello"[...]
try Bool.parser().parse(&input) // false
input // " Hello"
// Otherwise fails:
input = "1 Hello"[...]
try Bool.parser().parse(&input)
// error: unexpected input
// --> input:1:1
// 1 | 1 Hello
// ^ expected "true" or "false"
```
The `Bool.parser()` method is overloaded to work on a variety of string representations in order
to be as efficient as possible, including `Substring`, `UTF8View`, and more general collections of
UTF-8 code units (see <doc:StringAbstractions> for more info).
Typically Swift can choose the correct overload by using type inference based on what other parsers
you are combining `Bool.parser()` with. For example, if you use `Bool.parser()` with a
`Substring` parser, say the literal `","` parser (see <doc:String> for more information), Swift
will choose the overload that works on substrings:
```swift
let parser = Parse {
Bool.parser()
","
Bool.parser()
}
try parser.parse("true,false") // (true, false)
```
On the other hand, if `Bool.parser()` is used in a context where the input type cannot be inferred,
then you will get an compiler error:
```swift
let parser = Parse {
Bool.parser()
Bool.parser() // 🛑 Ambiguous use of 'parser(of:)'
}
try parser.parse("truefalse")
```
To fix this you can force one of the boolean parsers to be the `Substring` parser, and then the
other will figure it out via type inference:
```swift
let parser = Parse {
Bool.parser(of: Substring.self)
Bool.parser() // ✅
}
try parser.parse("truefalse")
```

View file

@ -0,0 +1,73 @@
# CaseIterable
A parser that consumes a case-iterable, raw representable value from the beginning of a string.
Given a type that conforms to `CaseIterable` and `RawRepresentable` with a `RawValue` of `String`
or `Int`, we can incrementally parse a value of it.
Notably, raw enumerations that conform to `CaseIterable` meet this criteria, so cases of the
following type can be parsed with no extra work:
```swift
enum Role: String, CaseIterable {
case admin
case guest
case member
}
try Parse {
Int.parser()
","
Role.parser()
}
.parse("123,member") // (123, .member)
```
This also works with raw enumerations that are backed by integers:
```swift
enum Role: Int, CaseIterable {
case admin = 1
case guest = 2
case member = 3
}
try Parse {
Int.parser()
","
Role.parser()
}
.parse("123,1") // (123, .admin)
```
The `parser()` method on `CaseIterable` is overloaded to work on a variety of string representations
in order to be as efficient as possible, including `Substring`, `UTF8View`, and more general
collections of UTF-8 code units (see <doc:StringAbstractions> for more info).
Typically Swift can choose the correct overload by using type inference based on what other parsers
you are combining `parser()` with. For example, if you use `Role.parser()` with a
`Substring` parser, like the literal "," parser in the above examples, Swift
will choose the overload that works on substrings.
On the other hand, if `Role.parser()` is used in a context where the input type cannot be inferred,
then you will get an compiler error:
```swift
let parser = Parse {
Int.parser()
Role.parser() // 🛑 Ambiguous use of 'parser(of:)'
}
try parser.parse("123member")
```
To fix this you can force one of the parsers to be the `Substring` parser, and then the
other will figure it out via type inference:
```swift
let parser = Parse {
Int.parser(of: Substring.self)
Role.parser()
}
try parser.parse("123member") // (123, .member)

View file

@ -0,0 +1,14 @@
# CharacterSet
A parser that consumes the characters contained in a `CharacterSet` from the beginning of a string.
For example:
```swift
Parse {
CharacterSet.alphanumerics
CharacterSet.punctuationCharacters
CharacterSet.alphanumerics
}
.parse("Hello...World") // ("Hello", "...", "World")
```

View file

@ -0,0 +1,66 @@
# Float
A parser that consumes a floating-point number from the beginning of a string.
Supports any type that conforms to `BinaryFloatingPoint` and `LosslessStringConvertible`. This
includes `Double`, `Float`, `Float16`, and `Float80`.
Parses the same format parsed by `LosslessStringConvertible.init(_:)` on `BinaryFloatingPoint`.
```swift
var input = "123.45 Hello world"[...]
try Double.parser().parse(&input) // 123.45
input // " Hello world"
input = "-123. Hello world"[...]
try Double.parser().parse(&input) // -123.0
input // " Hello world"
input = "123.123E+2 Hello world"[...]
try Double.parser().parse(&input) // 12312.3
input // " Hello world"
```
The `parser()` static method is overloaded to work on a variety of string representations in order
to be as efficient as possible, including `Substring`, `UTF8View`, and generally collections of
UTF-8 code units (see <doc:StringAbstractions> for more info).
Typically Swift can choose the correct overload by using type inference based on what other parsers
you are combining `parser()` with. For example, if you use `Double.parser()` with a `Substring`
parser, say the literal `","` parser (see <doc:String> for more information), Swift will choose the
overload that works on substrings:
```swift
let parser = Parse {
Double.parser()
","
Double.parser()
}
try parser.parse("1,-2") // (1.0, -2.0)
```
On the other hand, if `Double.parser()` is used in a context where the input type cannot be
inferred, then you will get an compiler error:
```swift
let parser = Parse {
Double.parser()
Double.parser() // 🛑 Ambiguous use of 'parser(of:)'
}
try parser.parse(".1.2")
```
To fix this you can force one of the double parsers to be the `Substring` parser, and then the
other will figure it out via type inference:
```swift
let parser = Parse {
Double.parser(of: Substring.self)
Double.parser() // ✅
}
try parser.parse(".1.2") // (0.1, 0.2)
```

View file

@ -0,0 +1,84 @@
# Int
A parser that consumes an integer from the beginning of a string.
Supports any type that conforms to `FixedWidthInteger`. This includes `Int`, `UInt`, `UInt8`, and
many more.
Parses the same format parsed by `FixedWidthInteger.init(_:radix:)`.
```swift
var input = "123 Hello world"[...]
try Int.parser().parse(&input) // 123
input // " Hello world"
input = "-123 Hello world"
try Int.parser().parse(&input) // -123
input // " Hello world"
```
This parser fails when it does not find an integer at the beginning of the collection:
```swift
var input = "+Hello"[...]
let number = try Int.parser().parse(&input)
// error: unexpected input
// --> input:1:2
// 1 | +Hello
// | ^ expected integer
```
And it fails when the digits extracted from the beginning of the collection would cause the
integer type to overflow:
```swift
var input = "9999999999999999999 Hello"[...]
let number = try Int.parser().parse(&input)
// error: failed to process "Int"
// --> input:1:1-19
// 1 | 9999999999999999999 Hello
// | ^^^^^^^^^^^^^^^^^^^ overflowed 9223372036854775807
```
The static `parser()` method is overloaded to work on a variety of string representations in order
to be as efficient as possible, including `Substring`, `UTF8View`, and generally collections of
UTF-8 code units (see <doc:StringAbstractions> for more info).
Typically Swift can choose the correct overload by using type inference based on what other parsers
you are combining `parser()` with. For example, if you use `Int.parser()` with a `Substring` parser,
say the literal `","` parser (see <doc:String> for more information), Swift will choose the overload
that works on substrings:
```swift
let parser = Parse {
Int.parser()
","
Int.parser()
}
try parser.parse("123,456") // (123, 456)
```
On the other hand, if `Int.parser()` is used in a context where the input type cannot be inferred,
then you will get an compiler error:
```swift
let parser = Parse {
Int.parser() // 🛑 Ambiguous use of 'parser(of:radix:)'
Bool.parser()
}
try parser.parse("123true")
```
To fix this you can force one of the parsers to be the `Substring` parser, and then the
other will figure it out via type inference:
```swift
let parser = Parse {
Int.parser() // ✅
Bool.parser(of: Substring.self)
}
try parser.parse("123true") // (123, true)
```

View file

@ -0,0 +1,38 @@
# String
A parser that consumes a string literal from the beginning of a string.
Many of Swift's string types conform to the ``Parser`` protocol, which allows you to use string types
directly in a parser. For example, to parse two integers separated by a comma we can do:
```swift
try Parse {
Int.parser()
","
Int.parser()
}
.parse("123,456") // (123, 456)
```
The string `","` acts as a parser that consumes a comma from the beginning of an input and fails
if the input does not start with a comma.
Swift's other string representations also conform to ``Parser``, such as `UnicodeScalarView`
and `UTF8View`. This allows you to consume strings from the beginning of an input in a more
efficient manner than is possible with `Substring` (see <doc:StringAbstractions> for more info).
For example, we can conver the above parser to work on the level of `UTF8View`s, which is a
collection of UTF-8 code units:
```swift
try Parse {
Int.parser()
",".utf8
Int.parser()
}
.parse("123,456") // (123, 456)
```
Here `",".utf8` is a `String.UTF8View`, which conforms to the ``Parser`` protocol. Also, by type
inference, Swift is choosing the overload of `Int.parser()` that now works on `UTF8View`s rather
than `Substring`s. See <doc:Int> for more info.

View file

@ -0,0 +1,15 @@
# UUID
A parser that consumes a `UUID` value from the beginning of a string.
For example:
```swift
try Parse {
UUID.parser()
","
Bool.parser()
}
.parse("deadbeef-dead-beef-dead-beefdeadbeef,true")
// (DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF, true)
```

View file

@ -0,0 +1,183 @@
# String Abstractions
Learn how to write parsers on different levels of string abstractions, giving you the ability to
trade performance for correctness where needed.
## Levels of abstraction
The parsers in the library do not work on `String`s directly, and instead operate on _views_ into a
string, such as `Substring`, `UnicodeScalarView` and `UTF8View`. Each of these types represents a
particular kind of "view" into some subset of a string, which means they are cheap to copy around,
and it makes consuming elements from the beginning and end of the string very efficient since only
their start and end index need to be mutated to point to different parts of the string.
However, there are tradeoffs to using each type:
* `Substring`, like `String`, is a collection of `Character`s, which are extended grapheme
clusters that most closely represents a single visual character one can see on the screen. This
type is easy to use and hides a lot of the complexities of UTF8 from you (such as multiple byte
sequences that represent the same visual character), and as such it is less efficient to use.
Its elements are variable width, which means scanning its elements is an O(n) operation.
* `UnicodeScalarView` is a collection of unicode scalars represented by the `Unicode.Scalar` type.
Unicode scalars are 21-bit, and so not variable width like `Substring`, which makes scanning
`UnicodeScalarView`s more efficient, but at the cost of some additional complexity in the API.
For example, complex elements that can be represented by a single `Character`, such as "🇺🇸",
are represented by multiple `Unicode.Scalar` elements, "🇺" and "🇸". When put together they
form the single extended grapheme cluster of the flag character.
Further, some `Character`s have multiple representations as collections of unicode scalars. For
example, an "e" with an accute accent only has one visual representation, yet there are two
different sequences of unicode scalars that can represent that character:
```swift
Array("é".unicodeScalars) // [233]
Array("é".unicodeScalars) // [101, 769]
```
You can't tell from looking at the character, but the first "é" is a single unicode scalar
called a "LATIN SMALL LETTER E WITH ACUTE" and the second "é" is two scalars, one just a plain
"e" and the second a "COMBINING ACUTE ACCENT". Importantly, these two accented e's are equal as
`Character`s but unequal as `UnicodeScalarView`s:
```swift
let e1 = "\u{00E9}"
let e2 = "e\u{0301}"
e1 == e2 // true
e1.unicodeScalars.elementsEqual(e2.unicodeScalars) // false
```
So, when parsing on the level of `UnicodeScalarView` you have to be aware of these subtleties in
order to form a correct parser.
* `UTF8View` is a collection of `Unicode.UTF8.CodeUnit`s, which is just a typealias for `UInt8`,
_i.e._, a single byte. This is an even lower-level representation of strings than
`UnicodeScalarView`, and scanning these collections is quite efficient, but at the cost of even
more complexity.
For example, the non-ASCII characters described above have an even more complex representation
has UTF8 bytes:
```swift
Array("é".utf8) // [195, 169]
Array("é".utf8) // [101, 204, 129]
Array("🇺🇸".utf8) // [240, 159, 135, 186, 240, 159, 135, 184]
```
* There's even `ArraySlice<UInt8>`, which is just a raw collection of bytes. This can be even more
efficient to parse than `UTF8View` because it does not require representing a valid UTF-8
string, but then you have no guarantees that you can losslessly convert it back into a `String`.
## Mixing and matching abstraction levels
It is possible to plug together parsers that work on different abstraction levels so that you can
decide where you want to trade correctness for performance and vice-versa.
For example, suppose you have an enum representing a few cities that you want to parse a string
into:
```swift
enum City {
case losAngeles
case newYork
case sanJose
}
let city = OneOf {
"Los Angeles".map { City.losAngeles }
"New York".map { City.newYork }
"San José".map { City.sanJose }
}
```
For the most part this parser could work on the level of UTF-8 because it is mostly dealing with
plain ASCII characters for which there are not multiple ways of representing the same visual
character. The only exception is "San José", which has an accented "e" that can be represented
by two different sequences of bytes.
The `Substring` abstraction is hiding those details from us because this parser will happily parse
both representations of "San José" from a string:
```swift
city.parse("San Jos\u{00E9}") // ✅
city.parse("San Jose\u{0301}") // ✅
```
But, if we naively convert this parser to work on the level of `UTF8View`:
```swift
let city = OneOf {
"Los Angeles".utf8.map { City.losAngeles }
"New York".utf8.map { City.newYork }
"San José".utf8.map { City.sanJose }
}
```
We have accidentally introduced a bug into the parser in which it recognizes one version of
"San José", but not the other:
```swift
city.parse("San Jos\u{00E9}".utf8) // ✅
city.parse("San Jose\u{0301}".utf8) // ❌
```
One way to fix this would be to add another case to the `OneOf` for this alternate representation
of "San José":
```swift
let city = OneOf {
"Los Angeles".utf8.map { City.losAngeles }
"New York".utf8.map { City.newYork }
"San Jos\u{00E9}".utf8.map { City.sanJose }
"San Jose\u{0301}".utf8.map { City.sanJose }
}
city.parse("San Jos\u{00E9}".utf8) // ✅
city.parse("San Jose\u{0301}".utf8) // ✅
```
This does work, but you are now responsible for understanding the ins and outs of UTF-8
normalization. UTF-8 is incredibly complex and Swift does a lot of work to hide that complexity
from you.
However, there's no need to parse everything on the level of `Substring` just because this one
parser needs to. We can parse everything on the level of `UTF8View` and then parse just "San José"
on the level of `Substring`. We do this by using the ``FromSubstring`` parser, which allows us to
temporarily leave the `UTF8View` world to work in the `Substring` world:
```swift
let city = OneOf {
"Los Angeles".utf8.map { City.losAngeles }
"New York".utf8.map { City.newYork }
FromSubstring { "San José" }.map { City.sanJose }
}
city.parse("San Jos\u{00E9}".utf8) // ✅
city.parse("San Jose\u{0301}".utf8) // ✅
```
This will run the "San José" parser on the level of `Substring`, meaning it will handle all the
complexities of UTF8 normalization so that we don't have to think about it.
If we want to be _really_ pedantic we can even decide to parse only the "é" character on the
level of `Substring` and leave everything else to `UTF8View`:
```swift
let city = OneOf {
"Los Angeles".utf8.map { City.losAngeles }
"New York".utf8.map { City.newYork }
Parse {
"San Jos".utf8
FromSubstring { "é" }
}
.map { City.sanJose }
}
city.parse("San Jos\u{00E9}".utf8) // ✅
city.parse("San Jose\u{0301}".utf8) // ✅
```
We don't necessarily recommend being this pedantic in general, at least not without benchmarking to
make sure it is worth it. But it does demonstrate how you can be very precise with which abstraction
levels you want to work on.

View file

@ -0,0 +1,7 @@
# ``Parsing/OneOf``
## Topics
### Builder
- ``OneOfBuilder``

View file

@ -0,0 +1,7 @@
# ``Parsing/Parse``
## Topics
### Builder
- ``ParserBuilder``

View file

@ -0,0 +1,68 @@
# ``Parsing/Parser``
## Topics
### Diving deeper
* <doc:GettingStarted>
* <doc:Design>
* <doc:StringAbstractions>
* <doc:ErrorMessages>
* <doc:Backtracking>
### Running a parser
All of the ways to run a parser on an input.
- ``parse(_:)-717qw``
- ``parse(_:)-6h1d0``
- ``parse(_:)-2wzcq``
### Common parsers
Some of the most commonly used parsers in the library. Use these parsers with operators in order
to build complex parsers from simpler pieces.
- <doc:Int>
- <doc:String>
- <doc:Bool>
- <doc:Float>
- <doc:CharacterSet>
- <doc:UUID>
- <doc:CaseIterable>
- ``Parse``
- ``OneOf``
- ``Many``
- ``Prefix``
- ``PrefixThrough``
- ``PrefixUpTo``
- ``Optionally``
- ``Always``
- ``End``
- ``Rest``
- ``Fail``
- ``FromSubstring``
- ``FromUTF8View``
- ``FromUnicodeScalarView``
- ``First``
- ``Skip``
- ``Lazy``
- ``Newline``
- ``Whitespace``
- ``AnyParser``
- ``Peek``
- ``Not``
- ``StartsWith``
- ``Stream``
### Parser operators
- ``map(_:)``
- ``flatMap(_:)``
- ``compactMap(_:)``
- ``filter(_:)``
- ``pipe(_:)``
- ``pullback(_:)``
- ``replaceError(with:)``
- ``eraseToAnyParser()``
- ``pipe(_:)``

View file

@ -0,0 +1,106 @@
# ``Parsing``
A library for turning nebulous data into well-structured data, with a focus on composition,
performance, generality, and ergonomics.
## Additional Resources
- [GitHub Repo](https://github.com/pointfreeco/swift-parsing/)
- [Discussions](https://github.com/pointfreeco/swift-parsing/discussions)
- [Point-Free Videos](https://www.pointfree.co/collections/parsing)
## Overview
Parsing with this library is performed by listing out many small parsers that describe how to
incrementally consume small bits from the beginning of an input string. For example, suppose you
have a string that holds some user data that you want to parse into an array of `User`s:
```swift
var input = """
1,Blob,true
2,Blob Jr.,false
3,Blob Sr.,true
"""
struct User {
var id: Int
var name: String
var isAdmin: Bool
}
```
A parser can be constructed for transforming the input string into an array of users in succinct
and fluent API:
```swift
let user = Parse(User.init) {
Int.parser()
","
Prefix { $0 != "," }.map(String.init)
","
Bool.parser()
}
let users = Many {
user
} separator: {
"\n"
} terminator: {
End()
}
try users.parse(input) // [User(id: 1, name: "Blob", isAdmin: true), ...]
```
This says that to parse a user we:
* Parse and consume an integer from the beginning of the input
* then a comma
* then everything up to the next comma
* then another comma
* and finally a boolean.
And to parse an entire array of users we:
* Run the `user` parser many times
* between each invocation of `user` we run the separator parser to consume a newline
* and once the `user` and separator parsers have consumed all they can we run the terminator
parser to verify there is no more input to consume.
Further, if the input is malformed, like say we mistyped one of the booleans, then the parser emits
an error that describes exactly what went wrong:
```swift
var input = """
1,Blob,true
2,Blob Jr.,false
3,Blob Sr.,tru
"""
try users.parse(input)
// error: unexpected input
// --> input:3:11
// 3 | 3,Blob Jr,tru
// | ^ expected "true" or "false"
```
That's the basics of parsing a simple string format, but there are a lot more operators and tricks
to learn in order to performantly parse larger inputs.
## Topics
### Articles
* <doc:GettingStarted>
* <doc:Design>
* <doc:StringAbstractions>
* <doc:ErrorMessages>
* <doc:Backtracking>
## See Also
The collecton of videos from [Point-Free](https://www.pointfree.co) that dive deep into the
development of the Parsing library.
* [Point-Free Videos](https://www.pointfree.co/collections/parsing)

View file

@ -0,0 +1,34 @@
#if canImport(Darwin)
import Darwin
#endif
/// Raises a debug breakpoint if a debugger is attached.
@inline(__always)
@usableFromInline
func breakpoint(_ message: @autoclosure () -> String = "") {
#if canImport(Darwin)
// https://github.com/bitstadium/HockeySDK-iOS/blob/c6e8d1e940299bec0c0585b1f7b86baf3b17fc82/Classes/BITHockeyHelper.m#L346-L370
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
var info: kinfo_proc = kinfo_proc()
var info_size = MemoryLayout<kinfo_proc>.size
let isDebuggerAttached =
sysctl(&name, 4, &info, &info_size, nil, 0) != -1
&& info.kp_proc.p_flag & P_TRACED != 0
if isDebuggerAttached {
fputs(
"""
\(message())
Caught debug breakpoint. Type "continue" ("c") to resume execution.
""",
stderr
)
raise(SIGTRAP)
}
#else
assertionFailure(message())
#endif
}

View file

@ -0,0 +1,4 @@
// NB: Deprecated after 0.6.0
@available(*, deprecated, renamed: "Parsers.Conditional")
public typealias Conditional = Parsers.Conditional

View file

@ -0,0 +1,204 @@
/// Declares a type that can incrementally parse an `Output` value from an `Input` value.
///
/// A parser attempts to parse a nebulous piece of data, represented by the `Input` associated type,
/// into something more well-structured, represented by the `Output` associated type. The parser
/// implements the ``parse(_:)-76tcw`` method, which is handed an `inout Input`, and its job is to
/// turn this into an `Output` if possible, or throw an error if it cannot.
///
/// The argument of the ``parse(_:)-76tcw`` function is `inout` because a parser will usually
/// consume some of the input in order to produce an output. For example, we can use an
/// `Int.parser()` parser to extract an integer from the beginning of a substring and consume that
/// portion of the string:
///
/// ```swift
/// var input: Substring = "123 Hello world"
///
/// try Int.parser().parse(&input) // 123
/// input // " Hello world"
/// ```
///
/// Note that this parser works on `Substring` rather than `String` because substrings expose
/// efficient ways of removing characters from its beginning. Substrings are "views" into a string,
/// specified by start and end indices. Operations like `removeFirst`, `removeLast` and others can
/// be implemented efficiently on substrings because they simply move the start and end indices,
/// whereas their implementation on strings must make a copy of the string with the characters
/// removed.
@rethrows public protocol Parser {
/// The type of values this parser parses from.
associatedtype Input
/// The type of values parsed by this parser.
associatedtype Output
/// Attempts to parse a nebulous piece of data into something more well-structured. Typically
/// you only call this from other `Parser` conformances, not when you want to parse a concrete
/// input.
///
/// - Parameter input: A nebulous, mutable piece of data to be incrementally parsed.
/// - Returns: A more well-structured value parsed from the given input.
func parse(_ input: inout Input) throws -> Output
}
extension Parser {
/// Parse an input value into an output. This method is more ergonomic to use than
/// ``parse(_:)-76tcw`` because the input does not need to be inout.
///
/// Rather than having to create a mutable input value and feed it to the ``parse(_:)-76tcw``
/// method like this:
///
/// ```swift
/// var input = ...
/// let output = try parser.parse(&input)
/// ```
///
/// You can just feed the input directly:
///
/// ```swift
/// let output = try parser.parse(input)
/// ```
///
/// - Parameter input: A nebulous piece of data to be parsed.
/// - Returns: A more well-structured value parsed from the given input.
@inlinable
public func parse(_ input: Input) rethrows -> Output {
var input = input
return try self.parse(&input)
}
/// Parse a collection into an output using a parser that works on the collection's `SubSequence`.
/// This method is more ergnomic to use than ``parse(_:)-76tcw`` because it accepts a
/// collection directly rather than its subsequence, and the input does not need to be `inout`.
///
/// Rather than having to create a mutable subsequence value, such as a `Substring`, and feed it
/// to the ``parse(_:)-76tcw`` method like this:
///
/// ```swift
/// var input = "123,true"[...]
/// let output = try Parse {
/// Int.parser()
/// ","
/// Bool.parser()
/// }
/// .parse(&input) // (123, true)
/// ```
///
/// You can just feed a plain `String` input directly:
///
/// ```swift
/// let output = try Parse {
/// Int.parser()
/// ","
/// Bool.parser()
/// }
/// .parse("123,true") // (123, true)
/// ```
///
/// This method will fail if the parser does not consume the entirety of the input.
/// For example:
///
/// ```swift
/// let output = try Parse {
/// Int.parser()
/// ","
/// Bool.parser()
/// }
/// .parse("123,true ")
///
/// // error: unexpected input
/// // --> input:1:9
/// // 1 | 123,true
/// // | ^ expected end of input
/// ```
///
/// > Tip: If your input can have trailing whitespace that you would like to consume and discard
/// > you can do so like this:
/// > ```swift
/// > let output = try Parse {
/// > Int.parser()
/// > ",".utf8
/// > Bool.parser()
/// > Skip { Whitespace() }
/// > }
/// > .parse("123,true ") // (123, true)
/// > ```
///
/// - Parameter input: A nebulous collection of data to be parsed.
/// - Returns: A more well-structured value parsed from the given input.
@inlinable
public func parse<C: Collection>(_ input: C) rethrows -> Output
where Input == C.SubSequence {
try Parse {
self
End<Input>()
}.parse(input[...])
}
/// Parse a `String` into an output using a UTF-8 parser. This method is more ergnomic to use
/// than ``parse(_:)-76tcw`` because it accepts a plain string rather than a collection of
/// UTF-8 code units, and the input does not need to be `inout`.
///
/// Rather than having to create a mutable UTF-8 value and feed it to the ``parse(_:)-76tcw``
/// method like this:
///
/// ```swift
/// var input = "123,true"[...].utf8
/// let output = try Parse {
/// Int.parser()
/// ",".utf8
/// Bool.parser()
/// }
/// .parse(&input) // (123, true)
/// ```
///
/// You can just feed a plain `String` input directly:
///
/// ```swift
/// let output = try Parse {
/// Int.parser()
/// ",".utf8
/// Bool.parser()
/// }
/// .parse("123,true") // (123, true)
/// ```
///
/// This method will fail if the parser does not consume the entirety of the input.
/// For example:
///
/// ```swift
/// let output = try Parse {
/// Int.parser()
/// ",".utf8
/// Bool.parser()
/// }
/// .parse("123,true ")
///
/// // error: unexpected input
/// // --> input:1:9
/// // 1 | 123,true
/// // | ^ expected end of input
/// ```
///
/// > Tip: If your input can have trailing whitespace that you would like to consume and discard
/// > you can do so like this:
/// > ```swift
/// > let output = try Parse {
/// > Int.parser()
/// > ",".utf8
/// > Bool.parser()
/// > Skip { Whitespace() }
/// > }
/// > .parse("123,true ") // (123, true)
/// > ```
///
/// - Parameter input: A nebulous collection of data to be parsed.
/// - Returns: A more well-structured value parsed from the given input.
@_disfavoredOverload
@inlinable
public func parse<S: StringProtocol>(_ input: S) rethrows -> Output
where Input == S.SubSequence.UTF8View {
try Parse {
self
End<Input>()
}.parse(input[...].utf8)
}
}

View file

@ -0,0 +1,101 @@
/// A parser that always succeeds with the given value, and does not consume any input.
///
/// While not very useful on its own, the `Always` parser can be helpful when combined with other
/// parsers or operators.
///
/// When its `Output` is `Void`, it can be used as a "no-op" parser of sorts and be plugged into
/// other parser operations. For example, the ``Many`` parser can be configured with separator and
/// terminator parsers:
///
/// ```swift
/// Many {
/// Int.parser()
/// } separator: {
/// ","
/// } terminator: {
/// End()
/// }
/// ```
///
/// But also exposes initializers that omit these parsers when there is no separator or terminator
/// to be parsed:
///
/// ```swift
/// Many {
/// Prefix { $0 != "\n" }
/// "\n"
/// }
/// ```
///
/// To support this, `Many` plugs `Always<Input, Void>` into each omitted parser. As a simplified
/// example:
///
/// ```swift
/// struct Many<Element: Parser, Separator: Parser, Terminator: Parser>: Parser
/// where Separator.Input == Element.Input, Terminator.Input == Element.Input {
/// ...
/// }
///
/// extension Many where Separator == Always<Input, Void>, Terminator == Always<Input, Void> {
/// init(@ParserBuilder element: () -> Element) {
/// self.element = element()
/// self.separator = Always(())
/// self.terminator = Always(())
/// }
/// }
/// ```
///
/// This means the previous example is equivalent to:
///
/// ```swift
/// Many {
/// Prefix { $0 != "\n" }
/// "\n"
/// } separator: {
/// Always(())
/// } terminator: {
/// Always(())
/// }
/// ```
///
/// > Note: While `Always` can be used as the last alternative of a ``OneOf`` to specify a default
/// > output, the resulting parser will be throwing. Instead, prefer ``Parser/replaceError(with:)``,
/// > which returns a non-throwing parser.
public struct Always<Input, Output>: Parser {
public let output: Output
@inlinable
public init(_ output: Output) {
self.output = output
}
@inlinable
public func parse(_ input: inout Input) -> Output {
self.output
}
@inlinable
public func map<NewOutput>(
_ transform: @escaping (Output) -> NewOutput
) -> Always<Input, NewOutput> {
.init(transform(self.output))
}
}
extension Always where Input == Substring {
@inlinable
public init(_ output: Output) {
self.output = output
}
}
extension Always where Input == Substring.UTF8View {
@inlinable
public init(_ output: Output) {
self.output = output
}
}
extension Parsers {
public typealias Always = SwiftTL.Always // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,63 @@
extension Parser {
/// Wraps this parser with a type eraser.
///
/// This form of _type erasure_ preserves abstraction across API boundaries, such as different
/// modules.
///
/// When you expose your composed parsers as the ``AnyParser`` type, you can change the underlying
/// implementation over time without affecting existing clients.
///
/// Equivalent to passing `self` to `AnyParser.init(_:)`.
///
/// - Returns: An ``AnyParser`` wrapping this publisher.
@inlinable
public func eraseToAnyParser() -> AnyParser<Input, Output> {
.init(self)
}
}
/// A type-erased parser of `Output` from `Input`.
///
/// This parser forwards its ``parse(_:)`` method to an arbitrary underlying parser having the same
/// `Input` and `Output` types, hiding the specifics of the underlying ``Parser``.
///
/// Use ``AnyParser`` to wrap a parser whose type has details you don't want to expose across API
/// boundaries, such as different modules. When you use type erasure this way, you can change the
/// underlying parser over time without affecting existing clients.
public struct AnyParser<Input, Output>: Parser {
@usableFromInline
let parser: (inout Input) throws -> Output
/// Creates a type-erasing parser to wrap the given parser.
///
/// Equivalent to calling ``Parser/eraseToAnyParser()`` on the parser.
///
/// - Parameter parser: A parser to wrap with a type eraser.
@inlinable
public init<P: Parser>(_ parser: P) where P.Input == Input, P.Output == Output {
self.init(parser.parse)
}
/// Creates a parser that wraps the given closure in its ``parse(_:)`` method.
///
/// - Parameter parse: A closure that attempts to parse an output from an input. `parse` is
/// executed each time the ``parse(_:)`` method is called on the resulting parser.
@inlinable
public init(_ parse: @escaping (inout Input) throws -> Output) {
self.parser = parse
}
@inlinable
public func parse(_ input: inout Input) throws -> Output {
try self.parser(&input)
}
@inlinable
public func eraseToAnyParser() -> Self {
self
}
}
extension Parsers {
public typealias AnyParser = SwiftTL.AnyParser // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,78 @@
extension Bool {
/// A parser that consumes a Boolean value from the beginning of a collection of UTF-8 code units.
///
/// See <doc:Bool> for more information about this parser.
///
/// - Parameter inputType: The collection type of UTF-8 code units to parse.
/// - Returns: A parser that consumes a Boolean value from the beginning of a collection of UTF-8
/// code units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self
) -> Parsers.BoolParser<Input> {
.init()
}
/// A parser that consumes a Boolean value from the beginning of a substring's UTF-8 view.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is
/// `Substring.UTF8View`.
///
/// See <doc:Bool> for more information about this parser.
///
/// - Parameter inputType: The `Substring.UTF8View` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a Boolean value from the beginning of a substring's UTF-8
/// view.
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self
) -> Parsers.BoolParser<Substring.UTF8View> {
.init()
}
/// A parser that consumes a Boolean value from the beginning of a substring.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is `Substring`.
///
/// See <doc:Bool> for more information about this parser.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a Boolean value from the beginning of a substring.
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self
) -> FromUTF8View<Substring, Parsers.BoolParser<Substring.UTF8View>> {
.init { Parsers.BoolParser<Substring.UTF8View>() }
}
}
extension Parsers {
/// A parser that consumes a Boolean value from the beginning of a collection of UTF-8 code units.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// `Bool.parser()`, which constructs this type.
///
/// See <doc:Bool> for more information about this parser.
public struct BoolParser<Input: Collection>: Parser
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit
{
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws -> Bool {
if input.starts(with: [116, 114, 117, 101] /*"true".utf8*/) {
input.removeFirst(4)
return true
} else if input.starts(with: [102, 97, 108, 115, 101] /*"false".utf8*/) {
input.removeFirst(5)
return false
}
throw ParsingError.expectedInput("\"true\" or \"false\"", at: input)
}
}
}

View file

@ -0,0 +1,143 @@
extension CaseIterable where Self: RawRepresentable, RawValue == Int {
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of a substring.
///
/// See <doc:CaseIterable> for more info.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a substring.
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self
) -> Parsers.CaseIterableRawRepresentableParser<Substring, Self, String> {
.init(toPrefix: { String($0) }, areEquivalent: ==)
}
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of a substring's UTF-8 view.
///
/// See <doc:CaseIterable> for more info.
///
/// - Parameter inputType: The `Substring.UTF8View` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a substring's UTF-8 view.
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self
) -> Parsers.CaseIterableRawRepresentableParser<Substring.UTF8View, Self, String.UTF8View> {
.init(toPrefix: { String($0).utf8 }, areEquivalent: ==)
}
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of UTF-8 code units.
///
/// - Parameter inputType: The collection type of UTF-8 code units to parse.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a collection of UTF-8 code units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self
) -> Parsers.CaseIterableRawRepresentableParser<Input, Self, String.UTF8View>
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit
{
.init(toPrefix: { String($0).utf8 }, areEquivalent: ==)
}
}
extension CaseIterable where Self: RawRepresentable, RawValue == String {
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of a substring.
///
/// See <doc:CaseIterable> for more info.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a substring.
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self
) -> Parsers.CaseIterableRawRepresentableParser<Substring, Self, String> {
.init(toPrefix: { $0 }, areEquivalent: ==)
}
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of a substring's UTF-8 view.
///
/// See <doc:CaseIterable> for more info.
///
/// - Parameter inputType: The `Substring.UTF8View` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a substring's UTF-8 view.
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self
) -> Parsers.CaseIterableRawRepresentableParser<Substring.UTF8View, Self, String.UTF8View> {
.init(toPrefix: { $0.utf8 }, areEquivalent: ==)
}
/// A parser that consumes a case-iterable, raw representable value from the beginning of a
/// collection of UTF-8 code units.
///
/// - Parameter inputType: The collection type of UTF-8 code units to parse.
/// - Returns: A parser that consumes a case-iterable, raw representable value from the beginning
/// of a collection of UTF-8 code units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self
) -> Parsers.CaseIterableRawRepresentableParser<Input, Self, String.UTF8View>
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit
{
.init(toPrefix: { $0.utf8 }, areEquivalent: ==)
}
}
extension Parsers {
public struct CaseIterableRawRepresentableParser<
Input: Collection, Output: CaseIterable & RawRepresentable, Prefix: Collection
>: Parser
where
Input.SubSequence == Input,
Output.RawValue: Comparable,
Prefix.Element == Input.Element
{
@usableFromInline
let cases: [(case: Output, prefix: Prefix, count: Int)]
@usableFromInline
let areEquivalent: (Input.Element, Input.Element) -> Bool
@usableFromInline
init(
toPrefix: @escaping (Output.RawValue) -> Prefix,
areEquivalent: @escaping (Input.Element, Input.Element) -> Bool
) {
self.areEquivalent = areEquivalent
self.cases = Output.allCases
.map {
let prefix = toPrefix($0.rawValue)
return ($0, prefix, prefix.count)
}
.sorted(by: { $0.count > $1.count })
}
@inlinable
public func parse(_ input: inout Input) throws -> Output {
for (`case`, prefix, count) in self.cases {
if input.starts(with: prefix, by: self.areEquivalent) {
input.removeFirst(count)
return `case`
}
}
throw ParsingError.expectedInput("case of \"\(Output.self)\"", at: input)
}
}
}

View file

@ -0,0 +1,10 @@
import Foundation
extension CharacterSet: Parser {
@inlinable
public func parse(_ input: inout Substring) -> Substring {
let output = input.unicodeScalars.prefix(while: self.contains)
input.unicodeScalars.removeFirst(output.count)
return Substring(output)
}
}

View file

@ -0,0 +1,78 @@
extension Parser {
/// Returns a parser that outputs the non-`nil` result of calling the given closure with the
/// output of this parser.
///
/// This method is similar to `Sequence.compactMap` in the Swift standard library, as well as
/// `Publisher.compactMap` in the Combine framework.
///
/// ```swift
/// let evenParser = Int.parser().compactMap { $0.isMultiple(of: 2) }
///
/// var input = "124 hello world"[...]
/// try evenParser.parse(&input) // 124
/// input // " hello world"
/// ```
///
/// This parser fails when the provided closure returns `nil`. For example, the following parser tries
/// to convert two characters into a hex digit, but fails to do so because `"GG"` is not a valid
/// hex number:
///
/// ```swift
/// var input = "GG0000"[...]
/// let hex = try Prefix(2).compactMap { Int(String($0), radix: 16) }.parse(&input)
/// // error: failed to process "Int" from "GG"
/// // --> input:1:1-2
/// // 1 | GG0000
/// // | ^^
/// ```
///
/// - Parameter transform: A closure that accepts output of this parser as its argument and
/// returns an optional value.
/// - Returns: A parser that outputs the non-`nil` result of calling the given transformation
/// with the output of this parser.
@_disfavoredOverload
@inlinable
public func compactMap<NewOutput>(
_ transform: @escaping (Output) -> NewOutput?
) -> Parsers.CompactMap<Self, NewOutput> {
.init(upstream: self, transform: transform)
}
}
extension Parsers {
/// A parser that outputs the non-`nil` result of calling the given transformation with the output
/// of its upstream parser.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/compactMap(_:)`` operation, which constructs this type.
public struct CompactMap<Upstream: Parser, Output>: Parser {
public let upstream: Upstream
public let transform: (Upstream.Output) -> Output?
@inlinable
public init(
upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output?
) {
self.upstream = upstream
self.transform = transform
}
@inlinable
public func parse(_ input: inout Upstream.Input) throws -> Output {
let original = input
let output = try self.upstream.parse(&input)
guard let newOutput = self.transform(output)
else {
throw ParsingError.failed(
summary: """
failed to process "\(Output.self)" from \(formatValue(output))
""",
from: original,
to: input
)
}
return newOutput
}
}
}

View file

@ -0,0 +1,49 @@
extension Parsers {
/// A parser that can parse output from two types of parsers.
///
/// This parser is useful for situations where you want to run one of two different parsers based on
/// a condition, which typically would force you to perform ``Parser/eraseToAnyParser()`` and incur
/// a performance penalty.
///
/// For example, you can use this parser in a ``Parser/flatMap(_:)`` operation to use the parsed
/// output to determine what parser to run next:
///
/// ```swift
/// versionParser.flatMap { version in
/// version == "2.0"
/// ? Conditional.first(V2Parser())
/// : Conditional.second(LegacyParser())
/// }
/// ```
///
/// You won't typically construct this parser directly, but instead will use standard `if`-`else`
/// statements in a parser builder to automatically build conditional parsers:
///
/// ```swift
/// versionParser.flatMap { version in
/// if version == "2.0" {
/// V2Parser()
/// } else {
/// LegacyParser()
/// }
/// }
/// ```
public enum Conditional<First: Parser, Second: Parser>: Parser
where
First.Input == Second.Input,
First.Output == Second.Output
{
case first(First)
case second(Second)
@inlinable
public func parse(_ input: inout First.Input) rethrows -> First.Output {
switch self {
case let .first(first):
return try first.parse(&input)
case let .second(second):
return try second.parse(&input)
}
}
}
}

View file

@ -0,0 +1,54 @@
/// A parser that succeeds if the input is empty, and fails otherwise.
///
/// Useful as a final parser in a long sequence of parsers to guarantee that all input has been
/// consumed.
///
/// ```swift
/// let parser = Parse {
/// "Hello, "
/// Prefix { $0 != "!" }
/// "!"
/// End() // NB: All input should be consumed.
/// }
///
/// var input = "Hello, Blob!"[...]
/// try parser.parse(&input) // "Blob"
/// ```
///
/// This parser will fail if there are input elements that have not been consumed:
///
/// ```swift
/// input = "Hello, Blob!!"
/// try parser.parse(&input)
/// // error: unexpected input
/// // --> input:1:13
/// // 1 | Hello, Blob!!
/// // | ^ expected end of input
/// ```
public struct End<Input: Collection>: Parser {
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws {
guard input.isEmpty else {
throw ParsingError.expectedInput("end of input", at: input)
}
}
}
extension End where Input == Substring {
@_disfavoredOverload
@inlinable
public init() {}
}
/*extension End where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init() {}
}*/
extension Parsers {
public typealias End = SwiftTL.End // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,72 @@
/// A parser that always fails, no matter the input.
///
/// While not very useful on its own, this parser can be helpful when combined with other parsers or
/// operators.
///
/// For example, it can be used to conditionally causing parsing to fail when used with
/// ``Parser/flatMap(_:)``:
///
/// ```swift
/// struct OddFailure: Error {}
///
/// let evens = Int.parser().flatMap {
/// if $0.isMultiple(of: 2) {
/// Always($0)
/// } else {
/// Fail<Substring, Int>(throwing: OddFailure())
/// }
/// }
///
/// try evens.parse("42") // 42
///
/// try evens.parse("123")
/// // error: OddFailure()
/// // --> input:1:1-3
/// // 1 | 123
/// // | ^^^
/// ```
public struct Fail<Input, Output>: Parser {
@usableFromInline
let error: Error
/// Creates a parser that throws an error when it runs.
///
/// - Parameter error: An error to throw when the parser is run.
@inlinable
public init(throwing error: Error) {
self.error = error
}
@inlinable
public func parse(_ input: inout Input) throws -> Output {
switch self.error {
case is ParsingError:
throw self.error
default:
throw ParsingError.wrap(self.error, at: input)
}
}
}
extension Fail {
/// Creates a parser that throws an error when it runs.
@inlinable
public init() {
self.init(throwing: DefaultError())
}
@usableFromInline
struct DefaultError: Error, CustomDebugStringConvertible {
@usableFromInline
init() {}
@usableFromInline
var debugDescription: String {
"failed"
}
}
}
extension Parsers {
public typealias Fail = SwiftTL.Fail // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,61 @@
extension Parser {
/// Returns a parser that filters output from this parser when its output does not satisfy the
/// given predicate.
///
/// This method is similar to `Sequence.filter` in the Swift standard library, as well as
/// `Publisher.filter` in the Combine framework.
///
/// This parser fails if the predicate is not satisfied on the output of the upstream parser. For example,
/// the following parser consumes only even integers and so fails when an odd integer is used:
///
/// ```swift
/// var input = "43 Hello, world!"[...]
/// let number = try Int.parser().filter { $0.isMultiple(of: 2) }.parse(&input)
/// // error: processed value 43 failed to satisfy predicate
/// // --> input:1:1-2
/// // 1 | 43 Hello, world!
/// // | ^^ processed input
/// ```
///
/// - Parameter predicate: A closure that takes an output from this parser and returns a Boolean
/// value indicating whether the output should be returned.
/// - Returns: A parser that filters its output.
@_disfavoredOverload
@inlinable
public func filter(_ predicate: @escaping (Output) -> Bool) -> Parsers.Filter<Self> {
.init(upstream: self, predicate: predicate)
}
}
extension Parsers {
/// A parser that filters the output of an upstream parser when it does not satisfy a predicate.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/filter(_:)`` operation, which constructs this type.
public struct Filter<Upstream: Parser>: Parser {
public let upstream: Upstream
public let predicate: (Upstream.Output) -> Bool
@inlinable
public init(upstream: Upstream, predicate: @escaping (Upstream.Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
@inlinable
public func parse(_ input: inout Upstream.Input) throws -> Upstream.Output {
let original = input
let output = try self.upstream.parse(&input)
guard self.predicate(output)
else {
throw ParsingError.failed(
summary: "processed value \(formatValue(output)) failed to satisfy predicate",
label: "processed input",
from: original,
to: input
)
}
return output
}
}
}

View file

@ -0,0 +1,52 @@
/// A parser that consumes the first element from a collection.
///
/// This parser is named after `Sequence.first`, and attempts to parse the first element from a
/// collection of input by calling this property under the hood.
///
/// For example, it can parse the leading character off a substring:
///
/// ```swift
/// var input = "Hello"[...]
/// try First().parse(&input) // "H"
/// input // "ello"
/// ```
///
/// This parser fails if the input collection is empty:
///
/// ```swift
/// input = ""
/// try First().parse(&input)
/// // error: unexpected input
/// // --> input:1:1
/// // 1 |
/// // | ^ expected element
/// ```
public struct First<Input: Collection>: Parser where Input.SubSequence == Input {
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws -> Input.Element {
guard let first = input.first else {
throw ParsingError.expectedInput("element", at: input)
}
input.removeFirst()
return first
}
}
extension First where Input == Substring {
@_disfavoredOverload
@inlinable
public init() {}
}
extension First where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init() {}
}
extension Parsers {
public typealias First = SwiftTL.First // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,52 @@
extension Parser {
/// Returns a parser that transforms the output of this parser into a new parser.
///
/// This method is similar to `Sequence.flatMap`, `Optional.flatMap`, and `Result.flatMap` in the
/// Swift standard library, as well as `Publisher.flatMap` in the Combine framework.
///
/// - Parameter transform: A closure that transforms values of this parser's output and returns a
/// new parser.
/// - Returns: A parser that transforms output from an upstream parser into a new parser.
@inlinable
public func flatMap<NewParser>(
@ParserBuilder _ transform: @escaping (Output) -> NewParser
) -> Parsers.FlatMap<NewParser, Self> {
.init(upstream: self, transform: transform)
}
}
extension Parsers {
/// A parser that transforms the output of another parser into a new parser.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/flatMap(_:)`` operation, which constructs this type.
public struct FlatMap<NewParser: Parser, Upstream: Parser>: Parser
where NewParser.Input == Upstream.Input {
public let upstream: Upstream
public let transform: (Upstream.Output) -> NewParser
@inlinable
public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> NewParser) {
self.upstream = upstream
self.transform = transform
}
@inlinable
public func parse(_ input: inout Upstream.Input) rethrows -> NewParser.Output {
let original = input
do {
return try self.transform(self.upstream.parse(&input)).parse(&input)
} catch let ParsingError.failed(reason, context) {
throw ParsingError.failed(
reason,
.init(
originalInput: original,
remainingInput: input,
debugDescription: context.debugDescription,
underlyingError: ParsingError.failed(reason, context)
)
)
}
}
}
}

View file

@ -0,0 +1,172 @@
extension BinaryFloatingPoint where Self: LosslessStringConvertible {
/// A parser that consumes a floating-point number from the beginning of a collection of UTF-8
/// code units.
///
/// See <doc:Float> for more information about this parser.
///
/// - Parameter inputType: The collection type of UTF-8 code units to parse.
/// - Returns: A parser that consumes a floating-point number from the beginning of a collection
/// of UTF-8 code units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self
) -> Parsers.FloatParser<Input, Self> {
.init()
}
/// A parser that consumes a floating-point number from the beginning of a substring's UTF-8 view.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is
/// `Substring.UTF8View`.
///
/// See <doc:Float> for more information about this parser.
///
/// - Parameter inputType: The `Substring.UTF8View` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a floating-point number from the beginning of a substring's
/// UTF-8 view.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self
) -> Parsers.FloatParser<Substring.UTF8View, Self> {
.init()
}
/// A parser that consumes a floating-point number from the beginning of a substring.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is `Substring`.
///
/// See <doc:Float> for more information about this parser.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a floating-point number from the beginning of a substring.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self
) -> FromUTF8View<Substring, Parsers.FloatParser<Substring.UTF8View, Self>> {
.init { Parsers.FloatParser<Substring.UTF8View, Self>() }
}
}
extension Parsers {
/// A parser that consumes a floating-point number from the beginning of a collection of UTF-8
/// code units.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the static `parser()` method on the `BinaryFloatingPoint` of your choice, e.g.,
/// `Double.parser()`, `Float80.parser()`, etc., all of which construct this type.
///
/// See <doc:Float> for more information about this parser.
public struct FloatParser<Input: Collection, Output: BinaryFloatingPoint>: Parser
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit,
Output: LosslessStringConvertible
{
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws -> Output {
let original = input
let s = input.parseFloat()
guard let n = Output(String(decoding: s, as: UTF8.self))
else {
throw ParsingError.expectedInput("\(Output.self)".lowercased(), from: original, to: input)
}
return n
}
}
}
extension UTF8.CodeUnit {
@usableFromInline
var isDigit: Bool {
(.init(ascii: "0") ... .init(ascii: "9")).contains(self)
}
@usableFromInline
var isHexDigit: Bool {
(.init(ascii: "0") ... .init(ascii: "9")).contains(self)
|| (.init(ascii: "a") ... .init(ascii: "f")).contains(self)
|| (.init(ascii: "A") ... .init(ascii: "F")).contains(self)
}
@usableFromInline
var isSign: Bool {
self == .init(ascii: "-") || self == .init(ascii: "+")
}
}
extension Collection where SubSequence == Self, Element == UTF8.CodeUnit {
@inlinable
@inline(__always)
mutating func parseFloat() -> SubSequence {
let original = self
if self.first?.isSign == true {
self.removeFirst()
}
if self.first == .init(ascii: "0")
&& (self.dropFirst().first == .init(ascii: "x")
|| self.dropFirst().first == .init(ascii: "X"))
{
self.removeFirst(2)
let integer = self.prefix(while: { $0.isHexDigit })
self.removeFirst(integer.count)
if self.first == .init(ascii: ".") {
let fractional =
self
.dropFirst()
.prefix(while: { $0.isHexDigit })
self.removeFirst(1 + fractional.count)
}
if self.first == .init(ascii: "p") || self.first == .init(ascii: "P") {
var n = 1
if self.dropFirst().first?.isSign == true { n += 1 }
let exponent =
self
.dropFirst(n)
.prefix(while: { $0.isHexDigit })
guard !exponent.isEmpty else { return original[..<self.startIndex] }
self.removeFirst(n + exponent.count)
}
} else if self.first?.isDigit == true || self.first == .init(ascii: ".") {
let integer = self.prefix(while: { $0.isDigit })
self.removeFirst(integer.count)
if self.first == .init(ascii: ".") {
let fractional =
self
.dropFirst()
.prefix(while: { $0.isDigit })
self.removeFirst(1 + fractional.count)
}
if self.first == .init(ascii: "e") || self.first == .init(ascii: "E") {
var n = 1
if self.dropFirst().first?.isSign == true { n += 1 }
let exponent =
self
.dropFirst(n)
.prefix(while: { $0.isDigit })
guard !exponent.isEmpty else { return original[..<self.startIndex] }
self.removeFirst(n + exponent.count)
}
} else if self.prefix(8).caseInsensitiveElementsEqualLowercase("infinity".utf8) {
self.removeFirst(8)
} else if self.prefix(3).caseInsensitiveElementsEqualLowercase("inf".utf8)
|| self.prefix(3).caseInsensitiveElementsEqualLowercase("nan".utf8)
{
self.removeFirst(3)
}
return original[..<self.startIndex]
}
@inlinable
@inline(__always)
func caseInsensitiveElementsEqualLowercase<S: Sequence>(_ other: S) -> Bool
where S.Element == Element {
self.elementsEqual(other, by: { $0 == $1 || $0 + 32 == $1 })
}
}

View file

@ -0,0 +1,72 @@
/// A parser that transforms a parser on `Substring` into a parser on another view.
///
/// The `FromSubstring` operator allows you to mix and match representation levels of strings
/// so that you can maximize how much you parse on the faster, but more complex, lower level
/// representations and then switch to slower, but safer, higher level representations for
/// when you need that power.
///
/// For example, to parse "café" as a collection of UTF8 code units you must be careful to parse
/// both representations of "é":
///
/// ```swift
/// OneOf {
/// "caf\u{00E9}".utf8 // LATIN SMALL LETTER E WITH ACUTE
/// "cafe\u{0301}".utf8 // E + COMBINING ACUTE ACCENT
/// }
/// ```
///
/// Alternatively, you can parse the ASCII characters of "caf" as UTF8 code units, and then
/// switch to the higher level substring representation to parse "é" so that you don't have
/// to worry about UTF8 normalization:
///
/// ```swift
/// Parse {
/// "caf".utf8
///
/// // Parse any recognized "é" character, including:
/// // - LATIN SMALL LETTER E WITH ACUTE ("\u{00E9}")
/// // - E + COMBINING ACUTE ACCENT ("e\u{0301}")
/// FromSubstring { "é" }
/// }
/// ```
public struct FromSubstring<Input, SubstringParser: Parser>: Parser
where SubstringParser.Input == Substring {
public let substringParser: SubstringParser
public let toSubstring: (Input) -> Substring
public let fromSubstring: (Substring) -> Input
@inlinable
public func parse(_ input: inout Input) rethrows -> SubstringParser.Output {
var substring = self.toSubstring(input)
defer { input = self.fromSubstring(substring) }
return try self.substringParser.parse(&substring)
}
}
extension FromSubstring where Input == ArraySlice<UInt8> {
@inlinable
public init(@ParserBuilder _ build: () -> SubstringParser) {
self.substringParser = build()
self.toSubstring = { Substring(decoding: $0, as: UTF8.self) }
self.fromSubstring = { ArraySlice($0.utf8) }
}
}
extension FromSubstring where Input == Substring.UnicodeScalarView {
@_disfavoredOverload
@inlinable
public init(@ParserBuilder _ build: () -> SubstringParser) {
self.substringParser = build()
self.toSubstring = Substring.init
self.fromSubstring = \.unicodeScalars
}
}
extension FromSubstring where Input == Substring.UTF8View {
@inlinable
public init(@ParserBuilder _ build: () -> SubstringParser) {
self.substringParser = build()
self.toSubstring = Substring.init
self.fromSubstring = \.utf8
}
}

View file

@ -0,0 +1,32 @@
public struct FromUTF8View<Input, UTF8Parser: Parser>: Parser
where UTF8Parser.Input == Substring.UTF8View {
public let utf8Parser: UTF8Parser
public let toUTF8: (Input) -> Substring.UTF8View
public let fromUTF8: (Substring.UTF8View) -> Input
@inlinable
public func parse(_ input: inout Input) rethrows -> UTF8Parser.Output {
var utf8 = self.toUTF8(input)
defer { input = self.fromUTF8(utf8) }
return try self.utf8Parser.parse(&utf8)
}
}
extension FromUTF8View where Input == Substring {
@inlinable
public init(@ParserBuilder _ build: () -> UTF8Parser) {
self.utf8Parser = build()
self.toUTF8 = \.utf8
self.fromUTF8 = Substring.init
}
}
extension FromUTF8View where Input == Substring.UnicodeScalarView {
@_disfavoredOverload
@inlinable
public init(@ParserBuilder _ build: () -> UTF8Parser) {
self.utf8Parser = build()
self.toUTF8 = { Substring($0).utf8 }
self.fromUTF8 = { Substring($0).unicodeScalars }
}
}

View file

@ -0,0 +1,42 @@
/// A parser that transforms a parser on `Substring.UnicodeScalarView` into a parser on another
/// view.
public struct FromUnicodeScalarView<Input, UnicodeScalarsParser: Parser>: Parser
where UnicodeScalarsParser.Input == Substring.UnicodeScalarView {
public let unicodeScalarsParser: UnicodeScalarsParser
public let toUnicodeScalars: (Input) -> Substring.UnicodeScalarView
public let fromUnicodeScalars: (Substring.UnicodeScalarView) -> Input
@inlinable
public func parse(_ input: inout Input) rethrows -> UnicodeScalarsParser.Output {
var unicodeScalars = self.toUnicodeScalars(input)
defer { input = self.fromUnicodeScalars(unicodeScalars) }
return try self.unicodeScalarsParser.parse(&unicodeScalars)
}
}
extension FromUnicodeScalarView where Input == ArraySlice<UInt8> {
@inlinable
public init(@ParserBuilder _ build: () -> UnicodeScalarsParser) {
self.unicodeScalarsParser = build()
self.toUnicodeScalars = { Substring(decoding: $0, as: UTF8.self).unicodeScalars }
self.fromUnicodeScalars = { ArraySlice(Substring($0).utf8) }
}
}
extension FromUnicodeScalarView where Input == Substring {
@inlinable
public init(@ParserBuilder _ build: () -> UnicodeScalarsParser) {
self.unicodeScalarsParser = build()
self.toUnicodeScalars = \.unicodeScalars
self.fromUnicodeScalars = Substring.init
}
}
extension FromUnicodeScalarView where Input == Substring.UTF8View {
@inlinable
public init(@ParserBuilder _ build: () -> UnicodeScalarsParser) {
self.unicodeScalarsParser = build()
self.toUnicodeScalars = { Substring($0).unicodeScalars }
self.fromUnicodeScalars = { Substring($0).utf8 }
}
}

View file

@ -0,0 +1,163 @@
extension FixedWidthInteger {
/// A parser that consumes an integer (with an optional leading `+` or `-` sign for signed integer
/// types) from the beginning of a collection of UTF-8 code units.
///
/// See <doc:Int> for more information about this parser.
///
/// - Parameters:
/// - inputType: The collection type of UTF-8 code units to parse.
/// - radix: The radix, or base, to use for converting text to an integer value. `radix` must be
/// in the range `2...36`.
/// - Returns: A parser that consumes an integer from the beginning of a collection of UTF-8 code
/// units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self,
radix: Int = 10
) -> Parsers.IntParser<Input, Self> {
.init(radix: radix)
}
/// A parser that consumes an integer (with an optional leading `+` or `-` sign for signed integer
/// types) from the beginning of a collection of UTF-8 code units.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is
/// `Substring.UTF8View`.
///
/// See <doc:Int> for more information about this parser.
///
/// - Parameters:
/// - inputType: The `Substring.UTF8View` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - radix: The radix, or base, to use for converting text to an integer value. `radix` must be
/// in the range `2...36`.
/// - Returns: A parser that consumes an integer from the beginning of a substring's UTF-8 view.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self,
radix: Int = 10
) -> Parsers.IntParser<Substring.UTF8View, Self> {
.init(radix: radix)
}
/// A parser that consumes an integer (with an optional leading `+` or `-` sign for signed integer
/// types) from the beginning of a collection of UTF-8 code units.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is `Substring`.
///
/// See <doc:Int> for more information about this parser.
///
/// - Parameters:
/// - inputType: The `Substring` type. This parameter is included to mirror the interface that
/// parses any collection of UTF-8 code units.
/// - radix: The radix, or base, to use for converting text to an integer value. `radix` must be
/// in the range `2...36`.
/// - Returns: A parser that consumes an integer from the beginning of a substring.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self,
radix: Int = 10
) -> FromUTF8View<Substring, Parsers.IntParser<Substring.UTF8View, Self>> {
.init { Parsers.IntParser<Substring.UTF8View, Self>(radix: radix) }
}
}
extension Parsers {
/// A parser that consumes an integer (with an optional leading `+` or `-` sign for signed integer
/// types) from the beginning of a collection of UTF8 code units.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the static `parser()` method on the `FixedWidthInteger` of your choice, e.g. `Int.parser()`,
/// `UInt8.parser()`, etc., all of which construct this type.
///
/// See <doc:Int> for more information about this parser.
public struct IntParser<Input: Collection, Output: FixedWidthInteger>: Parser
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit
{
/// The radix, or base, to use for converting text to an integer value.
public let radix: Int
@inlinable
public init(radix: Int = 10) {
precondition((2...36).contains(radix), "Radix not in range 2...36")
self.radix = radix
}
@inlinable
public func parse(_ input: inout Input) throws -> Output {
@inline(__always)
func digit(for n: UTF8.CodeUnit) -> Output? {
let output: Output
switch n {
case .init(ascii: "0") ... .init(ascii: "9"):
output = Output(n - .init(ascii: "0"))
case .init(ascii: "A") ... .init(ascii: "Z"):
output = Output(n - .init(ascii: "A") + 10)
case .init(ascii: "a") ... .init(ascii: "z"):
output = Output(n - .init(ascii: "a") + 10)
default:
return nil
}
return output < self.radix ? output : nil
}
var length = 0
var iterator = input.makeIterator()
guard let first = iterator.next() else {
throw ParsingError.expectedInput("integer", at: input)
}
let isPositive: Bool
let parsedSign: Bool
var overflow = false
var output: Output
switch (Output.isSigned, first) {
case (true, .init(ascii: "-")):
parsedSign = true
isPositive = false
output = 0
case (true, .init(ascii: "+")):
parsedSign = true
isPositive = true
output = 0
case let (_, n):
guard let n = digit(for: n) else {
throw ParsingError.expectedInput("integer", at: input)
}
parsedSign = false
isPositive = true
output = n
}
let original = input
input.removeFirst()
length += 1
let radix = Output(self.radix)
while let next = iterator.next(), let n = digit(for: next) {
input.removeFirst()
(output, overflow) = output.multipliedReportingOverflow(by: radix)
func overflowError() -> Error {
ParsingError.failed(
summary: "failed to process \"\(Output.self)\"",
label: "overflowed \(Output.max)",
from: original,
to: input
)
}
guard !overflow else { throw overflowError() }
(output, overflow) =
isPositive
? output.addingReportingOverflow(n)
: output.subtractingReportingOverflow(n)
guard !overflow else { throw overflowError() }
length += 1
}
guard length > (parsedSign ? 1 : 0)
else {
throw ParsingError.expectedInput("integer", at: input)
}
return output
}
}
}

View file

@ -0,0 +1,27 @@
/// A parser that waits for a call to its ``parse(_:)`` method before running the given closure to
/// create a parser for the given input.
public final class Lazy<LazyParser: Parser>: Parser {
@usableFromInline
internal var lazyParser: LazyParser?
public let createParser: () -> LazyParser
@inlinable
public init(@ParserBuilder createParser: @escaping () -> LazyParser) {
self.createParser = createParser
}
@inlinable
public func parse(_ input: inout LazyParser.Input) rethrows -> LazyParser.Output {
guard let parser = self.lazyParser else {
let parser = self.createParser()
self.lazyParser = parser
return try parser.parse(&input)
}
return try parser.parse(&input)
}
}
extension Parsers {
public typealias Lazy = SwiftTL.Lazy // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,39 @@
extension Array: Parser where Element: Equatable {
@inlinable
public func parse(_ input: inout ArraySlice<Element>) throws {
guard input.starts(with: self) else {
throw ParsingError.expectedInput(self.debugDescription, at: input)
}
input.removeFirst(self.count)
}
}
extension String: Parser {
@inlinable
public func parse(_ input: inout Substring) throws {
guard input.starts(with: self) else {
throw ParsingError.expectedInput(self.debugDescription, at: input)
}
input.removeFirst(self.count)
}
}
extension String.UnicodeScalarView: Parser {
@inlinable
public func parse(_ input: inout Substring.UnicodeScalarView) throws {
guard input.starts(with: self) else {
throw ParsingError.expectedInput(String(self).debugDescription, at: input)
}
input.removeFirst(self.count)
}
}
extension String.UTF8View: Parser {
@inlinable
public func parse(_ input: inout Substring.UTF8View) throws {
guard input.starts(with: self) else {
throw ParsingError.expectedInput(String(self).debugDescription, at: input)
}
input.removeFirst(self.count)
}
}

View file

@ -0,0 +1,353 @@
import Foundation
/// A parser that attempts to run another parser as many times as specified, accumulating the result
/// of the outputs.
///
/// For example, given a comma-separated string of numbers, one could parse out an array of
/// integers:
///
/// ```swift
/// let intsParser = Many {
/// Int.parser()
/// } separator: {
/// ","
/// }
///
/// var input = "1,2,3"[...]
/// try intsParser.parse(&input) // [1, 2, 3]
/// input // ""
/// ```
///
/// In addition to an element and separator parser, a "terminator" parser that is run after the element
/// parser has run as many times as possible. This can be useful for proving that the `Many` parser has
/// consumed everything you expect:
///
/// ```swift
/// let intsParser = Many {
/// Int.parser()
/// } separator: {
/// ","
/// } terminator: {
/// "---"
/// }
///
/// var input = "1,2,3---"[...]
/// try intsParser.parse(&input) // [1, 2, 3]
/// input // ""
/// ```
///
/// The outputs of the element parser do not need to be accumulated in an array. More generally one can
/// specify a closure that customizes how outputs are accumulated, much like `Sequence.reduce(into:_)`. We
/// could, for example, sum the numbers as we parse them instead of accumulating each value in an array:
///
/// ```swift
/// let sumParser = Many(into: 0, +=) {
/// Int.parser()
/// } separator: {
/// ","
/// }
///
/// var input = "1,2,3"[...]
/// try sumParser.parse(&input) // 6
/// input // ""
/// ```
///
/// This parser fails if the terminator parser fails. For example, if we required our comma-separated
/// integer parser to be terminated by `"---"`, but we parsed a list that contained a non-integer we would
/// get an error:
///
/// ```swift
/// let intsParser = Many {
/// Int.parser()
/// } separator: {
/// ","
/// } terminator: {
/// "---"
/// }
/// var input = "1,2,Hello---"[...]
/// try intsParser.parse(&input)
/// // error: unexpected input
/// // --> input:1:5
/// // 1 | 1,2,Hello---
/// // | ^ expected integer
/// ```
public struct Many<Element: Parser, Result, Separator: Parser, Terminator: Parser>: Parser
where
Separator.Input == Element.Input,
Terminator.Input == Element.Input
{
public let element: Element
public let initialResult: Result
public let maximum: Int
public let minimum: Int
public let separator: Separator
public let terminator: Terminator
public let updateAccumulatingResult: (inout Result, Element.Output) throws -> Void
/// Initializes a parser that attempts to run the given parser at least and at most the given
/// number of times, accumulating the outputs into a result with a given closure.
///
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// - minimum: The minimum number of times to run this parser and consider parsing to be
/// successful.
/// - maximum: The maximum number of times to run this parser before returning the output.
/// - updateAccumulatingResult: A closure that updates the accumulating result with each output
/// of the element parser.
/// - element: A parser to run multiple times to accumulate into a result.
/// - separator: A parser that consumes input between each parsed output.
@inlinable
public init(
into initialResult: Result,
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
_ updateAccumulatingResult: @escaping (inout Result, Element.Output) throws -> Void,
@ParserBuilder element: () -> Element,
@ParserBuilder separator: () -> Separator,
@ParserBuilder terminator: () -> Terminator
) {
self.element = element()
self.initialResult = initialResult
self.maximum = maximum
self.minimum = minimum
self.separator = separator()
self.terminator = terminator()
self.updateAccumulatingResult = updateAccumulatingResult
}
@inlinable
public func parse(_ input: inout Element.Input) throws -> Result {
var rest = input
var previous = input
var result = self.initialResult
var count = 0
var loopError: Error?
while count < self.maximum {
let output: Element.Output
do {
output = try self.element.parse(&input)
} catch {
loopError = error
break
}
defer { previous = input }
count += 1
do {
try self.updateAccumulatingResult(&result, output)
} catch {
throw ParsingError.failed(
"",
.init(
originalInput: previous, remainingInput: input, debugDescription: "\(error)",
underlyingError: error)
)
}
rest = input
do {
_ = try self.separator.parse(&input)
} catch {
loopError = error
break
}
if memcmp(&input, &previous, MemoryLayout<Element.Input>.size) == 0 {
throw ParsingError.failed(
"expected input to be consumed",
.init(remainingInput: input, debugDescription: "infinite loop", underlyingError: nil)
)
}
}
input = rest
do {
_ = try self.terminator.parse(&input)
} catch {
if let loopError = loopError {
throw ParsingError.manyFailed([loopError, error], at: input)
} else {
throw error
}
}
guard count >= self.minimum else {
let atLeast = self.minimum - count
throw ParsingError.expectedInput(
"""
\(atLeast) \(count == 0 ? "" : "more ")value\(atLeast == 1 ? "" : "s") of \
"\(Element.Output.self)"
""",
at: rest
)
}
return result
}
}
extension Many where Separator == Always<Input, Void>, Terminator == Always<Input, Void> {
/// Initializes a parser that attempts to run the given parser at least and at most the given
/// number of times, accumulating the outputs into a result with a given closure.
///
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// - minimum: The minimum number of times to run this parser and consider parsing to be
/// successful.
/// - maximum: The maximum number of times to run this parser before returning the output.
/// - updateAccumulatingResult: A closure that updates the accumulating result with each output
/// of the element parser.
/// - element: A parser to run multiple times to accumulate into a result.
@inlinable
public init(
into initialResult: Result,
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
_ updateAccumulatingResult: @escaping (inout Result, Element.Output) throws -> Void,
@ParserBuilder element: () -> Element
) {
self.element = element()
self.initialResult = initialResult
self.maximum = maximum
self.minimum = minimum
self.separator = .init(())
self.terminator = .init(())
self.updateAccumulatingResult = updateAccumulatingResult
}
}
extension Many where Separator == Always<Input, Void> {
@inlinable
public init(
into initialResult: Result,
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
_ updateAccumulatingResult: @escaping (inout Result, Element.Output) throws -> Void,
@ParserBuilder element: () -> Element,
@ParserBuilder terminator: () -> Terminator
) {
self.element = element()
self.initialResult = initialResult
self.maximum = maximum
self.minimum = minimum
self.separator = .init(())
self.terminator = terminator()
self.updateAccumulatingResult = updateAccumulatingResult
}
}
extension Many where Terminator == Always<Input, Void> {
@inlinable
public init(
into initialResult: Result,
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
_ updateAccumulatingResult: @escaping (inout Result, Element.Output) throws -> Void,
@ParserBuilder element: () -> Element,
@ParserBuilder separator: () -> Separator
) {
self.element = element()
self.initialResult = initialResult
self.maximum = maximum
self.minimum = minimum
self.separator = separator()
self.terminator = .init(())
self.updateAccumulatingResult = updateAccumulatingResult
}
}
extension Many where Result == [Element.Output] {
/// Initializes a parser that attempts to run the given parser at least and at most the given
/// number of times, accumulating the outputs in an array.
///
/// - Parameters:
/// - minimum: The minimum number of times to run this parser and consider parsing to be
/// successful.
/// - maximum: The maximum number of times to run this parser before returning the output.
/// - element: A parser to run multiple times to accumulate into an array.
/// - separator: A parser that consumes input between each parsed output.
@inlinable
public init(
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
@ParserBuilder element: () -> Element,
@ParserBuilder separator: () -> Separator,
@ParserBuilder terminator: () -> Terminator
) {
self.init(
into: [],
atLeast: minimum,
atMost: maximum,
{ $0.append($1) },
element: element,
separator: separator,
terminator: terminator
)
}
}
extension Many
where
Result == [Element.Output],
Separator == Always<Input, Void>,
Terminator == Always<Input, Void>
{
/// Initializes a parser that attempts to run the given parser at least and at most the given
/// number of times, accumulating the outputs in an array.
///
/// - Parameters:
/// - minimum: The minimum number of times to run this parser and consider parsing to be
/// successful.
/// - maximum: The maximum number of times to run this parser before returning the output.
/// - element: A parser to run multiple times to accumulate into an array.
@inlinable
public init(
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
@ParserBuilder element: () -> Element
) {
self.init(
into: [],
atLeast: minimum,
atMost: maximum,
{ $0.append($1) },
element: element
)
}
}
extension Many where Result == [Element.Output], Separator == Always<Input, Void> {
@inlinable
public init(
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
@ParserBuilder element: () -> Element,
@ParserBuilder terminator: () -> Terminator
) {
self.init(
into: [],
atLeast: minimum,
atMost: maximum,
{ $0.append($1) },
element: element,
terminator: terminator
)
}
}
extension Many where Result == [Element.Output], Terminator == Always<Input, Void> {
@inlinable
public init(
atLeast minimum: Int = 0,
atMost maximum: Int = .max,
@ParserBuilder element: () -> Element,
@ParserBuilder separator: () -> Separator
) {
self.init(
into: [],
atLeast: minimum,
atMost: maximum,
{ $0.append($1) },
element: element,
separator: separator
)
}
}
extension Parsers {
public typealias Many = SwiftTL.Many // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,42 @@
extension Parser {
/// Returns a parser that transforms the output of this parser with a given closure.
///
/// This method is similar to `Sequence.map`, `Optional.map`, and `Result.map` in the Swift
/// standard library, as well as `Publisher.map` in the Combine framework.
///
/// - Parameter transform: A closure that transforms values of this parser's output.
/// - Returns: A parser of transformed outputs.
@_disfavoredOverload
@inlinable
public func map<NewOutput>(
_ transform: @escaping (Output) -> NewOutput
) -> Parsers.Map<Self, NewOutput> {
.init(upstream: self, transform: transform)
}
}
extension Parsers {
/// A parser that transforms the output of another parser with a given closure.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/map(_:)`` operation, which constructs this type.
public struct Map<Upstream: Parser, NewOutput>: Parser {
/// The parser from which this parser receives output.
public let upstream: Upstream
/// The closure that transforms output from the upstream parser.
public let transform: (Upstream.Output) -> NewOutput
@inlinable
public init(upstream: Upstream, transform: @escaping (Upstream.Output) -> NewOutput) {
self.upstream = upstream
self.transform = transform
}
@inlinable
@inline(__always)
public func parse(_ input: inout Upstream.Input) rethrows -> NewOutput {
self.transform(try self.upstream.parse(&input))
}
}
}

View file

@ -0,0 +1,70 @@
/// A parser that consumes a single newline from the beginning of the input.
///
/// - Note: This parser only consumes a line feed (`"\n"`) or a carriage returns with line feed
/// (`"\r\n"`). If you need richer support that covers all unicode newline characters, use a
/// ``Prefix`` parser that operates on the `Substring` level with a predicate that consumes a
/// single newline:
///
/// ```swift
/// Prefix(1) { $0.isNewline }
/// ```
/// It will consume both line feeds (`"\n"`) and carriage returns with line feeds (`"\r\n"`).
public struct Newline<Input: Collection, Bytes: Collection>: Parser
where
Input.SubSequence == Input,
Bytes.SubSequence == Bytes,
Bytes.Element == UTF8.CodeUnit
{
@usableFromInline
let toBytes: (Input) -> Bytes
@usableFromInline
let fromBytes: (Bytes) -> Input
@inlinable
public func parse(_ input: inout Input) throws {
var bytes = self.toBytes(input)
if bytes.first == .init(ascii: "\n") {
bytes.removeFirst()
} else if bytes.first == .init(ascii: "\r"), bytes.dropFirst().first == .init(ascii: "\n") {
bytes.removeFirst(2)
} else {
throw ParsingError.expectedInput(#""\n" or "\r\n""#, at: input)
}
input = self.fromBytes(bytes)
}
}
// NB: Swift 5.5.2 on Linux and Windows fails to build with a simpler `Bytes == Input` constraint
extension Newline where Bytes == Input.SubSequence, Bytes.SubSequence == Input {
@inlinable
public init() {
self.toBytes = { $0 }
self.fromBytes = { $0 }
}
}
extension Newline where Input == Substring, Bytes == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init() {
self.toBytes = { $0.utf8 }
self.fromBytes = Substring.init
}
}
extension Newline where Input == Substring.UTF8View, Bytes == Input {
@_disfavoredOverload
@inlinable
public init() { self.init() }
}
extension Newline where Input == ArraySlice<UTF8.CodeUnit>, Bytes == Input {
@_disfavoredOverload
@inlinable
public init() { self.init() }
}
extension Parsers {
public typealias Newline = SwiftTL.Newline // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,42 @@
/// A parser that succeeds if the given parser fails, and does not consume any input.
///
/// For example, to parse a line from an input that does not start with "//" one can do:
///
/// ```swift
/// let uncommentedLine = Parse {
/// Not { "//" }
/// PrefixUpTo("\n")
/// }
///
/// try uncommentedLine.parse("let x = 1") // "let x = 1"
///
/// try uncommentedLine.parse("// let x = 1") //
/// // error: unexpected input
/// // --> input:1:1-2
/// // 1 | // let x = 1
/// // | ^^ expected not to be processed
/// ```
public struct Not<Upstream: Parser>: Parser {
public let upstream: Upstream
/// Creates a parser that succeeds if the given parser fails, and does not consume any input.
///
/// - Parameter build: A parser that causes this parser to fail if it succeeds, or succeed if it
/// fails.
@inlinable
public init(@ParserBuilder _ build: () -> Upstream) {
self.upstream = build()
}
@inlinable
public func parse(_ input: inout Upstream.Input) throws {
let original = input
do {
_ = try self.upstream.parse(&input)
} catch {
input = original
return
}
throw ParsingError.expectedInput("not to be processed", from: original, to: input)
}
}

View file

@ -0,0 +1,166 @@
/// A parser that attempts to run a number of parsers till one succeeds.
///
/// Use this parser to list out a number of parsers in a ``OneOfBuilder`` result builder block.
///
/// The following example uses ``OneOf`` to parse an enum value. To do so, it spells out a list of
/// parsers to `OneOf`, one for each case:
///
/// ```swift
/// enum Currency { case eur, gbp, usd }
///
/// let currency = OneOf {
/// "".map { Currency.eur }
/// "£".map { Currency.gbp }
/// "$".map { Currency.usd }
/// }
/// ```
///
/// This parser fails if every parser inside fails:
///
/// ```swift
/// var input = "London, Hello!"[...]
/// try OneOf { "New York"; "Berlin" }.parse(&input)
///
/// // error: multiple failures occurred
/// //
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | London, Hello!
/// // | ^ expected "New York"
/// // | ^ expected "Berlin"
/// ```
///
/// If you are parsing input that should coalesce into some default, avoid using a final ``Always``
/// parser, and instead opt for a trailing ``replaceError(with:)``, which returns a parser that
/// cannot fail:
///
/// ```swift
/// enum Currency { case eur, gbp, usd, unknown }
///
/// let currency = OneOf {
/// "".map { Currency.eur }
/// "£".map { Currency.gbp }
/// "$".map { Currency.usd }
/// }
/// .replaceError(with: Currency.unknown)
///
/// currency.parse("$") // Currency.usd
/// currency.parse("฿") // Currency.unknown
/// ```
///
/// ## Specificity
///
/// The order of the parsers in the above ``OneOf`` does not matter because each of "", "£" and "$"
/// are mutually exclusive, i.e. at most one will succeed on any given input.
///
/// However, that is not always true, and when the parsers are not mutually exclusive (i.e. multiple
/// can succeed on a given input) you must order them from most specific to least specific. That is,
/// the first parser should succeed on the fewest number of inputs and the last parser should
/// succeed on the most number of inputs.
///
/// For example, suppose you wanted to parse a simple CSV format into a doubly-nested array of
/// strings, and the fields in the CSV are allowed to contain commas themselves as long as they
/// are quoted:
///
/// ```swift
/// let input = #"""
/// lastName,firstName
/// McBlob,Blob
/// "McBlob, Esq.",Blob Jr.
/// "McBlob, MD",Blob Sr.
/// """#
/// ```
///
/// Here we have a list of last and first names separated by a comma, and some of the last names are
/// quoted because they contain commas.
///
/// In order to safely parse this we must first try parsing a field as a quoted field, and then only
/// if that fails we can parse a plain field that takes everything up until the next comma or
/// newline:
///
/// ```swift
/// let quotedField = Parse {
/// "\""
/// Prefix { $0 != "\"" }
/// "\""
/// }
/// let plainField = Prefix { $0 != "," && $0 != "\n" }
///
/// let field = OneOf {
/// quotedField
/// plainField
/// }
/// ```
///
/// Then we can parse many fields to form an array of fields making up a line, and then parse many
/// lines to make up a full, doubly-nested array for the CSV:
///
/// ```swift
/// let line = Many { field } separator: { "," }
/// let csv = Many { line } separator: { "\n" }
/// ```
///
/// Running this parser on the input shows that it properly isolates each field of the CSV, even
/// fields that are quoted and contain a comma:
///
/// ```swift
/// XCTAssertEqual(
/// try csv.parse(input),
/// [
/// ["lastName", "firstName"],
/// ["McBlob", "Blob"],
/// ["McBlob, Esq.", "Blob Jr."],
/// ["McBlob, MD", "Blob Sr."],
/// ]
/// )
/// //
/// ```
///
/// The reason this parser works is because the `quotedField` and `plainField` parsers are listed in
/// a very specific order inside the `OneOf`:
///
/// ```swift
/// let field = OneOf {
/// quotedField
/// plainField
/// }
/// ```
///
/// The `quotedField` parser is a _more_ specific parser in that it will succeed on fewer inputs
/// than the `plainField` parser does. For example:
///
/// ```swift
/// try quotedField.parse("Blob Jr.") //
/// try plainField.parse("Blob Jr.") //
/// ```
///
/// Whereas the `plainField` parser will happily succeed on anything the `quotedField` parser will
/// succeed on:
///
/// ```swift
/// try quotedField.parse("\"Blob, Esq\"") //
/// try plainField.parse("\"Blob, Esq\"") //
/// ```
///
/// For this reason the `quotedField` parser must be listed first so that it can try its logic
/// first, which succeeds less frequently, before then trying the `plainField` parser, which
/// succeeds more often.
///
/// ## Backtracking
///
/// The ``OneOf`` parser is the primary tool for introducing backtracking into your parsers,
/// which means to undo the consumption of a parser when it fails. For more information, see the
/// article <doc:Backtracking>.
public struct OneOf<Parsers>: Parser where Parsers: Parser {
public let parsers: Parsers
@inlinable
public init(@OneOfBuilder _ build: () -> Parsers) {
self.parsers = build()
}
@inlinable
public func parse(_ input: inout Parsers.Input) rethrows -> Parsers.Output {
try self.parsers.parse(&input)
}
}

View file

@ -0,0 +1,47 @@
extension Parsers {
/// A parser that attempts to run a number of parsers till one succeeds.
///
/// You will not typically need to interact with this type directly. Instead you will usually loop
/// over each parser in a builder block:
///
/// ```swift
/// enum Role: String, CaseIterable {
/// case admin
/// case guest
/// case member
/// }
///
/// let roleParser = OneOf {
/// for role in Role.allCases {
/// status.rawValue.map { role }
/// }
/// }
/// ```
public struct OneOfMany<Parsers: Parser>: Parser {
public let parsers: [Parsers]
@inlinable
public init(_ parsers: [Parsers]) {
self.parsers = parsers
}
@inlinable
@inline(__always)
public func parse(_ input: inout Parsers.Input) throws -> Parsers.Output {
let original = input
var count = self.parsers.count
var errors: [Error] = []
errors.reserveCapacity(count)
for parser in self.parsers {
do {
return try parser.parse(&input)
} catch {
count -= 1
if count > 0 { input = original }
errors.append(error)
}
}
throw ParsingError.manyFailed(errors, at: original)
}
}
}

View file

@ -0,0 +1,40 @@
extension Optional: Parser where Wrapped: Parser {
public func parse(_ input: inout Wrapped.Input) rethrows -> Wrapped.Output? {
guard let self = self
else { return nil }
return try self.parse(&input)
}
}
extension Parsers {
/// A parser that attempts to run a given void parser, succeeding with void.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// `if` statements in parser builder blocks:
///
/// ```swift
/// Parse {
/// "Hello"
/// if useComma {
/// ","
/// }
/// " "
/// Rest()
/// }
/// ```
public struct OptionalVoid<Wrapped: Parser>: Parser where Wrapped.Output == Void {
let wrapped: Wrapped?
public init(wrapped: Wrapped?) {
self.wrapped = wrapped
}
public func parse(_ input: inout Wrapped.Input) rethrows {
guard let wrapped = self.wrapped
else { return }
try wrapped.parse(&input)
}
}
}

View file

@ -0,0 +1,53 @@
/// A parser that runs the given parser and succeeds with `nil` if it fails.
///
/// Use this parser when you are parsing into an output data model that contains `nil`.
///
/// When the wrapped parser fails ``Optionally`` will backtrack any consumption of the input so
/// that later parsers can attempt to parser the input:
///
/// ```swift
/// let parser = Parse {
/// "Hello,"
/// Optionally { " "; Bool.parser() }
/// " world!"
/// }
///
/// try parser.parse("Hello, world!") // nil 1
/// try parser.parse("Hello, true world!") // true
/// ```
///
/// If ``Optionally`` did not backtrack then 1 would fail because it would consume a space,
/// causing the `" world!"` parser to fail since there is no longer any space to consume.
/// Read the article <doc:Backtracking> to learn more about how backtracking works.
///
/// If you are optionally parsing input that should coalesce into some default, you can skip the
/// optionality and instead use ``replaceError(with:)`` with a default:
///
/// ```swift
/// Optionally { Int.parser() }
/// .map { $0 ?? 0 }
///
/// // vs.
///
/// Int.parser()
/// .replaceError(with: 0)
/// ```
public struct Optionally<Wrapped: Parser>: Parser {
public let wrapped: Wrapped
@inlinable
public init(@ParserBuilder _ build: () -> Wrapped) {
self.wrapped = build()
}
@inlinable
public func parse(_ input: inout Wrapped.Input) -> Wrapped.Output? {
let original = input
do {
return try self.wrapped.parse(&input)
} catch {
input = original
return nil
}
}
}

View file

@ -0,0 +1,51 @@
/// A parser that attempts to run a number of parsers to accumulate their outputs.
///
/// A general entry point into ``ParserBuilder`` syntax, which can be used to build complex parsers
/// from simpler ones.
///
/// ```swift
/// let point = Parse {
/// "("
/// Int.parser()
/// ","
/// Int.parser()
/// ")"
/// }
///
/// try point.parse("(2,-4)") // (2, -4)
///
/// try point.parse("(42,blob)")
/// // error: unexpected input
/// // --> input:1:5
/// // 1 | (42,blob)
/// // | ^ expected integer
/// ```
public struct Parse<Parsers: Parser>: Parser {
public let parsers: Parsers
@inlinable
public init(@ParserBuilder _ build: () -> Parsers) {
self.parsers = build()
}
@inlinable
public init<Upstream, NewOutput>(
_ transform: @escaping (Upstream.Output) -> NewOutput,
@ParserBuilder with build: () -> Upstream
) where Parsers == SwiftTL.Parsers.Map<Upstream, NewOutput> {
self.parsers = build().map(transform)
}
@inlinable
public init<Upstream, NewOutput>(
_ output: NewOutput,
@ParserBuilder with build: () -> Upstream
) where Upstream.Output == Void, Parsers == SwiftTL.Parsers.Map<Upstream, NewOutput> {
self.parsers = build().map { output }
}
@inlinable
public func parse(_ input: inout Parsers.Input) rethrows -> Parsers.Output {
try self.parsers.parse(&input)
}
}

View file

@ -0,0 +1,6 @@
/// A namespace for types that serve as parsers.
///
/// The various operators defined as extensions on ``Parser`` implement their functionality as
/// classes or structures that extend this enumeration. For example, the ``Parser/map(_:)`` operator
/// returns a ``Map`` parser.
public enum Parsers {}

View file

@ -0,0 +1,43 @@
/// A parser that runs the given parser, but does not consume any input.
///
/// It lets the upstream parser "peek" into the input without consuming it.
///
/// For example, identifiers (variables, functions, etc.) in Swift allow the first character to be a
/// letter or underscore, but not a digit, but subsequent characters can be digits. _E.g._, `foo123`
/// is a valid identifier, but `123foo` is not. We can create an identifier parser by using `Peek`
/// to first check if the input starts with a letter or underscore, and if it does, return the
/// remainder of the input up to the first character that is not a letter, a digit, or an
/// underscore.
///
/// ```swift
/// let identifier = Parse {
/// Peek { Prefix(1) { $0.isLetter || $0 == "_" } }
/// Prefix { $0.isNumber || $0.isLetter || $0 == "_" }
/// }
///
/// try identifier.parse("foo123") // "foo123"
/// try identifier.parse("_foo123") // "_foo123"
/// try identifier.parse("1_foo123") //
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | 1_foo123
/// // | ^ expected 1 element satisfying predicate
/// ```
public struct Peek<Upstream: Parser>: Parser {
public let upstream: Upstream
/// Construct a parser that runs the given parser, but does not consume any input.
///
/// - Parameter build: A parser this parser wants to inspect.
@inlinable
public init(@ParserBuilder _ build: () -> Upstream) {
self.upstream = build()
}
@inlinable
public func parse(_ input: inout Upstream.Input) rethrows {
let original = input
_ = try self.upstream.parse(&input)
input = original
}
}

View file

@ -0,0 +1,76 @@
extension Parser {
/// Returns a parser that runs this parser, pipes its output into the given parser, and returns
/// the output of the given parser.
///
/// For example, we can try to parse an integer of exactly 4 digits by piping the output of
/// ``Prefix`` into an `Int.parser()`:
///
/// ```swift
/// let year = Prefix(4).pipe { Int.parser() }
///
/// try year.parse("2022") // 2022
/// try year.parse("0123") // 123
/// ```
///
/// This parser fails if either the upstream or downstream parser fails. For example:
///
/// ```swift
/// try year.parse("123")
/// // error: unexpected input
/// // --> input:1:4
/// // 1 | 123
/// // | ^ expected 1 more element
///
/// try year.parse("fail!")
/// // error: unexpected input
/// // --> input:1:1-4
/// // 1 | fail!
/// // | ^^^^ pipe: expected integer
/// ```
///
/// - Parameter downstream: A parser that parses the output of this parser.
/// - Returns: A parser that pipes this parser's output into another parser. @inlinable
public func pipe<Downstream>(
@ParserBuilder _ build: () -> Downstream
) -> Parsers.Pipe<Self, Downstream> {
.init(upstream: self, downstream: build())
}
}
extension Parsers {
/// A parser that runs this parser, pipes its output into the given parser, and returns the output
/// of the given parser.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/pipe(_:)`` operation, which constructs this type.
public struct Pipe<Upstream: Parser, Downstream: Parser>: Parser
where Upstream.Output == Downstream.Input {
public let upstream: Upstream
public let downstream: Downstream
@inlinable
public init(upstream: Upstream, downstream: Downstream) {
self.upstream = upstream
self.downstream = downstream
}
@inlinable
public func parse(_ input: inout Upstream.Input) rethrows -> Downstream.Output {
let original = input
var downstreamInput = try self.upstream.parse(&input)
do {
return try self.downstream.parse(&downstreamInput)
} catch let ParsingError.failed(reason, context) {
throw ParsingError.failed(
"pipe: \(reason)",
.init(
originalInput: original,
remainingInput: input,
debugDescription: context.debugDescription,
underlyingError: ParsingError.failed(reason, context)
)
)
}
}
}
}

View file

@ -0,0 +1,286 @@
/// A parser that consumes a subsequence from the beginning of its input.
///
/// This parser is named after `Sequence.prefix`, which it uses under the hood to consume a number
/// of elements and return them as output. It can be configured with minimum and maximum lengths,
/// as well as a predicate.
///
/// For example, to parse as many numbers off the beginning of a substring:
///
/// ```swift
/// var input = "123 hello world"[...]
/// try Prefix { $0.isNumber }.parse(&input) // "123"
/// input // " Hello world"
/// ```
///
/// If you wanted this parser to fail if _no_ numbers are consumed, you could introduce a minimum
/// length.
///
/// ```swift
/// var input = "No numbers here"[...]
/// try Prefix(1...) { $0.isNumber }.parse(&input)
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | No numbers here
/// // | ^ expected 1 element satisfying predicate
/// ```
///
/// If a predicate is not provided, the parser will simply consume the prefix within the minimum and
/// maximum lengths provided:
///
/// ```swift
/// var input = "Lorem ipsum dolor"[...]
/// try Prefix(2).parse(&input) // "Lo"
/// input // "rem ipsum dolor"
/// ```
public struct Prefix<Input: Collection>: Parser where Input.SubSequence == Input {
public let maxLength: Int?
public let minLength: Int
public let predicate: ((Input.Element) -> Bool)?
/// Initializes a parser that consumes a subsequence from the beginning of its input.
///
/// - Parameters:
/// - minLength: The minimum number of elements to consume for parsing to be considered
/// successful.
/// - maxLength: The maximum number of elements to consume before the parser will return its
/// output.
/// - predicate: A closure that takes an element of the input sequence as its argument and
/// returns `true` if the element should be included or `false` if it should be excluded. Once
/// the predicate returns `false` it will not be called again.
@inlinable
public init(
minLength: Int = 0,
maxLength: Int? = nil,
while predicate: @escaping (Input.Element) -> Bool
) {
self.minLength = minLength
self.maxLength = maxLength
self.predicate = predicate
}
/// Initializes a parser that consumes a subsequence from the beginning of its input.
///
/// ```swift
/// try Prefix(2...4, while: \.isNumber).parse("123456") // "1234"
/// try Prefix(2...4, while: \.isNumber).parse("123") // "123"
///
/// try Prefix(2...4, while: \.isNumber).parse("1")
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | 1
/// // | ^ expected 1 more element satisfying predicate
/// ```
///
/// - Parameters:
/// - length: A closed range that provides a minimum number and maximum of elements to consume
/// for parsing to be considered successful.
/// - predicate: An optional closure that takes an element of the input sequence as its argument
/// and returns `true` if the element should be included or `false` if it should be excluded.
/// Once the predicate returns `false` it will not be called again.
@inlinable
public init(
_ length: ClosedRange<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.minLength = length.lowerBound
self.maxLength = length.upperBound
self.predicate = predicate
}
/// Initializes a parser that consumes a subsequence from the beginning of its input.
///
/// ```swift
/// try Prefix(4, while: \.isNumber).parse("123456") // "1234"
///
/// try Prefix(4, while: \.isNumber).parse("123")
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | 123
/// // | ^ expected 1 more element satisfying predicate
/// ```
///
/// - Parameters:
/// - length: An exact number of elements to consume for parsing to be considered successful.
/// - predicate: An optional closure that takes an element of the input sequence as its argument
/// and returns `true` if the element should be included or `false` if it should be excluded.
/// Once the predicate returns `false` it will not be called again.
@inlinable
public init(
_ length: Int,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.minLength = length
self.maxLength = length
self.predicate = predicate
}
/// Initializes a parser that consumes a subsequence from the beginning of its input.
///
/// ``` swift
/// try Prefix(4..., while: \.isNumber).parse("123456") // "123456"
///
/// try Prefix(4..., while: \.isNumber).parse("123")
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | 123
/// // | ^ expected 1 more element satisfying predicate
/// ```
///
/// - Parameters:
/// - length: A partial range that provides a minimum number of elements to consume for
/// parsing to be considered successful.
/// - predicate: An optional closure that takes an element of the input sequence as its argument
/// and returns `true` if the element should be included or `false` if it should be excluded.
/// Once the predicate returns `false` it will not be called again.
@inlinable
public init(
_ length: PartialRangeFrom<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.minLength = length.lowerBound
self.maxLength = nil
self.predicate = predicate
}
/// Initializes a parser that consumes a subsequence from the beginning of its input.
///
/// ```swift
/// try Prefix(...4, while: \.isNumber).parse("123456") // "1234"
/// try Prefix(...4, while: \.isNumber).parse("123") // "123"
/// ```
///
/// - Parameters:
/// - length: A partial, inclusive range that provides a maximum number of elements to consume.
/// - predicate: An optional closure that takes an element of the input sequence as its argument
/// and returns `true` if the element should be included or `false` if it should be excluded.
/// Once the predicate returns `false` it will not be called again.
@inlinable
public init(
_ length: PartialRangeThrough<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.minLength = 0
self.maxLength = length.upperBound
self.predicate = predicate
}
@inlinable
@inline(__always)
public func parse(_ input: inout Input) throws -> Input {
var prefix = maxLength.map(input.prefix) ?? input
prefix = predicate.map { prefix.prefix(while: $0) } ?? prefix
let count = prefix.count
input.removeFirst(count)
guard count >= self.minLength else {
let atLeast = self.minLength - count
throw ParsingError.expectedInput(
"""
\(self.minLength - count) \(count == 0 ? "" : "more ")element\(atLeast == 1 ? "" : "s")\
\(predicate == nil ? "" : " satisfying predicate")
""",
at: input
)
}
return prefix
}
}
extension Prefix where Input == Substring {
@_disfavoredOverload
@inlinable
public init(
minLength: Int = 0,
maxLength: Int? = nil,
while predicate: @escaping (Input.Element) -> Bool
) {
self.init(minLength: minLength, maxLength: maxLength, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: ClosedRange<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: Int,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: PartialRangeFrom<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: PartialRangeThrough<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
}
extension Prefix where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init(
minLength: Int = 0,
maxLength: Int? = nil,
while predicate: @escaping (Input.Element) -> Bool
) {
self.init(minLength: minLength, maxLength: maxLength, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: ClosedRange<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: Int,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: PartialRangeFrom<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
@_disfavoredOverload
@inlinable
public init(
_ length: PartialRangeThrough<Int>,
while predicate: ((Input.Element) -> Bool)? = nil
) {
self.init(length, while: predicate)
}
}
extension Parsers {
public typealias Prefix = SwiftTL.Prefix // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,73 @@
/// A parser that consumes a subsequence from the beginning of its input through a given sequence of
/// elements.
///
/// This parser is named after `Sequence.prefix(through:)`, and uses similar logic under the hood to
/// consume and return input through a particular subsequence.
///
/// ```swift
/// let lineParser = PrefixThrough("\n")
///
/// var input = "Hello\nworld\n"[...]
/// try line.parse(&input) // "Hello\n"
/// input // "world\n"
/// ```
public struct PrefixThrough<Input: Collection>: Parser where Input.SubSequence == Input {
public let possibleMatch: Input
public let areEquivalent: (Input.Element, Input.Element) -> Bool
@inlinable
public init(
_ possibleMatch: Input,
by areEquivalent: @escaping (Input.Element, Input.Element) -> Bool
) {
self.possibleMatch = possibleMatch
self.areEquivalent = areEquivalent
}
@inlinable
@inline(__always)
public func parse(_ input: inout Input) throws -> Input {
guard let first = self.possibleMatch.first else { return self.possibleMatch }
let count = self.possibleMatch.count
let original = input
while let index = input.firstIndex(where: { self.areEquivalent(first, $0) }) {
input = input[index...]
if input.count >= count,
zip(input[index...], self.possibleMatch).allSatisfy(self.areEquivalent)
{
let index = input.index(index, offsetBy: count)
input = input[index...]
return original[..<index]
}
input.removeFirst()
}
throw ParsingError.expectedInput("prefix through \(formatValue(self.possibleMatch))", at: input)
}
}
extension PrefixThrough where Input.Element: Equatable {
@inlinable
public init(_ possibleMatch: Input) {
self.init(possibleMatch, by: ==)
}
}
extension PrefixThrough where Input == Substring {
@_disfavoredOverload
@inlinable
public init(_ possibleMatch: String) {
self.init(possibleMatch[...])
}
}
extension PrefixThrough where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init(_ possibleMatch: String.UTF8View) {
self.init(String(possibleMatch)[...].utf8)
}
}
extension Parsers {
public typealias PrefixThrough = SwiftTL.PrefixThrough // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,71 @@
/// A parser that consumes a subsequence from the beginning of its input up to a given sequence of
/// elements.
///
/// This parser is named after `Sequence.prefix(upTo:)`, and uses similar logic under the hood to
/// consume and return input up to a particular subsequence.
///
/// ```swift
/// let lineParser = PrefixUpTo("\n")
///
/// var input = "Hello\nworld\n"[...]
/// try line.parse(&input) // "Hello"
/// input // "\nworld\n"
/// ```
public struct PrefixUpTo<Input: Collection>: Parser where Input.SubSequence == Input {
public let possibleMatch: Input
public let areEquivalent: (Input.Element, Input.Element) -> Bool
@inlinable
public init(
_ possibleMatch: Input,
by areEquivalent: @escaping (Input.Element, Input.Element) -> Bool
) {
self.possibleMatch = possibleMatch
self.areEquivalent = areEquivalent
}
@inlinable
@inline(__always)
public func parse(_ input: inout Input) throws -> Input {
guard let first = self.possibleMatch.first else { return self.possibleMatch }
let count = self.possibleMatch.count
let original = input
while let index = input.firstIndex(where: { self.areEquivalent(first, $0) }) {
input = input[index...]
if input.count >= count,
zip(input[index...], self.possibleMatch).allSatisfy(self.areEquivalent)
{
return original[..<index]
}
input.removeFirst()
}
throw ParsingError.expectedInput("prefix up to \(formatValue(self.possibleMatch))", at: input)
}
}
extension PrefixUpTo where Input.Element: Equatable {
@inlinable
public init(_ possibleMatch: Input) {
self.init(possibleMatch, by: ==)
}
}
extension PrefixUpTo where Input == Substring {
@_disfavoredOverload
@inlinable
public init(_ possiblePrefix: String) {
self.init(possiblePrefix[...])
}
}
extension PrefixUpTo where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init(_ possibleMatch: String.UTF8View) {
self.init(String(possibleMatch)[...].utf8)
}
}
extension Parsers {
public typealias PrefixUpTo = SwiftTL.PrefixUpTo // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,58 @@
extension Parser {
/// Transforms the `Input` of a parser.
///
/// This operator allows you to transform a parser of `Input`s into one on `NewInput`s, via a
/// writable key path from `NewInput` to `Input`. Intuitively you can think of this as a way of
/// transforming a parser on local data into one on more global data.
///
/// For example, the parser `Int.parser()` parses `Substring.UTF8View` collections into integers,
/// and there's a key path from `Substring.UTF8View` to `Substring`, and so we can `pullback`:
///
/// ```swift
/// var input = "123 Hello world"[...]
/// let output = try Int.parser().pullback(\.utf8).parse(&input) // 123
/// input // " Hello world"
/// ```
///
/// This has allowed us to parse `Substring`s with something that is only defined on
/// `Substring.UTF8View`.
///
/// - Parameter keyPath: A key path to pull parsing back along from this parser's input to a new
/// input.
/// - Returns: A parser that parses new input.
@inlinable
public func pullback<NewInput>(
_ keyPath: WritableKeyPath<NewInput, Input>
) -> Parsers.Pullback<Self, NewInput> {
.init(downstream: self, keyPath: keyPath)
}
}
extension Parsers {
/// Transforms the `Input` of a downstream parser.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/pullback(_:)`` operator, which constructs this type.
public struct Pullback<Downstream: Parser, Input>: Parser {
public let downstream: Downstream
public let keyPath: WritableKeyPath<Input, Downstream.Input>
@inlinable
public init(downstream: Downstream, keyPath: WritableKeyPath<Input, Downstream.Input>) {
self.downstream = downstream
self.keyPath = keyPath
}
@inlinable
public func parse(_ input: inout Input) rethrows -> Downstream.Output {
try self.downstream.parse(&input[keyPath: self.keyPath])
}
@inlinable
public func pullback<NewInput>(
_ keyPath: WritableKeyPath<NewInput, Input>
) -> Pullback<Downstream, NewInput> {
.init(downstream: self.downstream, keyPath: keyPath.appending(path: self.keyPath))
}
}
}

View file

@ -0,0 +1,70 @@
extension Parser {
/// A parser that replaces its error with a provided output.
///
/// Useful for providing a default output for a parser.
///
/// For example, we could create a parser that parses a plus or minus sign and maps the result to
/// a positive or negative multiplier respectively, or else defaults to a positive multiplier:
///
/// ```swift
/// let sign = OneOf {
/// "+".map { 1 }
/// "-".map { -1 }
/// }
/// .replaceError(with: 1)
/// ```
///
/// Notably this parser is non-throwing:
///
/// ```swift
/// var input = "-123"[...]
///
/// // No `try` required:
/// sign.parse(&input) // -1
/// input // "123"
///
/// // Simply returns the default when parsing fails:
/// sign.parse(&input) // 1
/// ```
///
/// This means it can be used to turn throwing parsers into non-throwing ones, which is important
/// for building up complex parsers that cannot fail.
///
/// - Parameter output: An output to return should the upstream parser fail.
/// - Returns: A parser that never fails.
@inlinable
public func replaceError(with output: Output) -> Parsers.ReplaceError<Self> {
.init(output: output, upstream: self)
}
}
extension Parsers {
/// A parser that replaces its error with a provided output.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// the ``Parser/replaceError(with:)`` operation, which constructs this type.
public struct ReplaceError<Upstream: Parser>: Parser {
@usableFromInline
let output: Upstream.Output
@usableFromInline
let upstream: Upstream
@usableFromInline
init(output: Upstream.Output, upstream: Upstream) {
self.output = output
self.upstream = upstream
}
@inlinable
public func parse(_ input: inout Upstream.Input) -> Upstream.Output {
let original = input
do {
return try self.upstream.parse(&input)
} catch {
input = original
return self.output
}
}
}
}

View file

@ -0,0 +1,52 @@
/// A parser that consumes everything to the end of the collection and returns this subsequence as
/// its output.
///
/// ```swift
/// var input = "Hello"[...]
/// Rest().parse(&input) // "Hello"
/// input // ""
/// ```
///
/// This parser fails if there is no input to consume:
///
/// ```swift
/// try Rest().parse("")
///
/// /// error: unexpected input
/// /// --> input:1:1
/// /// 1 |
/// /// | ^ expected a non-empty input
/// ```
///
/// If you want to allow for the possibility of an empty remaining input you can use the
/// ``Optionally`` parser to parse an optional output value, or the ``replaceError(with:)`` method
/// to coalesce the error into a default output value.
public struct Rest<Input: Collection>: Parser where Input.SubSequence == Input {
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws -> Input {
guard !input.isEmpty
else { throw ParsingError.expectedInput("a non-empty input", at: input) }
let output = input
input.removeFirst(input.count)
return output
}
}
extension Rest where Input == Substring {
@_disfavoredOverload
@inlinable
public init() {}
}
extension Rest where Input == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init() {}
}
extension Parsers {
public typealias Rest = SwiftTL.Rest // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,19 @@
/// A parser that discards the output of another parser.
public struct Skip<Parsers: Parser>: Parser {
/// The parser from which this parser receives output.
public let parsers: Parsers
@inlinable
public init(@ParserBuilder _ build: () -> Parsers) {
self.parsers = build()
}
@inlinable
public func parse(_ input: inout Parsers.Input) rethrows {
_ = try self.parsers.parse(&input)
}
}
extension Parsers {
public typealias Skip = SwiftTL.Skip // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,92 @@
/// A parser that parses a sequence of elements from its input.
///
/// This parser is named after `Sequence.starts(with:)`, and tests that the input it is parsing
/// starts with a given subsequence by calling this method under the hood.
///
/// If `true`, it consumes this prefix and returns `Void`:
///
/// ```swift
/// var input = "Hello, Blob!"[...]
///
/// StartsWith("Hello, ").parse(&input) // ()
/// input // "Blob!"
/// ```
///
/// If `false`, it fails and leaves input intact:
///
/// ```swift
/// var input = "Goodnight, Blob!"[...]
/// try StartsWith("Hello, ").parse(&input)
/// // error: unexpected input
/// // --> input:1:1
/// // 1 | Goodnight, Blob!
/// // | ^ expected "Hello, "
/// ```
///
/// This parser returns `Void` and _not_ the sequence of elements it consumes because the sequence
/// is already known at the time the parser is created (it is the value quite literally passed to
/// ``StartsWith/init(_:)``).
///
/// In many circumstances you can omit the `StartsWith` parser entirely and just use the collection
/// as the parser. For example:
///
/// ```swift
/// var input = "Hello, Blob!"[...]
///
/// try "Hello, ".parse(&input) // ()
/// input // "Blob!"
/// ```
public struct StartsWith<Input: Collection>: Parser where Input.SubSequence == Input {
public let count: Int
public let possiblePrefix: AnyCollection<Input.Element>
public let startsWith: (Input) -> Bool
/// Initializes a parser that successfully returns `Void` when the initial elements of its input
/// are equivalent to the elements in another sequence, using the given predicate as the
/// equivalence test.
///
/// - Parameters:
/// - possiblePrefix: A sequence to compare to the start of an input sequence.
/// - areEquivalent: A predicate that returns `true` if its two arguments are equivalent;
/// otherwise, `false`.
@inlinable
public init<PossiblePrefix>(
_ possiblePrefix: PossiblePrefix,
by areEquivalent: @escaping (Input.Element, Input.Element) -> Bool
)
where
PossiblePrefix: Collection,
PossiblePrefix.Element == Input.Element
{
self.count = possiblePrefix.count
self.possiblePrefix = AnyCollection(possiblePrefix)
self.startsWith = { input in input.starts(with: possiblePrefix, by: areEquivalent) }
}
@inlinable
public func parse(_ input: inout Input) throws {
guard self.startsWith(input) else {
throw ParsingError.expectedInput(formatValue(self.possiblePrefix), at: input)
}
input.removeFirst(self.count)
}
}
extension Parsers.StartsWith where Input.Element: Equatable {
/// Initializes a parser that successfully returns `Void` when the initial elements of its input
/// are equivalent to the elements in another sequence.
///
/// - Parameter possiblePrefix: A sequence to compare to the start of an input sequence.
@inlinable
public init<PossiblePrefix>(_ possiblePrefix: PossiblePrefix)
where
PossiblePrefix: Collection,
PossiblePrefix.Element == Input.Element
{
self.init(possiblePrefix, by: ==)
}
}
extension Parsers {
public typealias StartsWith = SwiftTL.StartsWith // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,55 @@
/// A parser that can parse streams of input.
///
/// For example, the following parser can parse an integer followed by a newline from a collection
/// of UTF8 bytes:
///
/// ```swift
/// Parse {
/// Int.parser(of: ArraySlice<UInt8>.self)
/// StartsWith("\n".utf8)
/// }
/// ```
///
/// This parser can be transformed into one that processes an incoming stream of UTF8 bytes:
///
/// ```swift
/// Stream {
/// Parse {
/// Int.parser(of: ArraySlice<UInt8>.self)
/// StartsWith("\n".utf8)
/// }
/// }
/// ```
///
/// And then it can be used on a stream, such as values coming from standard in:
///
/// ```swift
/// var stdin = AnyIterator {
/// readLine().map { ArraySlice($0.utf8) }
/// }
///
/// try newlineSeparatedIntegers.parse(&stdin)
/// ```
public struct Stream<Parsers: Parser>: Parser where Parsers.Input: RangeReplaceableCollection {
public let parsers: Parsers
@inlinable
public init(@ParserBuilder build: () -> Parsers) {
self.parsers = build()
}
@inlinable
public func parse(_ input: inout AnyIterator<Parsers.Input>) rethrows -> [Parsers.Output] {
var buffer = Parsers.Input()
var outputs: Output = []
while let chunk = input.next() {
buffer.append(contentsOf: chunk)
outputs.append(try self.parsers.parse(&buffer))
}
return outputs
}
}
extension Parsers {
public typealias Stream = SwiftTL.Stream // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,137 @@
import Foundation
extension UUID {
/// A parser that consumes a hexadecimal UUID from the beginning of a collection of UTF-8 code
/// units.
///
/// ```swift
/// var input = "deadbeef-dead-beef-dead-beefdeadbeef,"[...]
/// try UUID.parser().parse(&input) // DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF
/// input // ","
/// ```
///
/// - Parameter inputType: The collection type of UTF-8 code units to parse.
/// - Returns: A parser that consumes a hexadecimal UUID from the beginning of a collection of
/// UTF-8 code units.
@inlinable
public static func parser<Input>(
of inputType: Input.Type = Input.self
) -> Parsers.UUIDParser<Input> {
.init()
}
/// A parser that consumes a hexadecimal UUID from the beginning of a substring's UTF-8 view.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is
/// `Substring.UTF8View`.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a hexadecimal UUID from the beginning of a substring's UTF-8
/// view.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.UTF8View.Type = Substring.UTF8View.self
) -> Parsers.UUIDParser<Substring.UTF8View> {
.init()
}
/// A parser that consumes a hexadecimal UUID from the beginning of a substring.
///
/// This overload is provided to allow the `Input` generic to be inferred when it is `Substring`.
///
/// - Parameter inputType: The `Substring` type. This parameter is included to mirror the
/// interface that parses any collection of UTF-8 code units.
/// - Returns: A parser that consumes a hexadecimal UUID from the beginning of a substring.
@_disfavoredOverload
@inlinable
public static func parser(
of inputType: Substring.Type = Substring.self
) -> FromUTF8View<Substring, Parsers.UUIDParser<Substring.UTF8View>> {
.init { Parsers.UUIDParser<Substring.UTF8View>() }
}
}
extension Parsers {
/// A parser that consumes a UUID from the beginning of a collection of UTF8 code units.
///
/// You will not typically need to interact with this type directly. Instead you will usually use
/// `UUID.parser()`, which constructs this type.
public struct UUIDParser<Input: Collection>: Parser
where
Input.SubSequence == Input,
Input.Element == UTF8.CodeUnit
{
@inlinable
public init() {}
@inlinable
public func parse(_ input: inout Input) throws -> UUID {
func parseHelp<C>(_ bytes: C) throws -> UUID where C: Collection, C.Element == UTF8.CodeUnit {
var prefix = bytes.prefix(36)
guard prefix.count == 36
else { throw ParsingError.expectedInput("UUID", at: input) }
@inline(__always)
func digit(for n: UTF8.CodeUnit) throws -> UTF8.CodeUnit {
switch n {
case .init(ascii: "0") ... .init(ascii: "9"):
return UTF8.CodeUnit(n - .init(ascii: "0"))
case .init(ascii: "A") ... .init(ascii: "F"):
return UTF8.CodeUnit(n - .init(ascii: "A") + 10)
case .init(ascii: "a") ... .init(ascii: "f"):
return UTF8.CodeUnit(n - .init(ascii: "a") + 10)
default:
throw ParsingError.expectedInput("UUID", at: input)
}
}
@inline(__always)
func nextByte() throws -> UInt8 {
try digit(for: prefix.removeFirst()) * 16 + digit(for: prefix.removeFirst())
}
@inline(__always)
func chompHyphen() throws {
guard prefix.removeFirst() == .init(ascii: "-")
else { throw ParsingError.expectedInput("UUID", at: input) }
}
let _00 = try nextByte()
let _01 = try nextByte()
let _02 = try nextByte()
let _03 = try nextByte()
try chompHyphen()
let _04 = try nextByte()
let _05 = try nextByte()
try chompHyphen()
let _06 = try nextByte()
let _07 = try nextByte()
try chompHyphen()
let _08 = try nextByte()
let _09 = try nextByte()
try chompHyphen()
let _10 = try nextByte()
let _11 = try nextByte()
let _12 = try nextByte()
let _13 = try nextByte()
let _14 = try nextByte()
let _15 = try nextByte()
input.removeFirst(36)
return UUID(
uuid: (
_00, _01, _02, _03,
_04, _05,
_06, _07,
_08, _09,
_10, _11, _12, _13, _14, _15
)
)
}
return try input.withContiguousStorageIfAvailable(parseHelp) ?? parseHelp(input)
}
}
}

View file

@ -0,0 +1,66 @@
/// A parser that consumes all ASCII whitespace from the beginning of the input.
///
/// - Note: This parser only consumes ASCII spaces (`" "`), newlines (`"\n"` and `"\r"`), and tabs
/// (`"\t"`). If you need richer support that covers all unicode whitespace, use a ``Prefix``
/// parser that operates on the `Substring` level with a predicate that consumes whitespace:
///
/// ```swift
/// Prefix { $0.isWhitespace }
/// ```
public struct Whitespace<Input: Collection, Bytes: Collection>: Parser
where
Input.SubSequence == Input,
Bytes.SubSequence == Bytes,
Bytes.Element == UTF8.CodeUnit
{
@usableFromInline
let toBytes: (Input) -> Bytes
@usableFromInline
let fromBytes: (Bytes) -> Input
@inlinable
public func parse(_ input: inout Input) -> Input {
let output = self.toBytes(input).prefix(while: { (byte: UTF8.CodeUnit) in
byte == .init(ascii: " ")
|| byte == .init(ascii: "\n")
|| byte == .init(ascii: "\r")
|| byte == .init(ascii: "\t")
})
input.removeFirst(output.count)
return self.fromBytes(output)
}
}
extension Whitespace {
@inlinable
public init() where Bytes == Input {
self.toBytes = { $0 }
self.fromBytes = { $0 }
}
}
extension Whitespace where Input == Substring, Bytes == Substring.UTF8View {
@_disfavoredOverload
@inlinable
public init() {
self.toBytes = { $0.utf8 }
self.fromBytes = Substring.init
}
}
/*extension Whitespace where Input == Substring.UTF8View, Bytes == Input {
@_disfavoredOverload
@inlinable
public init() { self.init() }
}
extension Whitespace where Input == ArraySlice<UTF8.CodeUnit>, Bytes == Input {
@_disfavoredOverload
@inlinable
public init() { self.init() }
}*/
extension Parsers {
public typealias Whitespace = SwiftTL.Whitespace // NB: Convenience type alias for discovery
}

View file

@ -0,0 +1,486 @@
import Foundation
@usableFromInline
enum ParsingError: Error {
case failed(String, Context)
case manyFailed([Error], Context)
@usableFromInline
static func expectedInput(_ description: String, at remainingInput: Any) -> Self {
.failed(
summary: "unexpected input",
label: "expected \(description)",
at: remainingInput
)
}
@usableFromInline
static func expectedInput(
_ description: String,
from originalInput: Any,
to remainingInput: Any
) -> Self {
.failed(
summary: "unexpected input",
label: "expected \(description)",
from: originalInput,
to: remainingInput
)
}
@usableFromInline
static func failed(summary: String, label: String = "", at remainingInput: Any) -> Self {
.failed(label, .init(remainingInput: remainingInput, debugDescription: summary))
}
@usableFromInline
static func failed(
summary: String, label: String = "", from originalInput: Any, to remainingInput: Any
) -> Self {
.failed(
label,
.init(
originalInput: originalInput,
remainingInput: remainingInput,
debugDescription: summary
)
)
}
@usableFromInline
static func manyFailed(_ errors: [Error], at remainingInput: Any) -> Self {
.manyFailed(errors, .init(remainingInput: remainingInput, debugDescription: ""))
}
@usableFromInline
static func wrap(_ error: Error, at remainingInput: Any) -> Self {
error as? ParsingError
?? .failed(
"",
.init(
remainingInput: remainingInput,
debugDescription: formatError(error),
underlyingError: error
)
)
}
@usableFromInline
func flattened() -> Self {
func flatten(_ depth: Int = 0) -> (Error) -> [(depth: Int, error: Error)] {
{ error in
switch error {
case let ParsingError.manyFailed(errors, _):
return errors.flatMap(flatten(depth + 1))
default:
return [(depth, error)]
}
}
}
switch self {
case .failed:
return self
case let .manyFailed(errors, context):
return .manyFailed(
errors.flatMap(flatten())
.sorted {
switch ($0.error, $1.error) {
case let (lhs as ParsingError, rhs as ParsingError):
return lhs.context > rhs.context
default:
return $0.depth > $1.depth
}
}
.map { $0.error },
context
)
}
}
@usableFromInline
var context: Context {
switch self {
case let .failed(_, context), let .manyFailed(_, context):
return context
}
}
@usableFromInline
struct Context {
@usableFromInline
var debugDescription: String
@usableFromInline
var originalInput: Any
@usableFromInline
var remainingInput: Any
@usableFromInline
var underlyingError: Error?
@usableFromInline
init(
originalInput: Any,
remainingInput: Any,
debugDescription: String,
underlyingError: Error? = nil
) {
self.originalInput = originalInput
self.remainingInput = remainingInput
self.debugDescription = debugDescription
self.underlyingError = underlyingError
}
@usableFromInline
init(
remainingInput: Any,
debugDescription: String,
underlyingError: Error? = nil
) {
self.originalInput = remainingInput
self.remainingInput = remainingInput
self.debugDescription = debugDescription
self.underlyingError = underlyingError
}
}
}
extension ParsingError: CustomDebugStringConvertible {
@usableFromInline
var debugDescription: String {
switch self.flattened() {
case let .failed(label, context):
return format(labels: [label], context: context)
case let .manyFailed(errors, context) where errors.isEmpty:
return format(labels: [""], context: context)
case let .manyFailed(errors, _):
return debugDescription(for: errors)
}
}
fileprivate func debugDescription(for errors: [Error]) -> String {
func failed(_ error: Error) -> (String, Context)? {
guard
let error = error as? ParsingError,
case .failed(let label, let context) = error
else { return nil }
return (label, context)
}
var count = 0
var description = ""
func append(_ s: String) {
count += 1
if !description.isEmpty {
description.append("\n\n")
}
description.append(s)
}
var errors = errors[...]
while let error = errors.popFirst() {
guard case .some((let firstLabel, let firstContext)) = failed(error) else {
append(formatError(error))
continue
}
var labels = [firstLabel]
while case .some((let label, let context)) = errors.first.flatMap({ failed($0) }),
firstContext.canGroup(with: context)
{
errors.removeFirst()
labels.append(label)
}
append(format(labels: labels, context: firstContext))
}
if count != 1 {
description = """
error: multiple failures occurred
\(description)
"""
}
return description
}
}
extension ParsingError.Context {
fileprivate func canGroup(with other: Self) -> Bool {
func areSame(_ lhs: Any, _ rhs: Any) -> Bool {
switch (normalize(lhs), normalize(rhs)) {
case let (lhs as Substring, rhs as Substring):
return lhs.startIndex == rhs.startIndex && lhs.endIndex == rhs.endIndex
case let (lhs as Slice<[Substring]>, rhs as Slice<[Substring]>):
return zip(lhs, rhs).allSatisfy { l, r in
l.startIndex == r.startIndex && l.endIndex == r.endIndex
}
default:
return false
}
}
return
(areSame(self.remainingInput, other.remainingInput)
&& areSame(self.originalInput, other.originalInput)
&& self.underlyingError == nil && other.underlyingError == nil)
}
}
extension Array where Element == (label: String, context: ParsingError.Context) {
fileprivate func allSame(_ keyPath: KeyPath<ParsingError.Context, Any>) -> Bool {
guard let first = self.first else { return true }
guard let firstValue = first.context[keyPath: keyPath] as? AnyHashable else { return false }
return self.dropFirst().allSatisfy {
guard let value = $0.context[keyPath: keyPath] as? AnyHashable else { return false }
return value == firstValue
}
}
}
@usableFromInline
func format(labels: [String], context: ParsingError.Context) -> String {
func formatHelp<Input>(from originalInput: Input, to remainingInput: Input) -> String {
switch (normalize(originalInput), normalize(remainingInput)) {
case let (originalInput as Substring, remainingInput as Substring):
let substring =
originalInput.startIndex == remainingInput.startIndex
? originalInput
: originalInput.base[originalInput.startIndex..<remainingInput.startIndex]
let position = substring.base[..<substring.startIndex].reduce(
into: (0, 0)
) { (position: inout (line: Int, column: Int), character: Character) in
if character.isNewline {
position.line += 1
position.column = 0
} else {
position.column += 1
}
}
let through = substring.reduce(
into: position
) { (position: inout (line: Int, column: Int), character: Character) in
if character.isNewline {
position.line += 1
position.column = 0
} else {
position.column += 1
}
}
let offset = min(position.column, 20)
let selectedLine = substring.base[
substring.base.index(substring.startIndex, offsetBy: -offset)...
]
.prefix { !$0.isNewline }
let isStartTruncated = offset != position.column
var truncatedLine = selectedLine.prefix(79 - 4 - (isStartTruncated ? 1 : 0))
let isEndTruncated = truncatedLine.endIndex != selectedLine.endIndex
for index in truncatedLine.indices.reversed() {
guard truncatedLine[index].isWhitespace
else { break }
truncatedLine.replaceSubrange(index...index, with: "")
}
let diagnostic =
("\(isStartTruncated ? "" : "")\(truncatedLine)\(isEndTruncated ? "" : "")\n"
+ labels.map { label in
"""
\(String(repeating: " ", count: offset + (isStartTruncated ? 1 : 0)))\
\(String(repeating: "^", count: max(1, substring.count)))\
\(label.isEmpty ? "" : " \(label)")
"""
}.joined(separator: "\n"))
return formatError(
summary: context.debugDescription,
location: """
input:\(position.line + 1):\(position.column + 1)\
\(
through.line == position.line
? (through.column <= position.column + 1 ? "" : "-\(through.column)")
: "-\(through.line + 1):\(through.column + 1)")
""",
prefix: "\(position.line + 1)",
diagnostic: diagnostic
)
case let (originalInput as Slice<[Substring]>, remainingInput as Slice<[Substring]>):
let slice =
originalInput.startIndex == remainingInput.startIndex
? originalInput
: Slice(
base: originalInput.base,
bounds: originalInput.startIndex..<remainingInput.startIndex
)
let expectation: String
if let error = context.underlyingError as? ParsingError,
case let .failed(elementLabel, elementContext) = error,
let originalInput = normalize(elementContext.originalInput) as? Substring,
let remainingInput = normalize(elementContext.remainingInput) as? Substring
{
let substring =
originalInput.startIndex == remainingInput.startIndex
? originalInput
: originalInput.base[originalInput.startIndex..<remainingInput.startIndex]
let indent = String(
repeating: " ",
count: substring.distance(
from: substring.base.startIndex,
to: substring.startIndex
)
)
expectation = """
\(indent)\(String(repeating: "^", count: max(1, substring.count)))\
\(elementLabel.isEmpty ? "" : " \(elementLabel)")
"""
} else {
let count = slice.map(formatValue).joined(separator: ", ").count
expectation = labels.map { label in
"""
\(String(repeating: "^", count: max(1, count)))\(label.isEmpty ? "" : " \(label)")
"""
}.joined(separator: "\n")
}
let indent = slice.base[..<slice.startIndex].map { "\(formatValue($0.base)), " }.joined()
return formatError(
summary: context.debugDescription,
location: "input[\(slice.startIndex)]",
prefix: "\(slice.startIndex)",
diagnostic: """
\(slice.base)
\(String(repeating: " ", count: indent.count + 1))\(expectation)
"""
)
default:
return "error: \(context.debugDescription)"
}
}
return formatHelp(from: context.originalInput, to: context.remainingInput)
}
private func formatError(_ error: Error) -> String {
switch error {
case let error as ParsingError:
return error.debugDescription
case let error as LocalizedError:
return error.localizedDescription
default:
return "\(error)"
}
}
@usableFromInline
func formatValue<Input>(
_ input: Input
) -> String {
switch input {
case let input as String:
return input.debugDescription
case let input as String.UnicodeScalarView:
return String(input).debugDescription
case let input as String.UTF8View:
return String(input).debugDescription
case let input as Substring:
return input.debugDescription
case let input as Substring.UnicodeScalarView:
return Substring(input).debugDescription
case let input as Substring.UTF8View:
return Substring(input).debugDescription
default:
return "\(input)"
}
}
private func formatError(
summary: String,
location: String,
prefix: String,
diagnostic: String
) -> String {
let indent = String(repeating: " ", count: prefix.count)
var diagnostic =
diagnostic
.split(separator: "\n", omittingEmptySubsequences: false)
.map { "\(indent) |\($0.isEmpty ? "" : " \($0)")" }
.joined(separator: "\n")
diagnostic.replaceSubrange(..<indent.endIndex, with: prefix)
return """
error: \(summary)
\(indent)--> \(location)
\(diagnostic)
"""
}
extension ParsingError.Context {
fileprivate static func > (lhs: Self, rhs: Self) -> Bool {
switch (normalize(lhs.remainingInput), normalize(rhs.remainingInput)) {
case let (lhsInput as Substring, rhsInput as Substring):
return lhsInput.endIndex > rhsInput.endIndex
case let (lhsInput as Slice<[Substring]>, rhsInput as Slice<[Substring]>):
guard lhsInput.endIndex != rhsInput.endIndex else {
switch (lhs.underlyingError, rhs.underlyingError) {
case let (lhs as ParsingError, rhs as ParsingError):
return lhs.context > rhs.context
case (is ParsingError, _):
return true
default:
return false
}
}
return lhsInput.endIndex > rhsInput.endIndex
default:
return false
}
}
}
private func normalize(_ input: Any) -> Any {
// TODO: Use `_openExistential` for `C: Collection where C == C.SubSequence` for index juggling?
switch input {
case let input as Substring:
// NB: We want to ensure we are sliced at a character boundary and not a scalar boundary.
let startIndex =
input.startIndex == input.base.endIndex
? input.startIndex
: input.base.indices.last { $0 <= input.startIndex } ?? input.startIndex
let endIndex = input.endIndex == input.base.endIndex ? startIndex : input.endIndex
return input.base[startIndex..<endIndex]
case let input as Substring.UnicodeScalarView:
return normalize(Substring(input))
case let input as Substring.UTF8View:
return normalize(Substring(input))
case let input as Slice<[Substring]>:
return input.endIndex == input.base.endIndex ? input[..<input.startIndex] : input
default:
return input
}
}

View file

@ -0,0 +1,284 @@
import Foundation
struct QualifiedName: Hashable, Comparable, CustomStringConvertible {
var namespace: String?
var value: String
var description: String {
if let namespace = self.namespace {
return "\(namespace).\(self.value)"
} else {
return self.value
}
}
static func <(lhs: QualifiedName, rhs: QualifiedName) -> Bool {
return lhs.description < rhs.description
}
}
extension QualifiedName {
init(string: String) {
if let dotRange = string.range(of: ".") {
self.init(namespace: String(string[string.startIndex ..< dotRange.lowerBound]), value: String(string[dotRange.upperBound...]))
} else {
self.init(namespace: nil, value: string)
}
}
}
enum Resolver {
struct ResolutionError: Error, CustomStringConvertible {
var text: String
var description: String {
return self.text
}
}
indirect enum TypeReference {
case int32
case int64
case int256
case double
case bytes
case string
case bool
case boolTrue
case bareVector(TypeReference)
case boxedVector(TypeReference)
case bareConstructor(typeName: QualifiedName, name: QualifiedName)
case boxedType(QualifiedName)
}
struct Argument {
struct Condition {
var fieldName: String
var bitIndex: Int
}
var name: String
var type: TypeReference
var condition: Condition?
}
final class SumType {
struct Constructor {
var name: QualifiedName
var id: UInt32
var arguments: [Argument]
}
let name: QualifiedName
var constructors: [QualifiedName: Constructor] = [:]
init(name: QualifiedName) {
self.name = name
}
}
struct Function {
var name: QualifiedName
var id: UInt32
var arguments: [Argument]
var result: TypeReference
}
static func resolveBuiltinType(name: QualifiedName) -> TypeReference? {
if name.namespace == nil {
if name.value == "int" {
return .int32
} else if name.value == "long" {
return .int64
} else if name.value == "int256" {
return .int256
} else if name.value == "double" {
return .double
} else if name.value == "string" {
return .string
} else if name.value == "bytes" {
return .bytes
} else if name.value == "true" {
return .boolTrue
}
}
return nil
}
static func resolveTypes(constructors: [DescriptionParser.ConstructorDescription]) throws -> [SumType] {
var constructedTypes: [QualifiedName: [DescriptionParser.ConstructorDescription]] = [:]
var constructorNameToType: [QualifiedName: QualifiedName] = [:]
for constructorDescription in constructors {
switch constructorDescription.type {
case let .type(name):
if !name.value[name.value.startIndex].isUppercase {
throw ResolutionError(text: "Type constructor \(constructorDescription.name) -> \(name): the resulting type name should begin with a capital letter")
}
constructedTypes[name, default: []].append(constructorDescription)
if let _ = constructorNameToType[constructorDescription.name] {
throw ResolutionError(text: "Duplicate type constructor \(constructorDescription.name) found")
}
constructorNameToType[constructorDescription.name] = name
case let .generic(name, argumentType):
throw ResolutionError(text: "Type constructor \(constructorDescription.name) can not be used to construct a generic type \(name)<\(argumentType)>")
}
}
func resolveTypeReference(description: DescriptionParser.TypeReferenceDescription) throws -> TypeReference {
switch description {
case let .type(name):
if let resolvedBuiltinType = resolveBuiltinType(name: name) {
return resolvedBuiltinType
}
if name.value[name.value.startIndex].isUppercase {
if let _ = constructedTypes[name] {
return .boxedType(name)
} else {
throw ResolutionError(text: "Unresolved type \(name)")
}
} else {
if let typeName = constructorNameToType[name] {
return .bareConstructor(typeName: typeName, name: name)
} else {
throw ResolutionError(text: "Unresolved type constructor \(name)")
}
}
case let .generic(name, argumentType):
if name == "vector" {
return .bareVector(try resolveTypeReference(description: .type(name: argumentType)))
} else if name == "Vector" {
return .boxedVector(try resolveTypeReference(description: .type(name: argumentType)))
} else {
throw ResolutionError(text: "Unresolved generic type \(name)")
}
}
}
func resolveArgument(existingArguments: [Argument], description: DescriptionParser.ArgumentDescription) throws -> Argument {
return Argument(
name: description.name,
type: try resolveTypeReference(description: description.type),
condition: try description.condition.flatMap { condition -> Argument.Condition in
if !existingArguments.contains(where: { $0.name == condition.fieldName }) {
throw ResolutionError(text: "Unresolved conditional field reference to \(condition.fieldName)")
}
return Argument.Condition(fieldName: condition.fieldName, bitIndex: condition.bitIndex)
}
)
}
var types: [QualifiedName: SumType] = [:]
for (typeName, constructorDescriptions) in constructedTypes {
let type = SumType(name: typeName)
for constructorDescription in constructorDescriptions {
var arguments: [Argument] = []
for argumentDescription in constructorDescription.arguments {
arguments.append(try resolveArgument(existingArguments: arguments, description: argumentDescription))
}
guard let id = constructorDescription.explicitId else {
throw ResolutionError(text: "Constructor \(constructorDescription.name) does not have an id")
}
type.constructors[constructorDescription.name] = SumType.Constructor(
name: constructorDescription.name,
id: id,
arguments: arguments
)
}
types[type.name] = type
}
return types.values.sorted(by: { $0.name < $1.name })
}
static func resolveFunctions(types: [SumType], functionDescriptions: [DescriptionParser.ConstructorDescription]) throws -> [Function] {
var functions: [QualifiedName: Function] = [:]
var typeMap: [QualifiedName: SumType] = [:]
var constructorMap: [QualifiedName: SumType] = [:]
for type in types {
typeMap[type.name] = type
for (_, constructor) in type.constructors {
constructorMap[constructor.name] = type
}
}
func resolveTypeReference(description: DescriptionParser.TypeReferenceDescription) throws -> TypeReference {
switch description {
case let .type(name):
if let resolvedBuiltinType = resolveBuiltinType(name: name) {
return resolvedBuiltinType
}
if name.value[name.value.startIndex].isUppercase {
if let _ = typeMap[name] {
return .boxedType(name)
} else {
throw ResolutionError(text: "Unresolved type \(name)")
}
} else {
if let type = constructorMap[name] {
return .bareConstructor(typeName: type.name, name: name)
} else {
throw ResolutionError(text: "Unresolved type constructor \(name)")
}
}
case let .generic(name, argumentType):
if name == "vector" {
return .bareVector(try resolveTypeReference(description: .type(name: argumentType)))
} else if name == "Vector" {
return .boxedVector(try resolveTypeReference(description: .type(name: argumentType)))
} else {
throw ResolutionError(text: "Unresolved generic type \(name)")
}
}
}
func resolveArgument(existingArguments: [Argument], description: DescriptionParser.ArgumentDescription) throws -> Argument {
return Argument(
name: description.name,
type: try resolveTypeReference(description: description.type),
condition: try description.condition.flatMap { condition -> Argument.Condition in
if !existingArguments.contains(where: { $0.name == condition.fieldName }) {
throw ResolutionError(text: "Unresolved conditional field reference to \(condition.fieldName)")
}
return Argument.Condition(fieldName: condition.fieldName, bitIndex: condition.bitIndex)
}
)
}
for functionDescription in functionDescriptions {
var arguments: [Argument] = []
for argumentDescription in functionDescription.arguments {
arguments.append(try resolveArgument(existingArguments: arguments, description: argumentDescription))
}
let result = try resolveTypeReference(description: functionDescription.type)
guard let id = functionDescription.explicitId else {
throw ResolutionError(text: "Function \(functionDescription.name) does not have an id")
}
functions[functionDescription.name] = Function(
name: functionDescription.name,
id: id,
arguments: arguments,
result: result
)
}
return functions.values.sorted(by: { $0.name < $1.name })
}
}

View file

@ -0,0 +1,130 @@
import Foundation
private extension String {
var camelCased: String {
var result = ""
var capitalizeNext = false
for c in self {
if c == "_" {
capitalizeNext = true
} else {
if capitalizeNext {
capitalizeNext = false
result.append(c.uppercased())
} else {
result.append(c)
}
}
}
return result
}
}
if CommandLine.arguments.count < 3 {
print("Usage: SwiftTL path-to-scheme.tl path-to-output-folder [--stub-functions] [--print-constructors=N-M]")
exit(0)
}
let schemeFilePath = CommandLine.arguments[1]
let outputDirectoryPath = CommandLine.arguments[2]
var stubFunctions = false
var printConstructorsRange: (start: Int, end: Int)? = nil
for arg in CommandLine.arguments {
if arg == "--stub-functions" {
stubFunctions = true
}
if arg.hasPrefix("--print-constructors=") {
let value = String(arg.dropFirst("--print-constructors=".count))
let parts = value.split(separator: "-")
if parts.count == 2, let start = Int(parts[0]), let end = Int(parts[1]) {
if start > end {
print("Error: Invalid range for --print-constructors: start (\(start)) must be <= end (\(end))")
exit(1)
}
printConstructorsRange = (start, end)
} else {
print("Error: Invalid format for --print-constructors. Expected: --print-constructors=N-M (e.g., --print-constructors=0-10)")
exit(1)
}
}
}
guard let data = try? String(contentsOfFile: schemeFilePath) else {
print("Could not open scheme file \(schemeFilePath)")
exit(1)
}
do {
let parsedData = try DescriptionParser.parse(data: data)
let resolvedTypes = try Resolver.resolveTypes(constructors: parsedData.constructors)
var resolvedFunctions = try Resolver.resolveFunctions(types: resolvedTypes, functionDescriptions: parsedData.functions)
resolvedFunctions.append(Resolver.Function(name: QualifiedName(namespace: "help", value: "test"), id: 0xc0e202f7, arguments: [], result: .boxedType(QualifiedName(namespace: nil, value: "Bool"))))
var constructorOrder: [(typeName: QualifiedName, constructorName: String)] = []
var typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])] = []
let sortedTypes = resolvedTypes.sorted(by: { $0.name < $1.name })
if let range = printConstructorsRange {
print("--- CONSTRUCTORS ---")
for (index, type) in sortedTypes.enumerated() {
if index >= range.start && index < range.end {
for constructor in type.constructors.values.sorted(by: { $0.name < $1.name }) {
let storedArguments = constructor.arguments.filter {
if case .boolTrue = $0.type { return false }
return true
}
if !storedArguments.isEmpty {
let fieldNames = storedArguments.map { $0.name.camelCased }
print("\(constructor.name.value):\(fieldNames.joined(separator: ","))")
}
}
}
}
print("--- END CONSTRUCTORS ---")
print("Total types: \(sortedTypes.count)")
exit(0)
}
for type in sortedTypes {
for constructor in type.constructors.values.sorted(by: { $0.name < $1.name }) {
constructorOrder.append((type.name, constructor.name.value))
}
}
var totalConstructorCount = 0
var currentConstructorCount = 0
for type in sortedTypes {
if typeOrder.isEmpty || currentConstructorCount >= 32 {
typeOrder.append(([], []))
currentConstructorCount = 0
}
typeOrder[typeOrder.count - 1].types.append((type.name, type.constructors.values.sorted(by: { $0.name < $1.name }).map(\.name.value)))
currentConstructorCount += type.constructors.count
totalConstructorCount += type.constructors.count
if totalConstructorCount > 40 {
}
}
typeOrder.append(([], []))
for function in resolvedFunctions.sorted(by: { $0.name < $1.name }) {
typeOrder[typeOrder.count - 1].functions.append(function.name)
}
try FileManager.default.createDirectory(at: URL(fileURLWithPath: outputDirectoryPath), withIntermediateDirectories: true, attributes: nil)
let generatedFiles = try CodeGenerator.generate(types: resolvedTypes, functions: resolvedFunctions, constructorOrder: constructorOrder, typeOrder: typeOrder, stubFunctions: stubFunctions)
for (name, fileData) in generatedFiles {
let filePath = URL(fileURLWithPath: outputDirectoryPath).appendingPathComponent(name).path
let _ = try? FileManager.default.removeItem(atPath: filePath)
try fileData.write(toFile: filePath, atomically: true, encoding: .utf8)
}
} catch let e {
print("\(e)")
}