Skip to content

Regex Tester — Rust 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 Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! Regular expression tester: match a pattern against input text and report
//! each match with its capture groups and byte index.
//!
//! Language: Rust
//! CosmoDev polyglot showcase port of the `regex-tester` tool.
//! Ported from src/tools/RegexTester.tsx — display source, part of CosmoDev's
//! polyglot tool pages.
//!
//! Compiles the pattern with inline flags and walks the captures, surfacing
//! the full match, its index, and the capture groups. JS flags map to RE2
//! inline groups: i→(?i), m→(?m), s→(?s); the regex crate is Unicode by
//! default, so u is a no-op. g selects all-matches vs first-match (capped at
//! 2000, mirroring the JS source). Parity caveats: the regex crate is RE2,
//! so patterns using backreferences or lookahead/lookbehind will not compile;
//! the sticky y flag has no equivalent. Indices are byte offsets (== JS char
//! indices for ASCII input); RE2 advances past empty matches itself, so the
//! JS source's zero-width guard is unnecessary here.
//! (Cargo.toml: `regex = "1"`)

use regex::Regex;

/// One match: full text, starting byte index, and capture groups (group 0
/// excluded; `None` means the group did not participate, rendered as "").
pub struct RegexMatch {
    pub r#match: String,
    pub index: usize,
    pub groups: Vec<Option<String>>,
}

pub struct RegexResult {
    pub matches: Vec<RegexMatch>,
    pub error: Option<String>,
}

/// Turn the JS flag letters the regex crate understands into an inline group
/// such as "(?ims)". Unknown letters (g, u, y) are skipped.
fn flag_prefix(flags: &str) -> String {
    let body: String = flags.chars().filter(|f| matches!(f, 'i' | 'm' | 's')).collect();
    if body.is_empty() {
        String::new()
    } else {
        format!("(?{body})")
    }
}

fn caps_to_match(caps: &regex::Captures) -> RegexMatch {
    let full = caps.get(0).expect("capture 0 always present");
    RegexMatch {
        r#match: full.as_str().to_string(),
        index: full.start(),
        groups: (1..caps.len())
            .map(|i| caps.get(i).map(|m| m.as_str().to_string()))
            .collect(),
    }
}

/// Run `pattern` (with `flags`) against `test_str` and collect every match.
/// With the g flag all matches are returned (≤2000); without it, only the first.
pub fn run_regex(pattern: &str, flags: &str, test_str: &str) -> RegexResult {
    if pattern.is_empty() {
        return RegexResult { matches: Vec::new(), error: None };
    }
    let re = match Regex::new(&format!("{}{}", flag_prefix(flags), pattern)) {
        Ok(re) => re,
        Err(e) => return RegexResult { matches: Vec::new(), error: Some(e.to_string()) },
    };

    let mut out: Vec<RegexMatch> = Vec::new();
    if flags.contains('g') {
        for caps in re.captures_iter(test_str).take(2000) {
            out.push(caps_to_match(&caps));
        }
    } else if let Some(caps) = re.captures(test_str) {
        out.push(caps_to_match(&caps));
    }
    RegexResult { matches: out, error: None }
}

/// Render a result as the tool's UI does:
/// `N. "match" @ index → [g1, g2]`, or `(none)` when empty.
pub fn format_matches(r: &RegexResult) -> String {
    if let Some(e) = &r.error {
        return e.clone();
    }
    if r.matches.is_empty() {
        return "(none)".to_string();
    }
    r.matches
        .iter()
        .enumerate()
        .map(|(i, m)| {
            let mut line = format!("{}. \"{}\" @ {}", i + 1, m.r#match, m.index);
            if !m.groups.is_empty() {
                let gs: Vec<String> = m.groups.iter().map(|g| g.clone().unwrap_or_default()).collect();
                line += &format!(" → [{}]", gs.join(", "));
            }
            line
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn main() {
    let res = run_regex(r"\b(\w+)@(\w+)\.(\w+)\b", "gi", "mail@x.com nope bob@test.org");
    println!("{}", format_matches(&res));
}

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 →