Skip to content

Regex Tester — Go 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 Go 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: Go
// 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 and walks every match via the stdlib regexp package,
// surfacing the full match, its index, and capture groups. JS flags map to
// RE2 inline groups: i→(?i), m→(?m), s→(?s); Go's regexp is Unicode-aware by
// default, so u is a no-op. g selects all-matches vs first-match (capped at
// 2000, mirroring the JS source). Parity caveats: Go uses RE2, so patterns
// using backreferences or lookahead/lookbehind will not compile; the sticky
// y flag has no RE2 equivalent. Indices are byte offsets (== JS char indices
// for ASCII input); RE2 advances past empty matches itself, so the explicit
// zero-width guard from the JS source is unnecessary here.
package main

import (
	"fmt"
	"regexp"
	"strings"
)

// RegexMatch is one match: full text, its starting index, and the capture
// groups (group 0 excluded; "" for a group that did not participate).
type RegexMatch struct {
	Match  string
	Index  int
	Groups []string
}

// RegexResult holds the matches, or — when the pattern is invalid — an error.
type RegexResult struct {
	Matches []RegexMatch
	Error   string
}

// flagPrefix turns the JS flag letters Go understands into an RE2 inline group
// such as "(?ims)". Unknown letters (g, u, y) are handled elsewhere or no-op.
func flagPrefix(flags string) string {
	var b strings.Builder
	b.WriteString("(?")
	for _, f := range flags {
		switch f {
		case 'i', 'm', 's':
			b.WriteRune(f)
		}
	}
	b.WriteString(")")
	if body := b.String(); body != "(?)" {
		return body
	}
	return ""
}

// RunRegex compiles pattern+flags against testStr and returns every match.
// With the g flag all matches are returned (≤2000); without it, only the first.
func RunRegex(pattern, flags, testStr string) RegexResult {
	if pattern == "" {
		return RegexResult{}
	}
	re, err := regexp.Compile(flagPrefix(flags) + pattern)
	if err != nil {
		return RegexResult{Error: err.Error()}
	}

	var locs [][]int
	if strings.ContainsRune(flags, 'g') {
		locs = re.FindAllStringSubmatchIndex(testStr, 2000)
	} else if loc := re.FindStringSubmatchIndex(testStr); loc != nil {
		locs = [][]int{loc}
	}

	out := make([]RegexMatch, 0, len(locs))
	for _, loc := range locs {
		m := RegexMatch{Match: testStr[loc[0]:loc[1]], Index: loc[0], Groups: []string{}}
		for i := 2; i+1 < len(loc); i += 2 {
			if loc[i] >= 0 { // -1 means the group did not participate
				m.Groups = append(m.Groups, testStr[loc[i]:loc[i+1]])
			} else {
				m.Groups = append(m.Groups, "")
			}
		}
		out = append(out, m)
	}
	return RegexResult{Matches: out}
}

// FormatMatches renders a result as the tool's UI does:
// `N. "match" @ index → [g1, g2]`, or `(none)` when empty.
func FormatMatches(r RegexResult) string {
	if r.Error != "" {
		return r.Error
	}
	if len(r.Matches) == 0 {
		return "(none)"
	}
	lines := make([]string, 0, len(r.Matches))
	for i, m := range r.Matches {
		line := fmt.Sprintf("%d. %q @ %d", i+1, m.Match, m.Index)
		if len(m.Groups) > 0 {
			line += " → [" + strings.Join(m.Groups, ", ") + "]"
		}
		lines = append(lines, line)
	}
	return strings.Join(lines, "\n")
}

func main() {
	res := RunRegex(`\b(\w+)@(\w+)\.(\w+)\b`, "gi", "mail@x.com nope bob@test.org")
	fmt.Println(FormatMatches(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 →