Skip to content

Commit b3ea513

Browse files
natecook1000milseman
authored andcommitted
Allow CustomConsuming types to match w/ zero width (swiftlang#479)
* Allow CustomConsuming types to match w/ zero width We previously asserted if a custom consuming type matches with zero width, but that isn't necessary or good. A custom type can implement a lookaround assertion or act as a tracer. * Rename Processor.advance(to:) to resume(at:) Since the given index doesn’t need to advance, this name is less misleading.
1 parent 6251053 commit b3ea513

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

Sources/_StringProcessing/Engine/Processor.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,15 @@ extension Processor {
153153
return true
154154
}
155155

156-
mutating func advance(to nextIndex: Input.Index) {
157-
assert(nextIndex >= bounds.lowerBound)
158-
assert(nextIndex <= bounds.upperBound)
159-
assert(nextIndex > currentPosition)
160-
currentPosition = nextIndex
156+
/// Continue matching at the specified index.
157+
///
158+
/// - Precondition: `bounds.contains(index) || index == bounds.upperBound`
159+
/// - Precondition: `index >= currentPosition`
160+
mutating func resume(at index: Input.Index) {
161+
assert(index >= bounds.lowerBound)
162+
assert(index <= bounds.upperBound)
163+
assert(index >= currentPosition)
164+
currentPosition = index
161165
}
162166

163167
func doPrint(_ s: String) {
@@ -344,7 +348,7 @@ extension Processor {
344348
signalFailure()
345349
return
346350
}
347-
advance(to: nextIndex)
351+
resume(at: nextIndex)
348352
controller.step()
349353

350354
case .assertBy:
@@ -372,7 +376,7 @@ extension Processor {
372376
return
373377
}
374378
registers[valReg] = val
375-
advance(to: nextIdx)
379+
resume(at: nextIdx)
376380
controller.step()
377381
} catch {
378382
abort(error)

Tests/RegexBuilderTests/RegexDSLTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,42 @@ class RegexDSLTests: XCTestCase {
10151015
XCTAssertEqual(str.wholeMatch(of: parser)?.1, version)
10161016
}
10171017
}
1018+
1019+
func testZeroWidthConsumer() throws {
1020+
struct Trace: CustomConsumingRegexComponent {
1021+
typealias RegexOutput = Void
1022+
var label: String
1023+
init(_ label: String) { self.label = label }
1024+
1025+
static var traceOutput = ""
1026+
1027+
func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: Void)? {
1028+
print("Matching '\(label)'", to: &Self.traceOutput)
1029+
print(input, to: &Self.traceOutput)
1030+
let dist = input.distance(from: input.startIndex, to: index)
1031+
print(String(repeating: " ", count: dist) + "^", to: &Self.traceOutput)
1032+
return (index, ())
1033+
}
1034+
}
1035+
1036+
let regex = Regex {
1037+
OneOrMore(.word)
1038+
Trace("end of key")
1039+
":"
1040+
Trace("start of value")
1041+
OneOrMore(.word)
1042+
}
1043+
XCTAssertNotNil("hello:goodbye".firstMatch(of: regex))
1044+
XCTAssertEqual(Trace.traceOutput, """
1045+
Matching 'end of key'
1046+
hello:goodbye
1047+
^
1048+
Matching 'start of value'
1049+
hello:goodbye
1050+
^
1051+
1052+
""")
1053+
}
10181054
}
10191055

10201056
extension Unicode.Scalar {

0 commit comments

Comments
 (0)