randregex

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

randregex

randregex is a Go library for generating pseudo-random strings that match regular expressions. It is intended for test data, identifiers, fixtures, property-style checks, and other workflows where a compact regexp is a clearer way to describe valid sample strings than handwritten generation code.

Install

go get github.com/ryanfowler/randregex

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/ryanfowler/randregex"
)

func main() {
	g, err := randregex.Compile(`[a-z]{8}\d{2}`)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(g.Generate())
}

API Overview

The public API is intentionally small:

  • Compile(pattern string) (*Generator, error) parses and validates a regexp pattern using DefaultMaxRepeat.
  • CompileMaxRepeat(pattern string, maxRepeat int) (*Generator, error) parses and validates a regexp pattern using a caller-provided unbounded-repeat limit.
  • MustCompile(pattern string) *Generator is suitable for package-level generators and panics on invalid input.
  • MustCompileMaxRepeat(pattern string, maxRepeat int) *Generator combines package-level setup with a caller-provided unbounded-repeat limit.
  • FromRegexp(re *syntax.Regexp) (*Generator, error) compiles an existing regexp/syntax.Regexp without mutating it, using DefaultMaxRepeat.
  • FromRegexpMaxRepeat(re *syntax.Regexp, maxRepeat int) (*Generator, error) compiles an existing regexp/syntax.Regexp with a caller-provided unbounded-repeat limit.
  • (*Generator).Generate() string returns a generated string using the default pseudo-random source.
  • (*Generator).GenerateWithRand(r Rand) string uses a caller-provided random source.
  • (*Generator).Append(dst []byte) []byte appends generated output to a buffer.
  • (*Generator).AppendWithRand(dst []byte, r Rand) []byte combines buffer reuse with a caller-provided random source.
  • CryptoRand is a Rand value backed by Go's crypto/rand source.

DefaultMaxRepeat is the bound used by Compile, MustCompile, and FromRegexp for unbounded repetitions:

const DefaultMaxRepeat = 32

Use a MaxRepeat variant when a pattern needs a different unbounded-repeat policy:

g, err := randregex.CompileMaxRepeat(pattern, 8)

FromRegexp and FromRegexpMaxRepeat return an error for a nil *syntax.Regexp. They do not mutate the regexp passed by the caller. Passing an already simplified regexp is supported, but regexp/syntax.Simplify may rewrite counted unbounded repetitions such as a{3,} into forms that no longer preserve the original minimum for randregex's maxRepeat policy.

Compile Once, Reuse Often

Compile patterns once and reuse the generator:

var userID = randregex.MustCompile(`user-[a-z0-9]{12}`)

func newUserID() string {
	return userID.Generate()
}

*Generator is immutable after construction and safe for concurrent use.

Reproducible Output

Use GenerateWithRand or AppendWithRand with any value that satisfies:

type Rand interface {
	IntN(n int) int
}

This interface is satisfied by *math/rand/v2.Rand:

r := rand.New(rand.NewPCG(1, 2))
g := randregex.MustCompile(`[a-z]{8}`)

fmt.Println(g.GenerateWithRand(r))

If a Rand value is shared across goroutines, the Rand implementation must provide its own synchronization.

GenerateWithRand and AppendWithRand require a non-nil Rand that returns a value in [0, n) from IntN(n). Invalid Rand implementations may cause a panic or invalid output.

Cryptographic Randomness

For security-sensitive output, pass CryptoRand to GenerateWithRand or AppendWithRand:

g := randregex.MustCompile(`[a-zA-Z0-9_-]{32}`)

token := g.GenerateWithRand(randregex.CryptoRand)

CryptoRand uses crypto/rand.Reader and panics if the system cryptographic source fails. Direct calls to CryptoRand.IntN also panic when n <= 0.

The regular expression still determines the output entropy. CryptoRand provides an unpredictable source of randomness, but it does not make a small output space secure; for example, [0-9]{6} still has only one million possible values.

Buffer Reuse

Append and AppendWithRand are the allocation-conscious APIs:

g := randregex.MustCompile(`[a-zA-Z0-9_-]{24}`)
buf := make([]byte, 0, 64)

for range 1000 {
	buf = buf[:0]
	buf = g.Append(buf)
	use(buf)
}

For common ASCII patterns, AppendWithRand allocates zero times when the provided buffer has enough capacity.

Supported Syntax

Patterns are parsed with Go's regexp/syntax package using syntax.Perl.

Supported:

  • Empty expressions
  • Literal strings and escaped literal characters
  • Literal Unicode characters
  • Concatenation
  • Alternation, such as foo|bar
  • Capturing and non-capturing groups
  • Character classes, such as [a-z], [abc], and [a-zA-Z0-9_]
  • Predefined ASCII classes: \d, \D, \w, \W, \s, \S
  • Repetition: ?, *, +, {n}, {n,m}, {n,}
  • Dot .
  • Anchors as zero-width nodes: ^, $, \A, \z, \b, \B

Unsupported expressions return compile-time errors. Go's regexp syntax does not support lookaround or backreferences, so randregex does not either.

Word-boundary assertions are accepted only when the adjacent generated characters make the assertion guaranteed. For example, \b[a-z]{4}\b is valid, while a?\b is rejected because one random branch would violate the assertion.

Repetition Bounds

The maxRepeat argument controls unbounded repetitions:

  • a* generates 0 through maxRepeat repetitions.
  • a+ generates 1 through maxRepeat repetitions, or exactly 1 when maxRepeat is 0.
  • a{3,} generates 3 through maxRepeat repetitions when maxRepeat > 3.
  • If the minimum is greater than or equal to maxRepeat, an unbounded repeat generates exactly the minimum.

maxRepeat must be greater than or equal to zero. Compile, MustCompile, and FromRegexp use DefaultMaxRepeat. Passing 0 to a MaxRepeat variant explicitly chooses a zero upper bound.

Character Generation

Character generation is intentionally ASCII-first for performance, predictability, and testability.

  • Literal Unicode characters are supported and emitted literally.
  • ASCII character classes are sampled directly.
  • Dot . samples from printable ASCII, from space through tilde.
  • Negated and very broad character classes sample from printable ASCII after applying the class.
  • \d is [0-9].
  • \w is [0-9A-Za-z_].
  • \s is tab, newline, vertical tab, form feed, carriage return, and space.

Full Unicode character-class sampling is intentionally out of scope. Use Unicode literals outside character classes when exact Unicode characters are needed.

Randomness and Security

The default methods use Go's pseudo-random math/rand/v2 default source. Generated strings are not cryptographic secrets.

For reproducible output, pass a seeded math/rand/v2.Rand. For security-sensitive use, pass randregex.CryptoRand.

randregex samples choices locally at each regexp node. It does not attempt to provide a uniform distribution over all strings accepted by a pattern.

Performance Characteristics

randregex compiles regular expressions into an immutable internal generator tree. Generation does not re-parse patterns or walk regexp/syntax trees.

The implementation:

  • Appends directly into caller-provided buffers.
  • Precomputes sampleable character sets.
  • Chooses alternation branches without generating unused branches.
  • Generates repetition counts once per repeat node.
  • Avoids reflection and external runtime dependencies.

Benchmarks are included in randregex_benchmark_test.go and can be run with:

go test -bench=. -benchmem ./...

Error Handling Model

Invalid patterns, unsupported regexp nodes, unsafe word-boundary assertions, and unsampleable character classes are rejected during compilation. Generation methods assume a valid compiled generator and therefore return only generated output.

This design makes errors explicit at setup time and keeps hot-path generation simple.

Documentation

Overview

Package randregex generates pseudo-random strings that match regular expressions.

It is intended for test data, identifiers, fixtures, property-style checks, and other workflows where a regexp is a compact description of valid sample strings. Patterns are parsed with regexp/syntax using syntax.Perl.

Generator values are immutable and safe for concurrent use. The Generate and Append methods use the default pseudo-random source. GenerateWithRand and AppendWithRand let callers provide any Rand implementation, including a seeded *math/rand/v2.Rand for reproducible output. If a Rand is shared across goroutines, the Rand implementation must provide its own synchronization.

Compile parses, validates, and converts patterns into an immutable internal representation using DefaultMaxRepeat for unbounded repetitions such as a*, a+, and a{3,}. Use CompileMaxRepeat or FromRegexpMaxRepeat when a different unbounded-repeat policy is needed. Use MustCompile for package-level generators when invalid patterns should be treated as programmer errors.

Generated strings are not cryptographic secrets unless callers provide a Rand implementation backed by an appropriate cryptographic source, such as CryptoRand.

Character generation is ASCII-first. Literal Unicode characters are emitted as literals, but dot and negated or very broad character classes sample from printable ASCII, from space through tilde. The predefined Perl classes \d, \w, and \s use conventional ASCII definitions. Full Unicode character-class sampling and regex features unsupported by Go's regexp engine, such as lookaround and backreferences, are out of scope.

Index

Examples

Constants

View Source
const DefaultMaxRepeat = 32

DefaultMaxRepeat is the recommended upper bound for unbounded repetitions.

It is used by Compile, MustCompile, and FromRegexp for patterns such as a*, a+, and a{3,}. Use the MaxRepeat variants to choose a different bound.

Variables

View Source
var CryptoRand = &cryptoRand{}

CryptoRand is a Rand value backed by crypto/rand.Reader.

Pass it to GenerateWithRand or AppendWithRand when generated strings are used as secrets or other security-sensitive identifiers. CryptoRand is safe for concurrent use. It panics if crypto/rand.Reader fails or if IntN is called with n <= 0.

Functions

This section is empty.

Types

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator is an immutable compiled regular-expression string generator.

Generator values are safe for concurrent use. Generation methods using an explicit Rand require the caller's Rand to be safe when shared concurrently.

func Compile

func Compile(pattern string) (*Generator, error)

Compile parses pattern using regexp/syntax.Perl and compiles it into a Generator using DefaultMaxRepeat.

Example
package main

import (
	"fmt"

	"github.com/ryanfowler/randregex"
)

func main() {
	g, err := randregex.Compile(`[a-z]{8}\d{2}`)
	if err != nil {
		panic(err)
	}

	fmt.Println(len(g.Generate()))
}
Output:
10

func CompileMaxRepeat

func CompileMaxRepeat(pattern string, maxRepeat int) (*Generator, error)

CompileMaxRepeat parses pattern using regexp/syntax.Perl and compiles it into a Generator.

maxRepeat controls the maximum used for unbounded repetitions. It must be non-negative. For a{n,}, the upper bound is maxRepeat when maxRepeat > n; otherwise generation emits exactly n repetitions.

Example
package main

import (
	"fmt"

	"github.com/ryanfowler/randregex"
)

func main() {
	g, err := randregex.CompileMaxRepeat(`a{4,}`, 4)
	if err != nil {
		panic(err)
	}

	fmt.Println(g.Generate())
}
Output:
aaaa

func FromRegexp

func FromRegexp(re *syntax.Regexp) (*Generator, error)

FromRegexp compiles re into a Generator without mutating re, using DefaultMaxRepeat. It returns an error when re is nil.

func FromRegexpMaxRepeat

func FromRegexpMaxRepeat(re *syntax.Regexp, maxRepeat int) (*Generator, error)

FromRegexpMaxRepeat compiles re into a Generator without mutating re. It returns an error when re is nil.

maxRepeat controls unbounded repetitions as described by CompileMaxRepeat. Passing an already simplified regexp is supported, but regexp/syntax.Simplify may rewrite counted unbounded repetitions such as a{3,} into forms that no longer preserve the original minimum for randregex's maxRepeat policy.

func MustCompile

func MustCompile(pattern string) *Generator

MustCompile is like Compile but panics if pattern cannot be compiled.

func MustCompileMaxRepeat

func MustCompileMaxRepeat(pattern string, maxRepeat int) *Generator

MustCompileMaxRepeat is like CompileMaxRepeat but panics if pattern cannot be compiled.

func (*Generator) Append

func (g *Generator) Append(dst []byte) []byte

Append appends a pseudo-random string matching the compiled regexp to dst and returns the extended buffer.

Example
package main

import (
	"fmt"

	"github.com/ryanfowler/randregex"
)

func main() {
	g := randregex.MustCompile(`[a-zA-Z0-9_-]{24}`)
	buf := make([]byte, 0, 64)

	buf = g.Append(buf)

	fmt.Println(len(buf))
}
Output:
24

func (*Generator) AppendWithRand

func (g *Generator) AppendWithRand(dst []byte, r Rand) []byte

AppendWithRand appends a generated string matching the compiled regexp to dst using r, and returns the extended buffer.

This is the lowest-allocation public API. If dst has sufficient capacity, it does not allocate for common ASCII patterns. r must be non-nil and must return values in [0, n) from IntN(n). If r is shared concurrently, it must provide its own synchronization.

func (*Generator) Generate

func (g *Generator) Generate() string

Generate generates a pseudo-random string matching the compiled regexp.

func (*Generator) GenerateWithRand

func (g *Generator) GenerateWithRand(r Rand) string

GenerateWithRand generates a string matching the compiled regexp using r.

r must be non-nil and must return values in [0, n) from IntN(n). If r is shared concurrently, it must provide its own synchronization.

Example
package main

import (
	"fmt"
	"math/rand/v2"

	"github.com/ryanfowler/randregex"
)

func main() {
	r := rand.New(rand.NewPCG(1, 2))
	g := randregex.MustCompile(`[a-z]{8}`)

	fmt.Println(g.GenerateWithRand(r))
}
Output:
uquugbml

type Rand

type Rand interface {
	IntN(n int) int
}

Rand is the random-number interface used by Generator.

It is satisfied by *math/rand/v2.Rand. The package-level CryptoRand value provides a cryptographic implementation. Implementations must return a value in [0, n), and may panic when n <= 0. If a Rand is shared concurrently, the Rand implementation is responsible for synchronization.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL