diff --git a/Sources/_StringProcessing/Engine/Processor.swift b/Sources/_StringProcessing/Engine/Processor.swift index 406d2cc8c..d6c1306f0 100644 --- a/Sources/_StringProcessing/Engine/Processor.swift +++ b/Sources/_StringProcessing/Engine/Processor.swift @@ -117,11 +117,15 @@ extension Processor { return true } - mutating func advance(to nextIndex: Input.Index) { - assert(nextIndex >= bounds.lowerBound) - assert(nextIndex <= bounds.upperBound) - assert(nextIndex > currentPosition) - currentPosition = nextIndex + /// Continue matching at the specified index. + /// + /// - Precondition: `bounds.contains(index) || index == bounds.upperBound` + /// - Precondition: `index >= currentPosition` + mutating func resume(at index: Input.Index) { + assert(index >= bounds.lowerBound) + assert(index <= bounds.upperBound) + assert(index >= currentPosition) + currentPosition = index } func doPrint(_ s: String) { @@ -358,7 +362,7 @@ extension Processor { signalFailure() return } - advance(to: nextIndex) + resume(at: nextIndex) controller.step() case .assertBy: @@ -386,7 +390,7 @@ extension Processor { return } registers[valReg] = val - advance(to: nextIdx) + resume(at: nextIdx) controller.step() } catch { abort(error) diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index a2ed03dae..0500db993 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -969,6 +969,42 @@ class RegexDSLTests: XCTestCase { XCTAssertEqual(str.wholeMatch(of: parser)?.1, version) } } + + func testZeroWidthConsumer() throws { + struct Trace: CustomConsumingRegexComponent { + typealias RegexOutput = Void + var label: String + init(_ label: String) { self.label = label } + + static var traceOutput = "" + + func consuming(_ input: String, startingAt index: String.Index, in bounds: Range) throws -> (upperBound: String.Index, output: Void)? { + print("Matching '\(label)'", to: &Self.traceOutput) + print(input, to: &Self.traceOutput) + let dist = input.distance(from: input.startIndex, to: index) + print(String(repeating: " ", count: dist) + "^", to: &Self.traceOutput) + return (index, ()) + } + } + + let regex = Regex { + OneOrMore(.word) + Trace("end of key") + ":" + Trace("start of value") + OneOrMore(.word) + } + XCTAssertNotNil("hello:goodbye".firstMatch(of: regex)) + XCTAssertEqual(Trace.traceOutput, """ + Matching 'end of key' + hello:goodbye + ^ + Matching 'start of value' + hello:goodbye + ^ + + """) + } } extension Unicode.Scalar {