Swift Regex vs NSRegularExpression: behavior atlas
A dot matched the whole e plus combining acute accent in Swift Regex, but NSRegularExpression returned two matches on the same Mac. That is the kind of difference I want to check before moving a pattern between APIs.
This atlas keeps 34 cases with their exact inputs, operations, options, captures, and UTF-16 ranges. Some cases agree. A shared result for one input does not establish engine parity.
Tested on one Mac
Dataset 1.0.0, schema 1, recorded October 16, 2026. The accepted run checked 68 engine observations across 34 cases.
| Component | Executed environment |
|---|---|
| macOS | 27.0, build 26A428 |
| Architecture | arm64 |
| Swift compiler | Apple Swift 6.4, swiftlang-6.4.0.34.1, clang-2100.3.34.1 |
| Xcode | 27.0, build 27A266a |
| macOS SDK | 27.0, build 26A425 |
| Libraries | Dynamically linked macOS system Swift and Foundation |
| Compilation | arm64-apple-macosx14.2, default -Onone |
Only this OS/build, architecture, and toolchain were executed. The macOS 14.2 compilation target is not a test on macOS 14.2. These cases do not measure speed or establish results on another macOS version, Intel, Linux, Wasm, JavaScript, or PCRE.
Read the results
match includes zero-length matches. noMatch means compilation succeeded and the operation returned no match. compileError means the pattern was rejected before matching. runtimeError keeps the error channel of a throwing operation separate; none of the 34 accepted string-pattern cases produced an engine runtime error.
Capture 0 is the full match. An absent optional capture has null text and range. A participating empty capture has "" text and a zero-length range. Ranges below use [offset, length] in UTF-16 code units. They do not count Swift Character values. Returned text preserves the input’s scalars.
matching is a source-reviewed mapping to Regex 2.5.0’s search inputs and i/s/m controls, not an app UI test. engineOnly marks standalone first-match, whole-input, scalar-mode, replacement, and empty-pattern probes. Counts are engine matches; the app’s selected-capture count follows participating ranges.
Compare the cases
Open a case for its copyable Swift string values, exact APIs, requested options, captures, and primary sources. The summary lists outcomes on the recorded environment only.
Inspect year and month capturesnamed-date-captures
Group 0 is the full match; numbered groups retain the named year and month text.
let pattern = "(?<year>\\d{4})-(?<month>\\d{2})"
let input = "on 2026-10"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"2026-10",[3,7]; group 1:"2026",[3,4]; group 2:"10",[8,2].
Foundation: match (1 match).
- Match 1 — group 0:
"2026-10",[3,7]; group 1:"2026",[3,4]; group 2:"10",[8,2].
An optional capture can be absentoptional-absent-capture
The first match has no group 1 range. Selecting group 1 in the app shows one participating range across these two matches.
let pattern = "(a)?b"
let input = "b ab"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (2 matches).
- Match 1 — group 0:
"b",[0,1]; group 1:null,null. - Match 2 — group 0:
"ab",[2,2]; group 1:"a",[2,1].
Foundation: match (2 matches).
- Match 1 — group 0:
"b",[0,1]; group 1:null,null. - Match 2 — group 0:
"ab",[2,2]; group 1:"a",[2,1].
An empty capture still has a rangeempty-participating-capture
Group 1 participates with zero length, which differs from an absent optional capture.
let pattern = "(a*)b"
let input = "b"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"b",[0,1]; group 1:"",[0,0].
Foundation: match (1 match).
- Match 1 — group 0:
"b",[0,1]; group 1:"",[0,0].
A repeated group retains its final capturerepeated-capture
The overall match contains three letters; group 1 refers to the final letter.
let pattern = "(a)+"
let input = "aaa"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"aaa",[0,3]; group 1:"a",[2,1].
Foundation: match (1 match).
- Match 1 — group 0:
"aaa",[0,3]; group 1:"a",[2,1].
A noncapturing group adds no capture slotnoncapturing-prefix
Only the trailing c becomes group 1.
let pattern = "(?:ab)(c)"
let input = "abc"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"abc",[0,3]; group 1:"c",[2,1].
Foundation: match (1 match).
- Match 1 — group 0:
"abc",[0,3]; group 1:"c",[2,1].
Search accepts the first successful alternativeordered-alternation
A search for cat or cater accepts cat here. Whole-input matching has a separate fixture.
let pattern = "(cat|cater)"
let input = "cater"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
Foundation: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
A backreference repeats captured textnumeric-backreference
Group 1 supplies the text required after the hyphen.
let pattern = "(\\w+)-\\1"
let input = "go-go"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"go-go",[0,5]; group 1:"go",[0,2].
Foundation: match (1 match).
- Match 1 — group 0:
"go-go",[0,5]; group 1:"go",[0,2].
Dot counts graphemes in Swift by defaultdot-decomposed-accent
Swift consumes e plus combining acute together. Foundation returns two scalar matches; both report UTF-16 ranges.
let pattern = "."
let input = "e\u{301}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"e\u{301}",[0,2].
Foundation: match (2 matches).
- Match 1 — group 0:
"e",[0,1]. - Match 2 — group 0:
"\u{301}",[1,1].
A flag emoji contains two regional indicatorsdot-flag
Swift returns one flag match. Foundation dot returns two regional-indicator matches, each spanning two UTF-16 units.
let pattern = "."
let input = "\u{1f1f8}\u{1f1ec}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"\u{1f1f8}\u{1f1ec}",[0,4].
Foundation: match (2 matches).
- Match 1 — group 0:
"\u{1f1f8}",[0,2]. - Match 2 — group 0:
"\u{1f1ec}",[2,2].
A composed pattern matches decomposed text in Swiftcanonical-equivalence
The returned text preserves the input scalars. Foundation does not match this composed pattern against decomposed input.
let pattern = "\u{e9}"
let input = "e\u{301}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"e\u{301}",[0,2].
Foundation: noMatch.
Swift scalar mode changes dot consumptionscalar-dot
Swift matchingSemantics(.unicodeScalar) returns separate e and accent matches, agreeing with Foundation for this input. The app has no scalar-mode control.
let pattern = "."
let input = "e\u{301}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
unicodeScalar- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (2 matches).
- Match 1 — group 0:
"e",[0,1]. - Match 2 — group 0:
"\u{301}",[1,1].
Foundation: match (2 matches).
- Match 1 — group 0:
"e",[0,1]. - Match 2 — group 0:
"\u{301}",[1,1].
A CRLF pair can be one character-class membercrlf-character-class
This pattern spells CR then LF within the class. The observation applies to this pattern and runtime; compare the newline-sequence fixture.
let pattern = "[\r\n]"
let input = "\r\n"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"\r\n",[0,2].
Foundation: match (2 matches).
- Match 1 — group 0:
"\r",[0,1]. - Match 2 — group 0:
"\n",[1,1].
Backslash R preserves a CRLF pairnewline-sequences
Both APIs find two newline sequences and keep the CRLF pair together.
let pattern = "\\R"
let input = "a\r\nb\n"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (2 matches).
- Match 1 — group 0:
"\r\n",[1,2]. - Match 2 — group 0:
"\n",[4,1].
Foundation: match (2 matches).
- Match 1 — group 0:
"\r\n",[1,2]. - Match 2 — group 0:
"\n",[4,1].
Backslash d accepts Arabic-Indic digitsunicode-decimal-digits
Both APIs accept the Arabic-Indic and ASCII digit runs.
let pattern = "\\d+"
let input = "\u{663}\u{664} 42"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (2 matches).
- Match 1 — group 0:
"\u{663}\u{664}",[0,2]. - Match 2 — group 0:
"42",[3,2].
Foundation: match (2 matches).
- Match 1 — group 0:
"\u{663}\u{664}",[0,2]. - Match 2 — group 0:
"42",[3,2].
An ASCII range excludes Arabic-Indic digitsascii-digit-contract
Use the ASCII range when the format requires ASCII digits; searching still extracts the later ASCII run.
let pattern = "[0-9]+"
let input = "\u{663}\u{664} 42"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"42",[3,2].
Foundation: match (1 match).
- Match 1 — group 0:
"42",[3,2].
Backslash w includes an accented letterunicode-word
Both APIs match café as one word run on this runtime.
let pattern = "\\w+"
let input = "caf\u{e9}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"caf\u{e9}",[0,4].
Foundation: match (1 match).
- Match 1 — group 0:
"caf\u{e9}",[0,4].
Case-insensitive matching includes accented lettersunicode-ignore-case
The option matches CAFÉ while preserving the input text in the capture.
let pattern = "caf\u{e9}"
let input = "CAF\u{c9}"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
caseInsensitive- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"CAF\u{c9}",[0,4].
Foundation: match (1 match).
- Match 1 — group 0:
"CAF\u{c9}",[0,4].
Dot excludes a line feed by defaultdot-excludes-newline
Neither API matches a newline with dot under the default options.
let pattern = "a.b"
let input = "a\nb"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: noMatch.
Foundation: noMatch.
Dot-all permits a line feeddot-all
The Swift dotMatchesNewlines and Foundation dotMatchesLineSeparators options both permit this match.
let pattern = "a.b"
let input = "a\nb"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
dotAll- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"a\nb",[0,3].
Foundation: match (1 match).
- Match 1 — group 0:
"a\nb",[0,3].
Multiline anchors can select an interior linemultiline-anchors
The multiline option changes the anchors, allowing the middle b line to match.
let pattern = "^b$"
let input = "a\nb\nc"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
multiline- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- unicode, foundation
Swift: match (1 match).
- Match 1 — group 0:
"b",[2,1].
Foundation: match (1 match).
- Match 1 — group 0:
"b",[2,1].
Finding digits does not validate the whole inputwhole-token-rejects-extraction
Whole-input matching rejects a digit run with a preceding ID label. Foundation uses a noncapturing wrapper with absolute start/end anchors, recorded in apis.
let pattern = "\\d+"
let input = "ID:42"
- Operation and APIs
whole. Swift:Regex.wholeMatch(in:). Foundation:NSRegularExpression.matches with \A(?:pattern)\z.- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: noMatch.
Foundation: noMatch.
Whole-input matching can try a longer alternativewhole-token-backtracks-alternative
Both whole-input operations select ab, even though a first search could accept a.
let pattern = "(a|ab)"
let input = "ab"
- Operation and APIs
whole. Swift:Regex.wholeMatch(in:). Foundation:NSRegularExpression.matches with \A(?:pattern)\z.- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: match (1 match).
- Match 1 — group 0:
"ab",[0,2]; group 1:"ab",[0,2].
Foundation: match (1 match).
- Match 1 — group 0:
"ab",[0,2]; group 1:"ab",[0,2].
First-match search extracts a substringfirst-extracts-digits
Both first-match APIs return 42 and leave the surrounding text outside the match.
let pattern = "\\d+"
let input = "ID:42,7"
- Operation and APIs
first. Swift:Regex.firstMatch(in:). Foundation:NSRegularExpression.firstMatch(in:range:).- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: match (1 match).
- Match 1 — group 0:
"42",[3,2].
Foundation: match (1 match).
- Match 1 — group 0:
"42",[3,2].
Lookahead checks following context without consuming itlookahead-context
The match contains the amount; the following space and USD remain outside its range.
let pattern = "\\d+(?= USD)"
let input = "42 USD"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"42",[0,2].
Foundation: match (1 match).
- Match 1 — group 0:
"42",[0,2].
Test this lookbehind against the recorded runtimelookbehind-local-support
On the recorded environment Swift rejects this pattern during compilation; Foundation matches 42. Retest this pattern when using another Swift version.
let pattern = "(?<=\\$)\\d+"
let input = "cost $42"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- swift-type, foundation
Swift: compileError.
Recorded diagnostic: lookbehind is not currently supported.
Foundation: match (1 match).
- Match 1 — group 0:
"42",[6,2].
Capture the amount after a dollar signcapture-context-alternative
Both APIs match the dollar sign and amount, with only the amount in group 1. In the app, choose capture group 1 to inspect the amount.
let pattern = "\\$(\\d+)"
let input = "cost $42"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (1 match).
- Match 1 — group 0:
"$42",[5,3]; group 1:"42",[6,2].
Foundation: match (1 match).
- Match 1 — group 0:
"$42",[5,3]; group 1:"42",[6,2].
An unclosed group is a compilation errormalformed-group
Neither API reaches matching. A compilation error must not become an empty successful match list.
let pattern = "("
let input = "abc"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: compileError.
Recorded diagnostic: expected ')'.
Foundation: compileError.
Recorded diagnostic: Error Domain=NSCocoaErrorDomain Code=2048 "The value “(” is invalid." UserInfo={NSInvalidValue=(}.
A nonexistent capture reference is rejectedinvalid-backreference
Both APIs reject the reference to group 2 in this pattern on the recorded environment.
let pattern = "(a)\\2"
let input = "aa"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- swift-type, foundation
Swift: compileError.
Recorded diagnostic: no capture numbered 2.
Foundation: compileError.
Recorded diagnostic: Error Domain=NSCocoaErrorDomain Code=2048 "The value “(a)\2” is invalid." UserInfo={NSInvalidValue=(a)\2}.
A possessive quantifier does not give a letter backpossessive-quantifier
The possessive repetition consumes the available letters, so the final a cannot match.
let pattern = "a*+a"
let input = "aaa"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: noMatch.
Foundation: noMatch.
Replace with a literal dollar expressionliteral-replacement
Swift inserts $1 literally. Foundation receives escapedTemplate(for:) so its replacement is also literal. This does not call the app replacement parser.
let pattern = "(\\w+)"
let input = "cat"
let replacement = "$1"
- Operation and APIs
replaceLiteral. Swift:String.replacing(_:with:) literal. Foundation:NSRegularExpression.stringByReplacingMatches + escapedTemplate.- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
Replacement output: "$1".
Foundation: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
Replacement output: "$1".
The same replacement string has different API meaningsreplacement-api-syntax
Swift replacing with a string inserts $1 literally; Foundation withTemplate expands group 1. This compares replacement APIs, not different regex matching results or the app parser.
let pattern = "(\\w+)"
let input = "cat"
let replacement = "$1"
- Operation and APIs
replaceAPISyntax. Swift:String.replacing(_:with:) literal. Foundation:NSRegularExpression.stringByReplacingMatches template.- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
Replacement output: "$1".
Foundation: match (1 match).
- Match 1 — group 0:
"cat",[0,3]; group 1:"cat",[0,3].
Replacement output: "cat".
Build a replacement from captured fieldscapture-replacement
The Swift closure joins capture 2, a space, and capture 1. Foundation uses $2 $1. The fixture names both recipes; the app has its own template parser.
let pattern = "(\\w+), (\\w+)"
let input = "Lovelace, Ada"
- Operation and APIs
replaceCaptures. Swift:String.replacing(_:with:) closure: capture 2 + space + capture 1. Foundation:NSRegularExpression.stringByReplacingMatches template: $2 $1.- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- algorithms, foundation
Swift: match (1 match).
- Match 1 — group 0:
"Lovelace, Ada",[0,13]; group 1:"Lovelace",[0,8]; group 2:"Ada",[10,3].
Replacement output: "Ada Lovelace".
Foundation: match (1 match).
- Match 1 — group 0:
"Lovelace, Ada",[0,13]; group 1:"Lovelace",[0,8]; group 2:"Ada",[10,3].
Replacement output: "Ada Lovelace".
A zero-width match has a participating empty rangezero-width-lookahead
Both APIs return two empty group 0 ranges before the letters. No characters are consumed by the lookahead.
let pattern = "(?=a)"
let input = "aa"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
matching— source-reviewed match-extraction inputs; no app UI test.- Version label
- Recorded environment only; no cross-version test.
- Primary source IDs
- swift-type, foundation
Swift: match (2 matches).
- Match 1 — group 0:
"",[0,0]. - Match 2 — group 0:
"",[1,0].
Foundation: match (2 matches).
- Match 1 — group 0:
"",[0,0]. - Match 2 — group 0:
"",[1,0].
An empty engine pattern can match boundariesempty-engine-pattern
Swift matches three boundaries on this runtime; Foundation rejects the empty pattern during compilation. The app handles an empty pattern as an empty workspace state before compiling it.
let pattern = ""
let input = "ab"
- Operation and APIs
all. Swift:String.matches(of:). Foundation:NSRegularExpression.matches(in:range:).- Requested options
- None; engine defaults.
- App scope
engineOnly— standalone API case; no app recipe.- Version label
- Version-sensitive fixture; observation limited to the recorded environment.
- Primary source IDs
- swift-type, foundation
Swift: match (3 matches).
- Match 1 — group 0:
"",[0,0]. - Match 2 — group 0:
"",[1,0]. - Match 3 — group 0:
"",[2,0].
Foundation: compileError.
Recorded diagnostic: Error Domain=NSCocoaErrorDomain Code=2048 "The value “” is invalid." UserInfo={NSInvalidValue=}.
Swift Regex lookbehind on this runtime
For (?<=\$)\d+ against cost $42, the recorded Swift runtime returned compileError with the diagnostic lookbehind is not currently supported. Foundation matched 42 at UTF-16 range [6, 2]. This is the dated lookbehind-local-support case; retest the pattern on another installed runtime.
SE-0448 describes the lookbehind proposal. Proposal status and implementation on a particular runtime answer different questions.
For the amount after a dollar sign, I can consume the dollar sign and capture the digits:
let input = "cost $42"
let regex = try Regex(#"\$(\d+)"#)
for match in input.matches(of: regex) {
print(match.output[1].substring ?? "")
}
The accepted capture-context-alternative case returns $42 as capture 0 and 42 as capture 1 in both APIs. Selecting capture 1 gives the amount, but the full match still consumes the dollar sign. This changes the match range and is not a general replacement for lookbehind.
Searching and validating use different operations
The first-extracts-digits case finds 42 in ID:42,7. The whole-token-rejects-extraction case rejects ID:42 as a whole-input match for \d+.
Swift’s whole-input cases use throwing Regex.wholeMatch(in:). Foundation uses the pattern wrapper \A(?:pattern)\z, with its absolute start and end anchors. That wrapper allows alternation to backtrack and consume the whole input. For (a|ab), both whole-input operations select ab in whole-token-backtracks-alternative. Checking the length of a first search match would answer a different question.
Replacement strings belong to their API
With (\w+) against cat, both APIs capture the same text. Passing $1 to Swift’s string replacement inserts the literal $1; passing $1 to Foundation’s template API inserts cat. See replacement-api-syntax.
For literal replacement, the accepted Foundation recipe uses NSRegularExpression.escapedTemplate(for:). For capture-based replacement, the Swift recipe uses a closure that joins capture 2, a space, and capture 1; Foundation uses $2 $1. Both produce Ada Lovelace from Lovelace, Ada in capture-replacement.
These are standalone API recipes. Regex for macOS has its own replacement parser, which expands $0–$8 and $$. A result from Swift’s literal replacement API does not describe that parser.
Reproduce a case
The cases come from the Regex-owned Tools/regex-atlas/ source at revision 71402a7. Fixtures are in fixtures.v1.json. Accepted output in data/macos-27.0-swift-6.4.v1.json records the environment, input fingerprints, exact APIs, observations, and checks. Stable case IDs connect the fixture and output records.
With that source checkout, Bun, and an installed Xcode command-line toolchain, run from the repository root:
bun Tools/regex-atlas/run.ts \
--recorded-on 2026-10-16 \
--case dot-decomposed-accent \
--output /tmp/swift-regex-atlas-accent
Use a fresh, empty output directory. Replace the case ID to run another example, or omit --case for the corpus. atlas.json contains the report. Exit 0 means the selected expectations passed, 1 means a semantic mismatch, and 2 means an input, build, or execution problem. A later run needs its own recording date and separate output; it does not silently update the accepted expectations.
The two string declarations in each case expose the exact pattern and input without relying on visible Unicode rendering. \u{301} is a combining acute accent, \r is carriage return, and \n is line feed. Backslashes in a Swift source string are escaped separately from regex syntax. Use each case’s recorded operation and options with the Swift APIs or Foundation API.
Keep the same inputs in Regex
For capture-context-alternative, paste \$(\d+) into Regex’s pattern field and cost $42 into Test Data. Leave all three matching options off. Capture 0 is $42; select capture 1 to inspect 42. Save As a named test, then duplicate it before trying a variant.
Saved Tests keeps the pattern, Test Data, replacement, selected capture group, and matching options together on this Mac. It stores a workspace snapshot. It does not store Atlas expectations or run assertions.
Regex 2.5.0 uses Swift Regex and searches for matches. It does not switch to Foundation, import the Atlas dataset, open a case from a link, expose a scalar-mode control, or perform these standalone whole-input and replacement API probes. The app handles an empty pattern before compilation and exposes capture selection 0–8. The matching labels above describe source-reviewed inputs, not newly executed UI behavior.
Regex 2.5.0 is a one-time US$13.99 purchase in the US storefront and requires macOS 14.2 or later. The app’s minimum OS is separate from the Atlas’s executed environment.
Test your own case on your Mac with Regex.
For the surrounding workflow, see Swift Regex Builder and Regex Tester for macOS. Run the final pattern in its destination engine before using it in JavaScript, grep, sed, nginx, or another tool.
Sources
Case source IDs refer to SE-0350: Regex type overview, SE-0363: Unicode for string processing, SE-0357: Regex-powered string processing algorithms, and the NSRegularExpression reference. The documents describe the APIs and matching model; the dataset records these particular observations.