diff --git a/Sources/_StringProcessing/Utility/AsciiBitset.swift b/Sources/_StringProcessing/Utility/AsciiBitset.swift index 61a6a6891..67f5f3937 100644 --- a/Sources/_StringProcessing/Utility/AsciiBitset.swift +++ b/Sources/_StringProcessing/Utility/AsciiBitset.swift @@ -46,7 +46,7 @@ extension DSLTree.CustomCharacterClass { setBit(val) if isCaseInsensitive { switch val { - case 64...90: setBit(val + 32) + case 65...90: setBit(val + 32) case 97...122: setBit(val - 32) default: break } diff --git a/Tests/RegexTests/MatchTests.swift b/Tests/RegexTests/MatchTests.swift index d75dbde64..2d9a6b1c2 100644 --- a/Tests/RegexTests/MatchTests.swift +++ b/Tests/RegexTests/MatchTests.swift @@ -1458,6 +1458,29 @@ extension RegexTests { firstMatchTest( "(?i)[X-cD]", input: input, match: input, semanticLevel: semantics) } + + // Boundary tests for case insensitive ASCII matching. + firstMatchTest( + "(?i)[@]", input: "@", match: "@", semanticLevel: semantics) + firstMatchTest( + "(?i)[@]", input: "`", match: nil, semanticLevel: semantics) + firstMatchTest( + "(?i)[`]", input: "`", match: "`", semanticLevel: semantics) + firstMatchTest( + "(?i)[`]", input: "@", match: nil, semanticLevel: semantics) + // A range that spans the "@"/"A" boundary folds "A" but not "@". + firstMatchTest( + "(?i)[?-A]", input: "a", match: "a", semanticLevel: semantics) + firstMatchTest( + "(?i)[?-A]", input: "`", match: nil, semanticLevel: semantics) + // The letter ends of each range still fold. + for (pattern, input) in [ + ("(?i)[A]", "a"), ("(?i)[a]", "A"), + ("(?i)[Z]", "z"), ("(?i)[z]", "Z"), + ] { + firstMatchTest( + pattern, input: input, match: input, semanticLevel: semantics) + } } }