diff --git a/Sources/Parsing/ParserPrinters/Indexed.swift b/Sources/Parsing/ParserPrinters/Indexed.swift new file mode 100644 index 0000000000..70f439033d --- /dev/null +++ b/Sources/Parsing/ParserPrinters/Indexed.swift @@ -0,0 +1,31 @@ +extension Parsers { + /// A parser of a collection that returns a base parser's output as well as the index range of the + /// parsed input. + public struct Indexed: Parser + where Base: Parser, Base.Input: Collection { + public let base: Base + + @inlinable + init(_ base: Base) { + self.base = base + } + + @inlinable + public func parse( + _ input: inout Base.Input + ) throws -> (output: Base.Output, indices: Range) { + let startIndex = input.startIndex + let output = try base.parse(&input) + return (output, startIndex.. Parsers.Indexed { + Parsers.Indexed(self) + } +} diff --git a/Tests/ParsingTests/IndexedTests.swift b/Tests/ParsingTests/IndexedTests.swift new file mode 100644 index 0000000000..270dd3d294 --- /dev/null +++ b/Tests/ParsingTests/IndexedTests.swift @@ -0,0 +1,46 @@ +import Parsing +import XCTest + +private struct Output: Equatable { + var username: Substring + var range: Range +} + +final class IndexedTests: XCTestCase { + func testStringHappyPath() throws { + let input = "Hi @BlobJr; please call @BlobSr when you get a chance. Thanks." + let parser: some Parser = Many { + Skip { + PrefixUpTo("@".utf8) + } + + From(.substring) { + Parse { + "@" + + Prefix(while: { $0.isLetter || $0.isNumber }) + } + .indexed() + } + .map(Output.init) + } terminator: { + Rest() + } + let usernames = try parser.parse(input) + XCTAssertEqual( + usernames, + [ + Output( + username: "BlobJr", + range: input + .index(input.startIndex, offsetBy: 3)..