diff --git a/Sources/_StringProcessing/ConsumerInterface.swift b/Sources/_StringProcessing/ConsumerInterface.swift index af46b5381..f95665288 100644 --- a/Sources/_StringProcessing/ConsumerInterface.swift +++ b/Sources/_StringProcessing/ConsumerInterface.swift @@ -117,8 +117,15 @@ extension DSLTree.Atom { case .any: // FIXME: Should this be a total ordering? - fatalError( - "unreachable: emitAny() should be called isntead") + if opts.semanticLevel == .graphemeCluster { + return { input, bounds in + input.index(after: bounds.lowerBound) + } + } else { + return consumeScalar { _ in + true + } + } case .assertion: // TODO: We could handle, should this be total? diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index 09f7999f9..d362b2951 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -1216,6 +1216,29 @@ class RegexDSLTests: XCTestCase { } } } + + // rdar://96280236 + func testCharacterClassAnyCrash() { + let regex = Regex { + "{" + Capture { + OneOrMore { + CharacterClass.any.subtracting(.anyOf("}")) + } + } + "}" + } + + func replace(_ template: String) throws -> String { + var b = template + while let result = try regex.firstMatch(in: b) { + b.replaceSubrange(result.range, with: "foo") + } + return b + } + + XCTAssertEqual(try replace("{bar}"), "foo") + } } extension Unicode.Scalar {