Skip to content

Repository files navigation

flare

A regex engine for Go that can't be tricked into hanging.

CI Go Reference Go Report Card MIT License Zero dependencies


Most regex engines — the ones in Python, JavaScript, Java, Ruby, PCRE — work by backtracking. It's a clever trick, and it's why they can do fancy things like backreferences. It's also why a pattern as harmless-looking as (a+)+$ can bring a production server to its knees. Feed it the wrong string and it doesn't just get slow, it gets exponentially slow. People have taken down real services this way. It even has a name: ReDoS.

flare takes a different road. It doesn't backtrack at all. Under the hood it runs your pattern as a state machine that walks the input exactly once, so matching is always linear in the length of the text. There is no evil input. There is no pattern that blows up. You can hand it a regex from a random user on the internet and go to sleep.

Here's the same pattern, (a+)+$, given a string of as that doesn't quite match:

Python's re        →  30 characters  →  still running after 10 seconds ✗
flare              →  100,000 chars  →  done in 0.02 seconds ✓

That's not a tuned benchmark. That's just what happens.

Install

go get github.com/umudhasanli/flare-regex

Go 1.21 or newer. No dependencies — it's the standard library and nothing else.

A quick taste

package main

import (
	"fmt"
	"github.com/umudhasanli/flare-regex"
)

func main() {
	re := flare.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)

	fmt.Println(re.MatchString("today is 2026-07-10"))        // true
	fmt.Println(re.FindString("today is 2026-07-10"))         // 2026-07-10
	fmt.Println(re.FindStringSubmatch("2026-07-10"))          // [2026-07-10 2026 07 10]
	fmt.Println(re.ReplaceAllString("2026-07-10", "$3/$2/$1")) // 10/07/2026
}

If you've used Go's standard regexp package, this will feel like home. That's on purpose — the method names line up so you can try flare without relearning anything.

When raw regex gets ugly, build it in English

Nobody enjoys reading ^\d{1,3}(\.\d{1,3}){3}$ six months after they wrote it. So flare ships a builder that reads like a sentence:

// Matches an IPv4 address like 192.168.0.1
re := flare.New().
	Start().
	Digit().Between(1, 3).
	Group(func(b *flare.Builder) {
		b.Lit(".").Digit().Between(1, 3)
	}).Times(3).
	End().
	MustCompile()

re.MatchString("192.168.0.1") // true

Lit escapes metacharacters for you, so New().Lit("a.b") looks for the literal text a.b — not "a, then anything, then b". One less thing to get wrong.

The feature Go's own regexp doesn't have: it explains itself

Everyone has stared at a regex someone else wrote and had no idea what it does. flare can just tell you — in plain English:

re := flare.MustCompile(`^(cat|dog)s?\d+$`)
fmt.Print(re.Explain())
• the start of the text
• capturing group #1:
  • one of:
    • the text "cat"
    • the text "dog"
• the character "s", optional (zero or one time)
• a digit (0-9), repeated one or more times
• the end of the text

The standard library can run your pattern, but it can't describe it. flare keeps the parsed structure around, so it walks the pattern and turns it back into readable steps. It works on the command line too:

flare -explain '(\d{4})-(\d{2})-(\d{2})'

Between the Builder (English → regex) and Explain (regex → English), you never have to hold a cryptic pattern in your head again.

Named groups, case-insensitivity, and bytes

Real patterns need real features. Name your groups and pull them out by name:

re := flare.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
m := re.FindStringSubmatchMap("2026-07-10")
// map[year:2026 month:07 day:10]

Match without caring about case, either for the whole pattern or just part of it:

flare.MustCompile(`(?i)hello`).MatchString("HELLO")   // true
flare.MustCompile(`(?i:cat)dog`).MatchString("CATdog") // true

And when you're working with []byte, skip the string conversion entirely — Match, Find, FindSubmatch, FindAll, and ReplaceAll all take bytes.

The CLI: grep that can't be DoS'd

Installing the module also gives you a little command-line tool. It behaves like grep, but it's backed by the same engine, so no pattern anyone types will ever hang it.

go install github.com/umudhasanli/flare-regex/cmd/flare@latest
flare '^error:' server.log                # lines that match
flare -o '\d{3}-\d{4}' contacts.txt       # only the matching parts
flare -n 'TODO' *.go                      # with line numbers
flare -c 'WARN' app.log                   # just count them
flare -r '$3/$2/$1' '(\d{4})-(\d{2})-(\d{2})' dates.txt   # find and replace
cat access.log | flare '4\d\d'            # reads stdin too

What it understands

Literals abc
Any character .
Character classes [a-z0-9], [^aeiou]
Shorthands \d \D \w \W \s \S
Anchors ^, $, \b, \B
Alternation cat|dog|bird
Groups (...), (?:...), (?P<name>...)
Case-insensitive (?i) and (?i:...)
Repetition * + ? {3} {2,} {2,5}
Lazy repetition *? +? ??
Escapes \. \* \n \t

What it deliberately leaves out

Backreferences (\1) and lookahead/lookbehind ((?=...)).

This isn't laziness — it's the whole point. Those are exactly the features that force an engine to backtrack, and backtracking is the thing that makes regex dangerous. Leaving them out is what lets flare promise it will never hang. It's a trade, and for the huge majority of real-world patterns, it's a trade worth making.

How it works, briefly

Three steps, and the whole engine is small enough to read in an afternoon:

  1. A recursive-descent parser turns your pattern into a small syntax tree.
  2. A compiler flattens that tree into a list of simple instructions — every *, +, or | becomes a branch.
  3. A virtual machine runs all the possible branches at the same time, keeping a set of active states and advancing them one character at a time.

Because the set of active states can never be bigger than the program itself, the work per character is bounded. Multiply by the number of characters and you get linear time. There's simply no room for an explosion to happen.

This is the same idea behind Go's own regexp package and Google's RE2, described beautifully in Russ Cox's articles on the subject. flare is a small, readable, dependency-free take on it.

Correctness and performance — the honest version

Correctness: flare is fuzz-tested against Go's standard regexp on a set of shared patterns. It's checked over tens of thousands of random inputs that both engines agree on every match. Run it yourself:

go test -fuzz FuzzAgainstStdlib -fuzztime 30s

Performance: be clear-eyed here. Go's standard library regexp is also a linear-time engine, and on ordinary patterns it is faster than flare and does zero allocations. flare converts to runes and tracks submatch state, so it trades some speed for its features. If you're on Go and want raw throughput on trusted patterns, use the standard library.

So when should you reach for flare? When you value what it adds: the readable Builder, Explain(), named-group maps, and a small self-contained engine you can actually read and learn from. The linear-time guarantee matters most when you're comparing against the backtracking engines in other languages — that's where "can't be DoS'd" is a feature you don't otherwise get.

Contributing

This project is meant to be approachable. If you've ever wanted to understand how a regex engine actually works, this is a good place to poke around — the code is short and there's no magic hiding in a dependency.

Good first contributions:

  • More syntax: Unicode character classes, POSIX classes ([[:alpha:]]), \A/\z
  • A UTF-8 fast path that avoids the []rune conversion (a real speed win)
  • Reducing per-match allocations in the VM
  • More CLI flags (-i for case-insensitive, recursive directory search)

Open an issue to talk it through, or just send a PR. All welcome.

License

MIT. Do what you like with it — see LICENSE.


If flare saved you from a regex that hangs, consider giving it a ⭐.
It genuinely helps other people find it.

About

A tiny, dependency-free regex engine for Go: guaranteed linear-time matching (no ReDoS), a fluent builder, and Explain() to describe any pattern in plain English.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages