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: ®ex::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 →