Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Sources/_StringProcessing/Regex/DSLTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,10 @@ extension DSLTree.Node {

// Groups (and other parent nodes) defer to the child.
case .nonCapturingGroup(let kind, let child):
// Don't let a negative lookahead affect this - need to continue to next sibling
if kind.isNegativeLookahead {
return nil
}
options.beginScope()
defer { options.endScope() }
if case .changeMatchingOptions(let sequence) = kind.ast {
Expand Down Expand Up @@ -902,6 +906,10 @@ extension DSLTree {
public static var negativeLookahead: Self {
.init(ast: .negativeLookahead)
}

internal var isNegativeLookahead: Bool {
self.ast == .negativeLookahead
}
}

@_spi(RegexBuilder)
Expand Down
4 changes: 4 additions & 0 deletions Tests/RegexTests/CompileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -589,5 +589,9 @@ extension RegexTests {
try expectCanOnlyMatchAtStart("(foo)?^bar", true) // The initial group must match ""
try expectCanOnlyMatchAtStart("(?:foo)?^bar", true)
try expectCanOnlyMatchAtStart("(foo)+^bar", false) // This can't actually match anywhere

// Test lookahead assertions with anchor
try expectCanOnlyMatchAtStart("(?=^)foo", true)
try expectCanOnlyMatchAtStart("(?!^)foo", false)
}
}
16 changes: 15 additions & 1 deletion Tests/RegexTests/MatchTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func _firstMatch(
) throws -> (String, [String?])? {
var regex = try Regex(regexStr, syntax: syntax).matchingSemantics(semanticLevel)
let result = try regex.firstMatch(in: input)

func validateSubstring(_ substringInput: Substring) throws {
// Sometimes the characters we add to a substring merge with existing
// string members. This messes up cross-validation, so skip the test.
Expand Down Expand Up @@ -1629,6 +1629,14 @@ extension RegexTests {
// engines generally enforce that lookbehinds are fixed width
firstMatchTest(
#"\d{3}(?<!USD\d{3})"#, input: "Price: JYP100", match: "100", xfail: true)

// Assertions inside negative lookahead
firstMatchTest(
#"(?!\b)(With)"#, input: "dispatchWithName", match: "With")
firstMatchTest(
#"(?!^)(With)"#, input: "dispatchWithName", match: "With")
firstMatchTest(
#"(?!\s)^dispatch"#, input: "dispatchWithName", match: "dispatch")
}

func testMatchAnchors() throws {
Expand Down Expand Up @@ -2875,6 +2883,12 @@ extension RegexTests {
("\r\n", "\r\n")
)
}

func testIssue815() throws {
// Original report from https://github.com/swiftlang/swift-experimental-string-processing/issues/815
let matches = "dispatchWithName".matches(of: #/(?!^)(With(?!No)|For|In|At|To)(?=[A-Z])/#)
XCTAssert(matches[0].output == ("With", "With"))
}

func testNSRECompatibility() throws {
// NSRE-compatibility includes scalar matching, so `[\r\n]` should match
Expand Down