Utility
Regex Tester
Test and debug regular expressions in real-time. Highlights matches, captures groups, and explains patterns in plain English.
Implementation
interface RegexMatch {
fullMatch: string;
index: number;
groups: { name: string | number; value: string }[];
}
interface RegexResult {
matches: RegexMatch[];
totalMatches: number;
executionTime: number;
error: string | null;
}
function testRegex(pattern: string, flags: string, input: string): RegexResult {
const start = performance.now();
try {
const re = new RegExp(pattern, flags);
const matches: RegexMatch[] = [];
if (flags.includes('g')) {
let match: RegExpExecArray | null;
while ((match = re.exec(input)) !== null) {
const groups: { name: string | number; value: string }[] = [];
// Numbered capture groups (skip index 0 = full match)
for (let i = 1; i < match.length; i++) {
if (match[i] !== undefined) {
groups.push({ name: i, value: match[i] });
}
}
// Named capture groups
if (match.groups) {
for (const [name, value] of Object.entries(match.groups)) {
if (value !== undefined) {
groups.push({ name, value });
}
}
}
matches.push({ fullMatch: match[0], index: match.index, groups });
if (match[0].length === 0) re.lastIndex++; // prevent infinite loop
}
} else {
const match = re.exec(input);
if (match) {
const groups: { name: string | number; value: string }[] = [];
for (let i = 1; i < match.length; i++) {
if (match[i] !== undefined) {
groups.push({ name: i, value: match[i] });
}
}
if (match.groups) {
for (const [name, value] of Object.entries(match.groups)) {
if (value !== undefined) {
groups.push({ name, value });
}
}
}
matches.push({ fullMatch: match[0], index: match.index, groups });
}
}
return {
matches,
totalMatches: matches.length,
executionTime: performance.now() - start,
error: null,
};
} catch (err) {
return {
matches: [],
totalMatches: 0,
executionTime: performance.now() - start,
error: (err as Error).message,
};
}
}
function explainRegex(pattern: string): string {
const explanations: { token: string; meaning: string }[] = [];
const rules: [RegExp, string][] = [
[/\^/, 'Start of string anchor'],
[/\$/, 'End of string anchor'],
[/\\./, 'Any single character'],
[/\\d/, 'Any digit (0-9)'],
[/\\D/, 'Any non-digit'],
[/\\w/, 'Any word character (a-z, A-Z, 0-9, _)'],
[/\\W/, 'Any non-word character'],
[/\\s/, 'Any whitespace (space, tab, newline)'],
[/\\S/, 'Any non-whitespace character'],
[/\\b/, 'Word boundary'],
[/\\B/, 'Non-word boundary'],
[/\+/, 'One or more of the preceding'],
[/\*/, 'Zero or more of the preceding'],
[/\?/, 'Zero or one of the preceding (optional)'],
[/\{(\d+)\}/, 'Exactly $1 of the preceding'],
[/\{(\d+),(\d*)\}/, 'Between $1 and $2 of the preceding'],
[/\\(\d+)/, 'Backreference to group $1'],
[/\(\?:/, 'Non-capturing group'],
[/\(\?=/, 'Positive lookahead'],
[/\(\?!/, 'Negative lookahead'],
[/\(\?<=/, 'Positive lookbehind'],
[/\(\?<!/, 'Negative lookbehind'],
[/\(\?<(\w+)>/, 'Named capture group "$1"'],
[/\(/, 'Capture group start'],
[/\)/, 'Group end'],
[/\[\^/, 'Negated character class'],
[/\[/, 'Character class start'],
[/\]/, 'Character class end'],
[/\|/, 'OR — alternation'],
];
for (const [regex, meaning] of rules) {
if (regex.test(pattern)) {
explanations.push({ token: regex.source, meaning });
}
}
if (explanations.length === 0) {
return 'Matches the literal text: "' + pattern + '"';
}
return explanations.map(e => ` ${e.meaning}`).join('\n');
}
// Usage
const result = testRegex(
'(\\d{4})-(\\d{2})-(\\d{2})',
'g',
'Dates: 2024-01-15 and 2024-12-31'
);
console.log(result.matches);
// => [
// { fullMatch: '2024-01-15', index: 7, groups: [{name:1,value:'2024'},{name:2,value:'01'},{name:3,value:'15'}] },
// { fullMatch: '2024-12-31', index: 22, groups: [{name:1,value:'2024'},{name:2,value:'12'},{name:3,value:'31'}] }
// ]
console.log(explainRegex('\\d{4}-\\d{2}'));
// => "Any digit (0-9)\n Exactly 4 of the preceding"Usage
Call testRegex(pattern, flags, input) to find all matches with group captures. Use explainRegex(pattern) to get a human-readable breakdown.
Examples
Input:
Output:
testRegex("(\\w+)@(\\w+\\.\\w+)", "g", "a@b.com c@d.org")Output:
2 matches with captured user and domain groupsInput:
Output:
testRegex("(?<year>\\d{4})-(?<month>\\d{2})", "g", "2024-01")Output:
Named groups: year="2024", month="01"Input:
Output:
explainRegex("^\\d{3}-\\d{4}$")Output:
"Start of string anchor, Any digit, Exactly 3 of the preceding, ..."