Regex Explainer — Swift source
Translate a regular expression into plain English, step by step. Explains anchors, character classes, quantifiers, groups, escapes, alternation, and flags.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// regex-explainer — Swift port: tokenize a regex into labeled tokens + describe JS flags.
// Mirrors src/lib/regexExplain.ts (canonical TS). Validation compiles with
// NSRegularExpression (ICU) — near-JS syntax; JS-only constructs report ok=false.
import Foundation
struct RegexToken { let token: String; let description: String }
struct FlagInfo { let flag: String; let description: String }
struct ExplainResult {
let ok: Bool; let tokens: [RegexToken]; let flags: [FlagInfo]; let error: String?
}
let flagDesc = ["g": "global - find all matches", "i": "case-insensitive",
"m": "multiline (^ and $ match line boundaries)", "s": "dotAll - \".\" matches newlines",
"u": "unicode", "y": "sticky - match at lastIndex", "d": "indices - expose match boundaries"]
let escapeDesc = ["d": "a digit [0-9]", "D": "a non-digit", "w": "a word character [A-Za-z0-9_]",
"W": "a non-word character", "s": "a whitespace character", "S": "a non-whitespace character",
"b": "a word boundary", "B": "a non-word boundary", "n": "a newline", "t": "a tab",
"r": "a carriage return"]
/// Index of the "]" closing a class opened at start; a leading "]" is a literal member.
func findClassEnd(_ p: [Character], _ start: Int) -> Int {
var i = start + 1
if i < p.count, p[i] == "^" { i += 1 }
if i < p.count, p[i] == "]" { i += 1 }
while i < p.count, p[i] != "]" { if p[i] == "\\" { i += 1 }; i += 1 }
return i < p.count ? i : p.count - 1
}
/// Index of the ")" matching the group opened at start; skips classes + escapes.
func findGroupEnd(_ p: [Character], _ start: Int) -> Int {
var depth = 1, i = start + 1
while i < p.count, depth > 0 {
if p[i] == "\\" { i += 2; continue }
if p[i] == "[" { i = findClassEnd(p, i) + 1; continue }
if p[i] == "(" { depth += 1 } else if p[i] == ")" { depth -= 1 }
i += 1
}
return i - 1
}
func describeGroup(_ grp: String) -> String {
for (pre, label) in [("?:", "non-capturing group"), ("?=", "lookahead assertion (positive)"),
("?!", "lookahead assertion (negative)"), ("?<=", "lookbehind assertion (positive)"),
("?<!", "lookbehind assertion (negative)")] where grp.hasPrefix("(" + pre) {
return label
}
return "capturing group"
}
func describeClass(_ inner: String) -> String {
inner.isEmpty ? "(empty)" : inner.replacingOccurrences(of: "\\", with: "\\\\")
}
/// Explain a regex pattern + flags into tokens. Never throws.
func explainRegex(_ pattern: String, _ flags: String = "") -> ExplainResult {
var opts = NSRegularExpression.Options() // JS i/m/s map onto ICU options
for f in flags {
if f == "i" { opts.insert(.caseInsensitive) }
if f == "m" { opts.insert(.anchorsMatchLines) }
if f == "s" { opts.insert(.dotMatchesLineSeparators) }
}
do { // validate with the native engine first
_ = try NSRegularExpression(pattern: pattern, options: opts)
} catch {
return ExplainResult(ok: false, tokens: [], flags: [], error: error.localizedDescription)
}
var tokens: [RegexToken] = []
func push(_ token: String, _ description: String) { tokens.append(RegexToken(token: token, description: description)) }
let p = Array(pattern) // per-character walk, mirroring the TS per-code-unit walk
var i = 0
while i < p.count {
let ch = String(p[i])
switch ch {
case "^": push("^", "start of the string (or line with /m)"); i += 1
case "$": push("$", "end of the string (or line with /m)"); i += 1
case ".": push(".", "any character (except newline, unless /s)"); i += 1
case "|": push("|", "OR - alternation between groups"); i += 1
case "\\":
let next = i + 1 < p.count ? String(p[i + 1]) : ""
push("\\" + next, escapeDesc[next] ?? "an escaped literal \"\(next)\"")
i += 2
case "[":
let end = findClassEnd(p, i)
let cls = String(p[i...end])
let negated = i + 1 < p.count && p[i + 1] == "^"
let inner = String(p[(i + 1 + (negated ? 1 : 0))..<end])
push(cls, "match any \(negated ? "character NOT in" : "of"): \(describeClass(inner))")
i = end + 1
case "(":
let end = findGroupEnd(p, i)
let grp = String(p[i...end])
push(grp, describeGroup(grp))
i = end + 1
case "*", "+", "?":
let lazy = i + 1 < p.count && p[i + 1] == "?"
let base = ["*": "0 or more times", "+": "1 or more times", "?": "0 or 1 time (optional)"][ch]!
push(ch + (lazy ? "?" : ""), "quantifier - \(base)\(lazy ? " (lazy/non-greedy)" : " (greedy)")")
i += lazy ? 2 : 1
case "{":
if let end = p[i...].firstIndex(of: "}") { // bounded quantifier {n,m}
let lazy = end + 1 < p.count && p[end + 1] == "?"
let q = String(p[i...end])
push(q + (lazy ? "?" : ""),
"quantifier - repeat \(String(p[(i + 1)..<end])) time(s)\(lazy ? " (lazy)" : "")")
i = end + 1 + (lazy ? 1 : 0)
} else { // no closing brace: a literal "{"
push("{", "the literal \"{\"")
i += 1
}
default: // a literal character
push(ch, "the literal \"\(ch)\"")
i += 1
}
}
let flagList = flags.map { FlagInfo(flag: String($0),
description: flagDesc[String($0)] ?? "unknown flag \"\($0)\"") }
return ExplainResult(ok: true, tokens: tokens, flags: flagList, error: nil)
}
let r = explainRegex("^(\\w+)@([\\w.-]+)$", "gi")
if let err = r.error {
print("error: \(err)")
} else {
for t in r.tokens { print(t.token.padding(toLength: 14, withPad: " ", startingAt: 0) + " " + t.description) }
for f in r.flags { print("flag \(f.flag): \(f.description)") }
}
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 →