Skip to content

Regex Tester — Swift source

Test regular expressions against any text with all flags (g/i/m/s/u/y). See every match, its index and capture groups, plus the constructed pattern. 100% in-browser.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// regex-tester — Swift port. Match a pattern against text, report every
// match with capture groups and index (NSRegularExpression, ICU engine).
import Foundation

struct RegexMatch {
    let match: String
    let index: Int
    let groups: [String] // "" where a group did not participate
}

enum RegexTester {
    // JS flag letters -> NSRegularExpression.Options. g selects all matches;
    // u is the default for String; y has no equivalent.
    static func compileOptions(_ flags: String) -> NSRegularExpression.Options {
        var o: NSRegularExpression.Options = []
        if flags.contains("i") { o.insert(.caseInsensitive) }
        if flags.contains("m") { o.insert(.anchorsMatchLines) }
        if flags.contains("s") { o.insert(.dotMatchesLineSeparators) }
        return o
    }

    static func runRegex(_ pattern: String, _ flags: String,
                         _ text: String) -> (matches: [RegexMatch], error: String?) {
        guard !pattern.isEmpty else { return ([], nil) }
        let ns = text as NSString
        let full = NSRange(location: 0, length: ns.length)
        do {
            let re = try NSRegularExpression(pattern: pattern, options: compileOptions(flags))
            var found = flags.contains("g")
                ? re.matches(in: text, options: [], range: full)
                : []
            if !flags.contains("g"), let m = re.firstMatch(in: text, options: [], range: full) {
                found = [m]
            }
            let matches = found.prefix(2000).map { m -> RegexMatch in
                var groups: [String] = []
                if m.numberOfRanges > 1 {
                    for g in 1..<m.numberOfRanges {
                        let r = m.range(at: g)
                        groups.append(r.location == NSNotFound ? "" : ns.substring(with: r))
                    }
                }
                return RegexMatch(match: ns.substring(with: m.range),
                                  index: m.range.location, groups: groups)
            }
            return (Array(matches), nil)
        } catch let e as NSError {
            return ([], e.localizedDescription)
        }
    }

    // Render a result the way the tool's UI does:
    // `N. "match" @ index -> [g1, g2]`, or `(none)` when empty.
    static func formatMatches(_ result: (matches: [RegexMatch], error: String?)) -> String {
        if let err = result.error { return err }
        if result.matches.isEmpty { return "(none)" }
        return result.matches.enumerated().map { i, m in
            var line = "\(i + 1). \"\(m.match)\" @ \(m.index)"
            if !m.groups.isEmpty { line += " -> [\(m.groups.joined(separator: ", "))]" }
            return line
        }.joined(separator: "\n")
    }
}

let result = RegexTester.runRegex(#"(\w+)@(\w+)\.(\w+)"#, "g", "mail@x.com nope bob@test.org")
print(RegexTester.formatMatches(result))

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →