diff --git a/Sources/_StringProcessing/Regex/DSLTree.swift b/Sources/_StringProcessing/Regex/DSLTree.swift index 8d6a5fbc..5971cd93 100644 --- a/Sources/_StringProcessing/Regex/DSLTree.swift +++ b/Sources/_StringProcessing/Regex/DSLTree.swift @@ -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 { @@ -902,6 +906,10 @@ extension DSLTree { public static var negativeLookahead: Self { .init(ast: .negativeLookahead) } + + internal var isNegativeLookahead: Bool { + self.ast == .negativeLookahead + } } @_spi(RegexBuilder) diff --git a/Tests/RegexTests/CompileTests.swift b/Tests/RegexTests/CompileTests.swift index 7ea38490..d3129130 100644 --- a/Tests/RegexTests/CompileTests.swift +++ b/Tests/RegexTests/CompileTests.swift @@ -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) } } diff --git a/Tests/RegexTests/MatchTests.swift b/Tests/RegexTests/MatchTests.swift index 27302cda..e20beeaf 100644 --- a/Tests/RegexTests/MatchTests.swift +++ b/Tests/RegexTests/MatchTests.swift @@ -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. @@ -1629,6 +1629,14 @@ extension RegexTests { // engines generally enforce that lookbehinds are fixed width firstMatchTest( #"\d{3}(?