From 22e58eb5e472a68187bb5a29a2f11a72d4c133ae Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 27 Mar 2026 19:22:57 +0800 Subject: [PATCH] Add SwiftTL --- build-system/SwiftTL/Package.swift | 15 + build-system/SwiftTL/README.md | 3 + .../Sources/SwiftTL/CodeGeneration.swift | 986 +++ .../Sources/SwiftTL/DescriptionParsing.swift | 228 + .../Sources/SwiftTL/LegacyOrderParser.swift | 128 + .../Parser/Builders/OneOfBuilder.swift | 146 + .../Parser/Builders/ParserBuilder.swift | 103 + .../SwiftTL/Parser/Builders/Variadics.swift | 6317 +++++++++++++++++ .../Articles/Backtracking.md | 132 + .../Documentation.docc/Articles/Design.md | 131 + .../Articles/ErrorMessages.md | 239 + .../Articles/GettingStarted.md | 303 + .../Articles/Parsers/Bool.md | 69 + .../Articles/Parsers/CaseIterable.md | 73 + .../Articles/Parsers/CharacterSet.md | 14 + .../Articles/Parsers/Float.md | 66 + .../Articles/Parsers/Int.md | 84 + .../Articles/Parsers/String.md | 38 + .../Articles/Parsers/UUID.md | 15 + .../Articles/StringAbstractions.md | 183 + .../Documentation.docc/Extensions/OneOf.md | 7 + .../Documentation.docc/Extensions/Parse.md | 7 + .../Documentation.docc/Extensions/Parser.md | 68 + .../Parser/Documentation.docc/Parsing.md | 106 + .../SwiftTL/Parser/Internal/Breakpoint.swift | 34 + .../Parser/Internal/Deprecations.swift | 4 + .../Sources/SwiftTL/Parser/Parser.swift | 204 + .../SwiftTL/Parser/Parsers/Always.swift | 101 + .../SwiftTL/Parser/Parsers/AnyParser.swift | 63 + .../Sources/SwiftTL/Parser/Parsers/Bool.swift | 78 + .../CaseIterableRawRepresentable.swift | 143 + .../SwiftTL/Parser/Parsers/CharacterSet.swift | 10 + .../SwiftTL/Parser/Parsers/CompactMap.swift | 78 + .../SwiftTL/Parser/Parsers/Conditional.swift | 49 + .../Sources/SwiftTL/Parser/Parsers/End.swift | 54 + .../Sources/SwiftTL/Parser/Parsers/Fail.swift | 72 + .../SwiftTL/Parser/Parsers/Filter.swift | 61 + .../SwiftTL/Parser/Parsers/First.swift | 52 + .../SwiftTL/Parser/Parsers/FlatMap.swift | 52 + .../SwiftTL/Parser/Parsers/Float.swift | 172 + .../Parser/Parsers/FromSubstring.swift | 72 + .../SwiftTL/Parser/Parsers/FromUTF8View.swift | 32 + .../Parsers/FromUnicodeScalarView.swift | 42 + .../Sources/SwiftTL/Parser/Parsers/Int.swift | 163 + .../Sources/SwiftTL/Parser/Parsers/Lazy.swift | 27 + .../SwiftTL/Parser/Parsers/Literal.swift | 39 + .../Sources/SwiftTL/Parser/Parsers/Many.swift | 353 + .../Sources/SwiftTL/Parser/Parsers/Map.swift | 42 + .../SwiftTL/Parser/Parsers/Newline.swift | 70 + .../Sources/SwiftTL/Parser/Parsers/Not.swift | 42 + .../SwiftTL/Parser/Parsers/OneOf.swift | 166 + .../SwiftTL/Parser/Parsers/OneOfMany.swift | 47 + .../SwiftTL/Parser/Parsers/Optional.swift | 40 + .../SwiftTL/Parser/Parsers/Optionally.swift | 53 + .../SwiftTL/Parser/Parsers/Parse.swift | 51 + .../SwiftTL/Parser/Parsers/Parsers.swift | 6 + .../Sources/SwiftTL/Parser/Parsers/Peek.swift | 43 + .../Sources/SwiftTL/Parser/Parsers/Pipe.swift | 76 + .../SwiftTL/Parser/Parsers/Prefix.swift | 286 + .../Parser/Parsers/PrefixThrough.swift | 73 + .../SwiftTL/Parser/Parsers/PrefixUpTo.swift | 71 + .../SwiftTL/Parser/Parsers/Pullback.swift | 58 + .../SwiftTL/Parser/Parsers/ReplaceError.swift | 70 + .../Sources/SwiftTL/Parser/Parsers/Rest.swift | 52 + .../Sources/SwiftTL/Parser/Parsers/Skip.swift | 19 + .../SwiftTL/Parser/Parsers/StartsWith.swift | 92 + .../SwiftTL/Parser/Parsers/Stream.swift | 55 + .../Sources/SwiftTL/Parser/Parsers/UUID.swift | 137 + .../SwiftTL/Parser/Parsers/Whitespace.swift | 66 + .../Sources/SwiftTL/Parser/ParsingError.swift | 486 ++ .../SwiftTL/Sources/SwiftTL/Resolution.swift | 284 + .../SwiftTL/Sources/SwiftTL/main.swift | 130 + 72 files changed, 13931 insertions(+) create mode 100644 build-system/SwiftTL/Package.swift create mode 100644 build-system/SwiftTL/README.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/OneOfBuilder.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/ParserBuilder.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/Variadics.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Backtracking.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Design.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/ErrorMessages.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/GettingStarted.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Bool.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CaseIterable.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CharacterSet.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Float.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Int.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/String.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/UUID.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/StringAbstractions.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/OneOf.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parse.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parser.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Parsing.md create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Breakpoint.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Deprecations.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parser.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Always.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/AnyParser.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Bool.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CaseIterableRawRepresentable.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CharacterSet.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CompactMap.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Conditional.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/End.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Fail.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Filter.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/First.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FlatMap.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Float.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromSubstring.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUTF8View.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUnicodeScalarView.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Int.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Lazy.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Literal.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Many.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Map.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Newline.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Not.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOf.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOfMany.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optional.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optionally.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parse.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parsers.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Peek.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Pipe.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Prefix.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/PrefixThrough.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/PrefixUpTo.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Pullback.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/ReplaceError.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Rest.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Skip.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/StartsWith.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Stream.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/UUID.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Whitespace.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Parser/ParsingError.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/Resolution.swift create mode 100644 build-system/SwiftTL/Sources/SwiftTL/main.swift diff --git a/build-system/SwiftTL/Package.swift b/build-system/SwiftTL/Package.swift new file mode 100644 index 0000000000..09227b92c4 --- /dev/null +++ b/build-system/SwiftTL/Package.swift @@ -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: []), + ] +) diff --git a/build-system/SwiftTL/README.md b/build-system/SwiftTL/README.md new file mode 100644 index 0000000000..215ff5fe98 --- /dev/null +++ b/build-system/SwiftTL/README.md @@ -0,0 +1,3 @@ +# SwiftTL + + diff --git a/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift b/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift new file mode 100644 index 0000000000..2e3c43482e --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift @@ -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 = [] + 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() + for type in types { + if let namespace = type.name.namespace { + namespaces.insert(namespace) + } + } + + var functionNamespaces = Set() + 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(_ 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("}") + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift b/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift new file mode 100644 index 0000000000..e3b12d3a42 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift @@ -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(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 { $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 + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift b/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift new file mode 100644 index 0000000000..4a4feb1570 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift @@ -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) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/OneOfBuilder.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/OneOfBuilder.swift new file mode 100644 index 0000000000..e5f15c6843 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/OneOfBuilder.swift @@ -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

(_ parsers: [P]) -> Parsers.OneOfMany

{ + .init(parsers) + } + + /// Provides support for specifying a parser in ``OneOfBuilder`` blocks. + @inlinable + static public func buildBlock(_ 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( + first parser: TrueParser + ) -> Parsers.Conditional { + .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( + second parser: FalseParser + ) -> Parsers.Conditional { + .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

(_ parser: P?) -> OptionalOneOf

{ + .init(wrapped: parser) + } + + /// Provides support for `if #available` statements in ``OneOfBuilder`` blocks, producing an + /// optional parser. + @inlinable + public static func buildLimitedAvailability

(_ parser: P?) -> OptionalOneOf

{ + .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: 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/ParserBuilder.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/ParserBuilder.swift new file mode 100644 index 0000000000..b351e1ad26 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/ParserBuilder.swift @@ -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(_ 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( + first parser: TrueParser + ) -> Parsers.Conditional { + .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( + second parser: FalseParser + ) -> Parsers.Conditional { + .second(parser) + } + + /// Provides support for `if` statements in ``ParserBuilder`` blocks, producing an optional + /// parser. + @inlinable + public static func buildIf(_ 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

(_ parser: P?) -> Parsers.OptionalVoid

{ + .init(wrapped: parser) + } + + /// Provides support for `if #available` statements in ``ParserBuilder`` blocks, producing an + /// optional parser. + @inlinable + public static func buildLimitedAvailability(_ 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

(_ parser: P?) -> Parsers.OptionalVoid

{ + .init(wrapped: parser) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/Variadics.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/Variadics.swift new file mode 100644 index 0000000000..af4ae282f1 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Builders/Variadics.swift @@ -0,0 +1,6317 @@ +// BEGIN AUTO-GENERATED CONTENT + +extension ParserBuilder { + public struct ZipOO: Parser + where + P0.Input == P1.Input + { + public let p0: P0, p1: P1 + + @inlinable public init(_ p0: P0, _ p1: P1) { + self.p0 = p0 + self.p1 = p1 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + return (o0, o1) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1 + ) -> ParserBuilder.ZipOO { + ParserBuilder.ZipOO(p0, p1) + } +} + +extension ParserBuilder { + public struct ZipOV: Parser + where + P0.Input == P1.Input, + P1.Output == Void + { + public let p0: P0, p1: P1 + + @inlinable public init(_ p0: P0, _ p1: P1) { + self.p0 = p0 + self.p1 = p1 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + return o0 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1 + ) -> ParserBuilder.ZipOV { + ParserBuilder.ZipOV(p0, p1) + } +} + +extension ParserBuilder { + public struct ZipVO: Parser + where + P0.Input == P1.Input, + P0.Output == Void + { + public let p0: P0, p1: P1 + + @inlinable public init(_ p0: P0, _ p1: P1) { + self.p0 = p0 + self.p1 = p1 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P1.Output { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + return o1 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1 + ) -> ParserBuilder.ZipVO { + ParserBuilder.ZipVO(p0, p1) + } +} + +extension ParserBuilder { + public struct ZipVV: Parser + where + P0.Input == P1.Input, + P0.Output == Void, + P1.Output == Void + { + public let p0: P0, p1: P1 + + @inlinable public init(_ p0: P0, _ p1: P1) { + self.p0 = p0 + self.p1 = p1 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows { + do { + try p0.parse(&input) + try p1.parse(&input) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1 + ) -> ParserBuilder.ZipVV { + ParserBuilder.ZipVV(p0, p1) + } +} + +extension ParserBuilder { + public struct ZipOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + return (o0, o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipOOO { + ParserBuilder.ZipOOO(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + return (o0, o1) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipOOV { + ParserBuilder.ZipOOV(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + return (o0, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipOVO { + ParserBuilder.ZipOVO(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + return o0 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipOVV { + ParserBuilder.ZipOVV(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P0.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + return (o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipVOO { + ParserBuilder.ZipVOO(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P0.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P1.Output { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + return o1 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipVOV { + ParserBuilder.ZipVOV(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P0.Output == Void, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P2.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + return o2 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipVVO { + ParserBuilder.ZipVVO(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> ParserBuilder.ZipVVV { + ParserBuilder.ZipVVV(p0, p1, p2) + } +} + +extension ParserBuilder { + public struct ZipOOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o0, o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOOOO { + ParserBuilder.ZipOOOO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + return (o0, o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOOOV { + ParserBuilder.ZipOOOV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o0, o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOOVO { + ParserBuilder.ZipOOVO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + return (o0, o1) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOOVV { + ParserBuilder.ZipOOVV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o0, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOVOO { + ParserBuilder.ZipOVOO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + return (o0, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOVOV { + ParserBuilder.ZipOVOV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o0, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOVVO { + ParserBuilder.ZipOVVO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + return o0 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipOVVV { + ParserBuilder.ZipOVVV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVOOO { + ParserBuilder.ZipVOOO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + return (o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVOOV { + ParserBuilder.ZipVOOV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVOVO { + ParserBuilder.ZipVOVO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P1.Output { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + return o1 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVOVV { + ParserBuilder.ZipVOVV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + return (o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVVOO { + ParserBuilder.ZipVVOO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P2.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + return o2 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVVOV { + ParserBuilder.ZipVVOV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P3.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + return o3 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVVVO { + ParserBuilder.ZipVVVO(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipVVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> ParserBuilder.ZipVVVV { + ParserBuilder.ZipVVVV(p0, p1, p2, p3) + } +} + +extension ParserBuilder { + public struct ZipOOOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o1, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOOOO { + ParserBuilder.ZipOOOOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o0, o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOOOV { + ParserBuilder.ZipOOOOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o1, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOOVO { + ParserBuilder.ZipOOOVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return (o0, o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOOVV { + ParserBuilder.ZipOOOVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o1, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOVOO { + ParserBuilder.ZipOOVOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o0, o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOVOV { + ParserBuilder.ZipOOVOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o1, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOVVO { + ParserBuilder.ZipOOVVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return (o0, o1) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOOVVV { + ParserBuilder.ZipOOVVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVOOO { + ParserBuilder.ZipOVOOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o0, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVOOV { + ParserBuilder.ZipOVOOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVOVO { + ParserBuilder.ZipOVOVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return (o0, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVOVV { + ParserBuilder.ZipOVOVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVVOO { + ParserBuilder.ZipOVVOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o0, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVVOV { + ParserBuilder.ZipOVVOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o0, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVVVO { + ParserBuilder.ZipOVVVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOVVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return o0 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipOVVVV { + ParserBuilder.ZipOVVVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o1, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOOOO { + ParserBuilder.ZipVOOOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOOOV { + ParserBuilder.ZipVOOOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o1, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOOVO { + ParserBuilder.ZipVOOVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return (o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOOVV { + ParserBuilder.ZipVOOVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o1, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOVOO { + ParserBuilder.ZipVOVOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOVOV { + ParserBuilder.ZipVOVOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o1, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOVVO { + ParserBuilder.ZipVOVVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVOVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P1.Output { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return o1 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVOVVV { + ParserBuilder.ZipVOVVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVOOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVOOO { + ParserBuilder.ZipVVOOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVOOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return (o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVOOV { + ParserBuilder.ZipVVOOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVOVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVOVO { + ParserBuilder.ZipVVOVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVOVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P2.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + return o2 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVOVV { + ParserBuilder.ZipVVOVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVVOO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + return (o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVVOO { + ParserBuilder.ZipVVVOO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVVOV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P3.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + return o3 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVVOV { + ParserBuilder.ZipVVVOV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVVVO: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P4.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + return o4 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVVVO { + ParserBuilder.ZipVVVVO(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipVVVVV: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> ParserBuilder.ZipVVVVV { + ParserBuilder.ZipVVVVV(p0, p1, p2, p3, p4) + } +} + +extension ParserBuilder { + public struct ZipOOOOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o2, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOOOO { + ParserBuilder.ZipOOOOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOOOV { + ParserBuilder.ZipOOOOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o2, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOOVO { + ParserBuilder.ZipOOOOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOOVV { + ParserBuilder.ZipOOOOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o2, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOVOO { + ParserBuilder.ZipOOOVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOVOV { + ParserBuilder.ZipOOOVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o2, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOVVO { + ParserBuilder.ZipOOOVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOOVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOOVVV { + ParserBuilder.ZipOOOVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVOOO { + ParserBuilder.ZipOOVOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVOOV { + ParserBuilder.ZipOOVOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVOVO { + ParserBuilder.ZipOOVOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVOVV { + ParserBuilder.ZipOOVOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVVOO { + ParserBuilder.ZipOOVVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o1, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVVOV { + ParserBuilder.ZipOOVVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o1, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVVVO { + ParserBuilder.ZipOOVVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOOVVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P1.Output + ) { + do { + let o0 = try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o1) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOOVVVV { + ParserBuilder.ZipOOVVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o2, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOOOO { + ParserBuilder.ZipOVOOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOOOV { + ParserBuilder.ZipOVOOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o2, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOOVO { + ParserBuilder.ZipOVOOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOOVV { + ParserBuilder.ZipOVOOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o2, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOVOO { + ParserBuilder.ZipOVOVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOVOV { + ParserBuilder.ZipOVOVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o2, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOVVO { + ParserBuilder.ZipOVOVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVOVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P2.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVOVVV { + ParserBuilder.ZipOVOVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVOOO { + ParserBuilder.ZipOVVOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVOOV { + ParserBuilder.ZipOVVOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVOVO { + ParserBuilder.ZipOVVOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P3.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o0, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVOVV { + ParserBuilder.ZipOVVOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P4.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVVOO { + ParserBuilder.ZipOVVVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P4.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o0, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVVOV { + ParserBuilder.ZipOVVVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P0.Output, + P5.Output + ) { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o0, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVVVO { + ParserBuilder.ZipOVVVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipOVVVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + do { + let o0 = try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return o0 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipOVVVVV { + ParserBuilder.ZipOVVVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o2, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOOOO { + ParserBuilder.ZipVOOOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o1, o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOOOV { + ParserBuilder.ZipVOOOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o2, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOOVO { + ParserBuilder.ZipVOOOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o1, o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOOVV { + ParserBuilder.ZipVOOOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o2, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOVOO { + ParserBuilder.ZipVOOVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o1, o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOVOV { + ParserBuilder.ZipVOOVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o2, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOVVO { + ParserBuilder.ZipVOOVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOOVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P2.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o1, o2) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOOVVV { + ParserBuilder.ZipVOOVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVOOO { + ParserBuilder.ZipVOVOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o1, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVOOV { + ParserBuilder.ZipVOVOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVOVO { + ParserBuilder.ZipVOVOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P3.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o1, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVOVV { + ParserBuilder.ZipVOVOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVVOO { + ParserBuilder.ZipVOVVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P4.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o1, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVVOV { + ParserBuilder.ZipVOVVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P1.Output, + P5.Output + ) { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o1, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVVVO { + ParserBuilder.ZipVOVVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVOVVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P1.Output { + do { + try p0.parse(&input) + let o1 = try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return o1 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVOVVVV { + ParserBuilder.ZipVOVVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o2, o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOOOO { + ParserBuilder.ZipVVOOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o2, o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOOOV { + ParserBuilder.ZipVVOOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o2, o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOOVO { + ParserBuilder.ZipVVOOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P3.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return (o2, o3) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOOVV { + ParserBuilder.ZipVVOOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o2, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOVOO { + ParserBuilder.ZipVVOVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o2, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOVOV { + ParserBuilder.ZipVVOVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P2.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o2, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOVVO { + ParserBuilder.ZipVVOVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVOVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P2.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + let o2 = try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return o2 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVOVVV { + ParserBuilder.ZipVVOVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVOOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P3.Output, + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o3, o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVOOO { + ParserBuilder.ZipVVVOOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVOOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P3.Output, + P4.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return (o3, o4) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVOOV { + ParserBuilder.ZipVVVOOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVOVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P3.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o3, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVOVO { + ParserBuilder.ZipVVVOVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVOVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P3.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + let o3 = try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + return o3 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVOVV { + ParserBuilder.ZipVVVOVV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVVOO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> ( + P4.Output, + P5.Output + ) { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + let o5 = try p5.parse(&input) + return (o4, o5) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVVOO { + ParserBuilder.ZipVVVVOO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVVOV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P4.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + let o4 = try p4.parse(&input) + try p5.parse(&input) + return o4 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVVOV { + ParserBuilder.ZipVVVVOV(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVVVO: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P5.Output { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + let o5 = try p5.parse(&input) + return o5 + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVVVO { + ParserBuilder.ZipVVVVVO(p0, p1, p2, p3, p4, p5) + } +} + +extension ParserBuilder { + public struct ZipVVVVVV: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == Void, + P1.Output == Void, + P2.Output == Void, + P3.Output == Void, + P4.Output == Void, + P5.Output == Void + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows { + do { + try p0.parse(&input) + try p1.parse(&input) + try p2.parse(&input) + try p3.parse(&input) + try p4.parse(&input) + try p5.parse(&input) + } catch { throw ParsingError.wrap(error, at: input) } + } + } +} + +extension ParserBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> ParserBuilder.ZipVVVVVV { + ParserBuilder.ZipVVVVVV(p0, p1, p2, p3, p4, p5) + } +} + +extension OneOfBuilder { + public struct OneOf2: Parser + where + P0.Input == P1.Input, + P0.Output == P1.Output + { + public let p0: P0, p1: P1 + + @inlinable public init(_ p0: P0, _ p1: P1) { + self.p0 = p0 + self.p1 = p1 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + throw ParsingError.manyFailed( + [e0, e1], at: input + ) + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1 + ) -> OneOfBuilder.OneOf2 { + OneOfBuilder.OneOf2(p0, p1) + } +} + +extension OneOfBuilder { + public struct OneOf3: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P0.Output == P1.Output, + P1.Output == P2.Output + { + public let p0: P0, p1: P1, p2: P2 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + throw ParsingError.manyFailed( + [e0, e1, e2], at: input + ) + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2 + ) -> OneOfBuilder.OneOf3 { + OneOfBuilder.OneOf3(p0, p1, p2) + } +} + +extension OneOfBuilder { + public struct OneOf4: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3], at: input + ) + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3 + ) -> OneOfBuilder.OneOf4 { + OneOfBuilder.OneOf4(p0, p1, p2, p3) + } +} + +extension OneOfBuilder { + public struct OneOf5: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4], at: input + ) + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4 + ) -> OneOfBuilder.OneOf5 { + OneOfBuilder.OneOf5(p0, p1, p2, p3, p4) + } +} + +extension OneOfBuilder { + public struct OneOf6: + Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output, + P4.Output == P5.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + do { + input = original + return try self.p5.parse(&input) + } catch let e5 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4, e5], at: input + ) + } + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5 + ) -> OneOfBuilder.OneOf6 { + OneOfBuilder.OneOf6(p0, p1, p2, p3, p4, p5) + } +} + +extension OneOfBuilder { + public struct OneOf7< + P0: Parser, P1: Parser, P2: Parser, P3: Parser, P4: Parser, P5: Parser, P6: Parser + >: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P5.Input == P6.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output, + P4.Output == P5.Output, + P5.Output == P6.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6 + + @inlinable public init(_ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + self.p6 = p6 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + do { + input = original + return try self.p5.parse(&input) + } catch let e5 { + do { + input = original + return try self.p6.parse(&input) + } catch let e6 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4, e5, e6], at: input + ) + } + } + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6 + ) -> OneOfBuilder.OneOf7 { + OneOfBuilder.OneOf7(p0, p1, p2, p3, p4, p5, p6) + } +} + +extension OneOfBuilder { + public struct OneOf8< + P0: Parser, P1: Parser, P2: Parser, P3: Parser, P4: Parser, P5: Parser, P6: Parser, P7: Parser + >: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P5.Input == P6.Input, + P6.Input == P7.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output, + P4.Output == P5.Output, + P5.Output == P6.Output, + P6.Output == P7.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7 + + @inlinable public init( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7 + ) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + self.p6 = p6 + self.p7 = p7 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + do { + input = original + return try self.p5.parse(&input) + } catch let e5 { + do { + input = original + return try self.p6.parse(&input) + } catch let e6 { + do { + input = original + return try self.p7.parse(&input) + } catch let e7 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4, e5, e6, e7], at: input + ) + } + } + } + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7 + ) -> OneOfBuilder.OneOf8 { + OneOfBuilder.OneOf8(p0, p1, p2, p3, p4, p5, p6, p7) + } +} + +extension OneOfBuilder { + public struct OneOf9< + P0: Parser, P1: Parser, P2: Parser, P3: Parser, P4: Parser, P5: Parser, P6: Parser, P7: Parser, + P8: Parser + >: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P5.Input == P6.Input, + P6.Input == P7.Input, + P7.Input == P8.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output, + P4.Output == P5.Output, + P5.Output == P6.Output, + P6.Output == P7.Output, + P7.Output == P8.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8 + + @inlinable public init( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7, _ p8: P8 + ) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + self.p6 = p6 + self.p7 = p7 + self.p8 = p8 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + do { + input = original + return try self.p5.parse(&input) + } catch let e5 { + do { + input = original + return try self.p6.parse(&input) + } catch let e6 { + do { + input = original + return try self.p7.parse(&input) + } catch let e7 { + do { + input = original + return try self.p8.parse(&input) + } catch let e8 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4, e5, e6, e7, e8], at: input + ) + } + } + } + } + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7, _ p8: P8 + ) -> OneOfBuilder.OneOf9 { + OneOfBuilder.OneOf9(p0, p1, p2, p3, p4, p5, p6, p7, p8) + } +} + +extension OneOfBuilder { + public struct OneOf10< + P0: Parser, P1: Parser, P2: Parser, P3: Parser, P4: Parser, P5: Parser, P6: Parser, P7: Parser, + P8: Parser, P9: Parser + >: Parser + where + P0.Input == P1.Input, + P1.Input == P2.Input, + P2.Input == P3.Input, + P3.Input == P4.Input, + P4.Input == P5.Input, + P5.Input == P6.Input, + P6.Input == P7.Input, + P7.Input == P8.Input, + P8.Input == P9.Input, + P0.Output == P1.Output, + P1.Output == P2.Output, + P2.Output == P3.Output, + P3.Output == P4.Output, + P4.Output == P5.Output, + P5.Output == P6.Output, + P6.Output == P7.Output, + P7.Output == P8.Output, + P8.Output == P9.Output + { + public let p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9 + + @inlinable public init( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7, _ p8: P8, + _ p9: P9 + ) { + self.p0 = p0 + self.p1 = p1 + self.p2 = p2 + self.p3 = p3 + self.p4 = p4 + self.p5 = p5 + self.p6 = p6 + self.p7 = p7 + self.p8 = p8 + self.p9 = p9 + } + + @inlinable public func parse(_ input: inout P0.Input) rethrows -> P0.Output { + let original = input + do { return try self.p0.parse(&input) } catch let e0 { + do { + input = original + return try self.p1.parse(&input) + } catch let e1 { + do { + input = original + return try self.p2.parse(&input) + } catch let e2 { + do { + input = original + return try self.p3.parse(&input) + } catch let e3 { + do { + input = original + return try self.p4.parse(&input) + } catch let e4 { + do { + input = original + return try self.p5.parse(&input) + } catch let e5 { + do { + input = original + return try self.p6.parse(&input) + } catch let e6 { + do { + input = original + return try self.p7.parse(&input) + } catch let e7 { + do { + input = original + return try self.p8.parse(&input) + } catch let e8 { + do { + input = original + return try self.p9.parse(&input) + } catch let e9 { + throw ParsingError.manyFailed( + [e0, e1, e2, e3, e4, e5, e6, e7, e8, e9], at: input + ) + } + } + } + } + } + } + } + } + } + } + } + } +} + +extension OneOfBuilder { + @inlinable public static func buildBlock( + _ p0: P0, _ p1: P1, _ p2: P2, _ p3: P3, _ p4: P4, _ p5: P5, _ p6: P6, _ p7: P7, _ p8: P8, + _ p9: P9 + ) -> OneOfBuilder.OneOf10 { + OneOfBuilder.OneOf10(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9) + } +} + +// END AUTO-GENERATED CONTENT diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Backtracking.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Backtracking.md new file mode 100644 index 0000000000..a1dc02e5d7 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Backtracking.md @@ -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. diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Design.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Design.md new file mode 100644 index 0000000000..4b5a80e785 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Design.md @@ -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 +``` + +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, 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( + _ transform: @escaping (Output) -> NewOutput + ) -> Parsers.Map { + .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: 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. diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/ErrorMessages.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/ErrorMessages.md new file mode 100644 index 0000000000..15733c3146 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/ErrorMessages.md @@ -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 +// | ^ +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/GettingStarted.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/GettingStarted.md new file mode 100644 index 0000000000..f98f41fbb7 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/GettingStarted.md @@ -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: "", + 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 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 diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Bool.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Bool.md new file mode 100644 index 0000000000..c82b31103e --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Bool.md @@ -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 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 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") +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CaseIterable.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CaseIterable.md new file mode 100644 index 0000000000..120687a49b --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CaseIterable.md @@ -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 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) diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CharacterSet.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CharacterSet.md new file mode 100644 index 0000000000..f271a5e496 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/CharacterSet.md @@ -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") +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Float.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Float.md new file mode 100644 index 0000000000..b4a7d1422c --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Float.md @@ -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 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 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) +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Int.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Int.md new file mode 100644 index 0000000000..d462d43684 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/Int.md @@ -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 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 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) +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/String.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/String.md new file mode 100644 index 0000000000..2d7324c23a --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/String.md @@ -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 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 for more info. diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/UUID.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/UUID.md new file mode 100644 index 0000000000..cad158c388 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/Parsers/UUID.md @@ -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) +``` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/StringAbstractions.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/StringAbstractions.md new file mode 100644 index 0000000000..8ba2c45550 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Articles/StringAbstractions.md @@ -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`, 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. diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/OneOf.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/OneOf.md new file mode 100644 index 0000000000..5277095132 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/OneOf.md @@ -0,0 +1,7 @@ +# ``Parsing/OneOf`` + +## Topics + +### Builder + +- ``OneOfBuilder`` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parse.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parse.md new file mode 100644 index 0000000000..7ab44618b1 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parse.md @@ -0,0 +1,7 @@ +# ``Parsing/Parse`` + +## Topics + +### Builder + +- ``ParserBuilder`` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parser.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parser.md new file mode 100644 index 0000000000..5d26068fca --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Extensions/Parser.md @@ -0,0 +1,68 @@ +# ``Parsing/Parser`` + +## Topics + +### Diving deeper + +* +* +* +* +* + +### 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. + +- +- +- +- +- +- +- +- ``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(_:)`` diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Parsing.md b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Parsing.md new file mode 100644 index 0000000000..86c104bed2 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Documentation.docc/Parsing.md @@ -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 + +* +* +* +* +* + +## 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) diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Breakpoint.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Breakpoint.swift new file mode 100644 index 0000000000..f4159e4070 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Breakpoint.swift @@ -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.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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Deprecations.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Deprecations.swift new file mode 100644 index 0000000000..33ec7a80c6 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Internal/Deprecations.swift @@ -0,0 +1,4 @@ +// NB: Deprecated after 0.6.0 + +@available(*, deprecated, renamed: "Parsers.Conditional") +public typealias Conditional = Parsers.Conditional diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parser.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parser.swift new file mode 100644 index 0000000000..f97da8b7e0 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parser.swift @@ -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(_ input: C) rethrows -> Output + where Input == C.SubSequence { + try Parse { + self + End() + }.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(_ input: S) rethrows -> Output + where Input == S.SubSequence.UTF8View { + try Parse { + self + End() + }.parse(input[...].utf8) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Always.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Always.swift new file mode 100644 index 0000000000..1ad8727a48 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Always.swift @@ -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` into each omitted parser. As a simplified +/// example: +/// +/// ```swift +/// struct Many: Parser +/// where Separator.Input == Element.Input, Terminator.Input == Element.Input { +/// ... +/// } +/// +/// extension Many where Separator == Always, Terminator == Always { +/// 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: 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( + _ transform: @escaping (Output) -> NewOutput + ) -> Always { + .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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/AnyParser.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/AnyParser.swift new file mode 100644 index 0000000000..a1804d8dc8 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/AnyParser.swift @@ -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 { + .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: 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(_ 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Bool.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Bool.swift new file mode 100644 index 0000000000..a3897bbbb2 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Bool.swift @@ -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 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( + of inputType: Input.Type = Input.self + ) -> Parsers.BoolParser { + .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 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 { + .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 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> { + .init { Parsers.BoolParser() } + } +} + +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 for more information about this parser. + public struct BoolParser: 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CaseIterableRawRepresentable.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CaseIterableRawRepresentable.swift new file mode 100644 index 0000000000..1c1eec3165 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CaseIterableRawRepresentable.swift @@ -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 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 { + .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 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 { + .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( + of inputType: Input.Type = Input.self + ) -> Parsers.CaseIterableRawRepresentableParser + 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 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 { + .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 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 { + .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( + of inputType: Input.Type = Input.self + ) -> Parsers.CaseIterableRawRepresentableParser + 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CharacterSet.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CharacterSet.swift new file mode 100644 index 0000000000..26e4a5cff0 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CharacterSet.swift @@ -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) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CompactMap.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CompactMap.swift new file mode 100644 index 0000000000..430682bb83 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/CompactMap.swift @@ -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( + _ transform: @escaping (Output) -> NewOutput? + ) -> Parsers.CompactMap { + .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: 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 + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Conditional.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Conditional.swift new file mode 100644 index 0000000000..2eb505037d --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Conditional.swift @@ -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: 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) + } + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/End.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/End.swift new file mode 100644 index 0000000000..6c3c051231 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/End.swift @@ -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: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Fail.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Fail.swift new file mode 100644 index 0000000000..b661a22b3a --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Fail.swift @@ -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(throwing: OddFailure()) +/// } +/// } +/// +/// try evens.parse("42") // 42 +/// +/// try evens.parse("123") +/// // error: OddFailure() +/// // --> input:1:1-3 +/// // 1 | 123 +/// // | ^^^ +/// ``` +public struct Fail: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Filter.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Filter.swift new file mode 100644 index 0000000000..3be532821b --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Filter.swift @@ -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 { + .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: 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 + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/First.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/First.swift new file mode 100644 index 0000000000..34713e73fb --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/First.swift @@ -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: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FlatMap.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FlatMap.swift new file mode 100644 index 0000000000..c64ef1480b --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FlatMap.swift @@ -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( + @ParserBuilder _ transform: @escaping (Output) -> NewParser + ) -> Parsers.FlatMap { + .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: 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) + ) + ) + } + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Float.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Float.swift new file mode 100644 index 0000000000..1b475dc7eb --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Float.swift @@ -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 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( + of inputType: Input.Type = Input.self + ) -> Parsers.FloatParser { + .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 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 { + .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 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> { + .init { Parsers.FloatParser() } + } +} + +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 for more information about this parser. + public struct FloatParser: 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[..(_ other: S) -> Bool + where S.Element == Element { + self.elementsEqual(other, by: { $0 == $1 || $0 + 32 == $1 }) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromSubstring.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromSubstring.swift new file mode 100644 index 0000000000..0101fb1018 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromSubstring.swift @@ -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: 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 { + @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 + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUTF8View.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUTF8View.swift new file mode 100644 index 0000000000..e5d05ed571 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUTF8View.swift @@ -0,0 +1,32 @@ +public struct FromUTF8View: 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 } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUnicodeScalarView.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUnicodeScalarView.swift new file mode 100644 index 0000000000..4662d25a66 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/FromUnicodeScalarView.swift @@ -0,0 +1,42 @@ +/// A parser that transforms a parser on `Substring.UnicodeScalarView` into a parser on another +/// view. +public struct FromUnicodeScalarView: 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 { + @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 } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Int.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Int.swift new file mode 100644 index 0000000000..7f93998e1f --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Int.swift @@ -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 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( + of inputType: Input.Type = Input.self, + radix: Int = 10 + ) -> Parsers.IntParser { + .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 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 { + .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 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> { + .init { Parsers.IntParser(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 for more information about this parser. + public struct IntParser: 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 + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Lazy.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Lazy.swift new file mode 100644 index 0000000000..0edb408488 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Lazy.swift @@ -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: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Literal.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Literal.swift new file mode 100644 index 0000000000..311ce06232 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Literal.swift @@ -0,0 +1,39 @@ +extension Array: Parser where Element: Equatable { + @inlinable + public func parse(_ input: inout ArraySlice) 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) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Many.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Many.swift new file mode 100644 index 0000000000..c74ab7b6e7 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Many.swift @@ -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: 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.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, Terminator == Always { + /// 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 { + @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 { + @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, + Terminator == Always +{ + /// 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 { + @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 { + @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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Map.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Map.swift new file mode 100644 index 0000000000..11e136d0bf --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Map.swift @@ -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( + _ transform: @escaping (Output) -> NewOutput + ) -> Parsers.Map { + .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: 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)) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Newline.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Newline.swift new file mode 100644 index 0000000000..79dc955625 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Newline.swift @@ -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: 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, Bytes == Input { + @_disfavoredOverload + @inlinable + public init() { self.init() } +} + +extension Parsers { + public typealias Newline = SwiftTL.Newline // NB: Convenience type alias for discovery +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Not.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Not.swift new file mode 100644 index 0000000000..f1d3b04b61 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Not.swift @@ -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: 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) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOf.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOf.swift new file mode 100644 index 0000000000..e25f631df0 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOf.swift @@ -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 . +public struct OneOf: 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) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOfMany.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOfMany.swift new file mode 100644 index 0000000000..1f7a072bb2 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/OneOfMany.swift @@ -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: 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optional.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optional.swift new file mode 100644 index 0000000000..4a57102a48 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optional.swift @@ -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: 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optionally.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optionally.swift new file mode 100644 index 0000000000..5cc3b13ebc --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Optionally.swift @@ -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 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: 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 + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parse.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parse.swift new file mode 100644 index 0000000000..7bb0ad6a87 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parse.swift @@ -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: Parser { + public let parsers: Parsers + + @inlinable + public init(@ParserBuilder _ build: () -> Parsers) { + self.parsers = build() + } + + @inlinable + public init( + _ transform: @escaping (Upstream.Output) -> NewOutput, + @ParserBuilder with build: () -> Upstream + ) where Parsers == SwiftTL.Parsers.Map { + self.parsers = build().map(transform) + } + + @inlinable + public init( + _ output: NewOutput, + @ParserBuilder with build: () -> Upstream + ) where Upstream.Output == Void, Parsers == SwiftTL.Parsers.Map { + self.parsers = build().map { output } + } + + @inlinable + public func parse(_ input: inout Parsers.Input) rethrows -> Parsers.Output { + try self.parsers.parse(&input) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parsers.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parsers.swift new file mode 100644 index 0000000000..bde927063a --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Parsers.swift @@ -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 {} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Peek.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Peek.swift new file mode 100644 index 0000000000..cb6504ad22 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Peek.swift @@ -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: 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 + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Pipe.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Pipe.swift new file mode 100644 index 0000000000..6c0b0107cf --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Pipe.swift @@ -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( + @ParserBuilder _ build: () -> Downstream + ) -> Parsers.Pipe { + .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: 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) + ) + ) + } + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Prefix.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Prefix.swift new file mode 100644 index 0000000000..ad3460b28e --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Prefix.swift @@ -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: 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, + 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, + 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, + 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, + 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, + while predicate: ((Input.Element) -> Bool)? = nil + ) { + self.init(length, while: predicate) + } + + @_disfavoredOverload + @inlinable + public init( + _ length: PartialRangeThrough, + 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, + 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, + while predicate: ((Input.Element) -> Bool)? = nil + ) { + self.init(length, while: predicate) + } + + @_disfavoredOverload + @inlinable + public init( + _ length: PartialRangeThrough, + while predicate: ((Input.Element) -> Bool)? = nil + ) { + self.init(length, while: predicate) + } +} + +extension Parsers { + public typealias Prefix = SwiftTL.Prefix // NB: Convenience type alias for discovery +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/PrefixThrough.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/PrefixThrough.swift new file mode 100644 index 0000000000..e5261a9049 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/PrefixThrough.swift @@ -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: 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[..: 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[..( + _ keyPath: WritableKeyPath + ) -> Parsers.Pullback { + .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: Parser { + public let downstream: Downstream + public let keyPath: WritableKeyPath + + @inlinable + public init(downstream: Downstream, keyPath: WritableKeyPath) { + 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( + _ keyPath: WritableKeyPath + ) -> Pullback { + .init(downstream: self.downstream, keyPath: keyPath.appending(path: self.keyPath)) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/ReplaceError.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/ReplaceError.swift new file mode 100644 index 0000000000..fcb8c09bec --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/ReplaceError.swift @@ -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 { + .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: 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 + } + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Rest.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Rest.swift new file mode 100644 index 0000000000..e3e67bcc93 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Rest.swift @@ -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: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Skip.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Skip.swift new file mode 100644 index 0000000000..a7296eb69d --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Skip.swift @@ -0,0 +1,19 @@ +/// A parser that discards the output of another parser. +public struct Skip: 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/StartsWith.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/StartsWith.swift new file mode 100644 index 0000000000..edfae3a3c1 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/StartsWith.swift @@ -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: Parser where Input.SubSequence == Input { + public let count: Int + public let possiblePrefix: AnyCollection + 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, + 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) + where + PossiblePrefix: Collection, + PossiblePrefix.Element == Input.Element + { + self.init(possiblePrefix, by: ==) + } +} + +extension Parsers { + public typealias StartsWith = SwiftTL.StartsWith // NB: Convenience type alias for discovery +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Stream.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Stream.swift new file mode 100644 index 0000000000..520b820287 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Stream.swift @@ -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.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.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: 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) 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 +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/UUID.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/UUID.swift new file mode 100644 index 0000000000..bebe31938e --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/UUID.swift @@ -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( + of inputType: Input.Type = Input.self + ) -> Parsers.UUIDParser { + .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 { + .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> { + .init { Parsers.UUIDParser() } + } +} + +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: Parser + where + Input.SubSequence == Input, + Input.Element == UTF8.CodeUnit + { + @inlinable + public init() {} + + @inlinable + public func parse(_ input: inout Input) throws -> UUID { + func parseHelp(_ 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) + } + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Whitespace.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Whitespace.swift new file mode 100644 index 0000000000..75617735ab --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/Parsers/Whitespace.swift @@ -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: 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, Bytes == Input { + @_disfavoredOverload + @inlinable + public init() { self.init() } +}*/ + +extension Parsers { + public typealias Whitespace = SwiftTL.Whitespace // NB: Convenience type alias for discovery +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Parser/ParsingError.swift b/build-system/SwiftTL/Sources/SwiftTL/Parser/ParsingError.swift new file mode 100644 index 0000000000..874d9e4928 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/Parser/ParsingError.swift @@ -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) -> 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(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 as Slice<[Substring]>): + let slice = + originalInput.startIndex == remainingInput.startIndex + ? originalInput + : Slice( + base: originalInput.base, + bounds: originalInput.startIndex.. 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 +) -> 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(.. \(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..: + return input.endIndex == input.base.endIndex ? input[.. 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 }) + } +} diff --git a/build-system/SwiftTL/Sources/SwiftTL/main.swift b/build-system/SwiftTL/Sources/SwiftTL/main.swift new file mode 100644 index 0000000000..30aa736667 --- /dev/null +++ b/build-system/SwiftTL/Sources/SwiftTL/main.swift @@ -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)") +}