Documentation
¶
Overview ¶
Package literal provides types and operations for extracting literal sequences from regex patterns for prefilter optimization.
Package literal provides types and operations for representing and manipulating literal byte sequences extracted from regex patterns.
The primary use case is for prefilter optimization in regex engines: by extracting literal strings from patterns (e.g., "hello" from /hello.*world/), we can quickly filter out non-matching text before running the full regex automaton.
Key concepts:
- A Literal is a concrete byte sequence that may appear in matches
- A Seq is a set of alternative literals (e.g., from alternations like /foo|bar/)
- Operations like Minimize, LCP, LCS help optimize prefilter strategies
Example ¶
Example demonstrates basic usage of literal sequences
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
// Create a sequence of literals from a regex alternation like /foo|bar|baz/
seq := literal.NewSeq(
literal.NewLiteral([]byte("foo"), true),
literal.NewLiteral([]byte("bar"), true),
literal.NewLiteral([]byte("baz"), true),
)
fmt.Printf("Sequence has %d literals\n", seq.Len())
fmt.Printf("First literal: %s\n", seq.Get(0).Bytes)
}
Output: Sequence has 3 literals First literal: foo
Index ¶
- type Extractor
- type ExtractorConfig
- type InnerLiteralInfo
- type Literal
- type Seq
- func (s *Seq) AllComplete() bool
- func (s *Seq) Clone() *Seq
- func (s *Seq) CrossForward(other *Seq)
- func (s *Seq) Dedup()
- func (s *Seq) Get(i int) Literal
- func (s *Seq) IsEmpty() bool
- func (s *Seq) IsFinite() bool
- func (s *Seq) IsPartialCoverage() bool
- func (s *Seq) KeepFirstBytes(n int)
- func (s *Seq) Len() int
- func (s *Seq) LongestCommonPrefix() []byte
- func (s *Seq) LongestCommonSuffix() []byte
- func (s *Seq) Minimize()
Examples ¶
- Package
- Extractor.ExtractInner
- Extractor.ExtractPrefixes
- Extractor.ExtractPrefixes (Alternates)
- Extractor.ExtractPrefixes (CharClass)
- Extractor.ExtractPrefixes (HttpMethods)
- Extractor.ExtractPrefixes (NoPrefix)
- Extractor.ExtractSuffixes
- ExtractorConfig
- Literal
- Seq.Clone
- Seq.IsEmpty
- Seq.IsFinite
- Seq.LongestCommonPrefix
- Seq.LongestCommonPrefix (None)
- Seq.LongestCommonSuffix
- Seq.Minimize
- Seq.Minimize (Chain)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Extractor ¶
type Extractor struct {
// contains filtered or unexported fields
}
Extractor extracts literal sequences from regex patterns.
It analyzes the regex AST (regexp/syntax.Regexp) and extracts:
- Prefix literals: literals that must appear at the start
- Suffix literals: literals that must appear at the end
- Inner literals: any literals that must appear somewhere
These literals enable fast prefiltering before running the full regex engine.
Algorithm overview:
- Parse regex to AST (caller uses regexp/syntax.Parse)
- Walk AST to extract literals based on operation type (OpLiteral, OpConcat, etc.)
- Apply limits (MaxLiterals, MaxLiteralLen, MaxClassSize)
- Return Seq of literals for prefilter selection
Example:
re, _ := syntax.Parse("(hello|world)", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
prefixes := extractor.ExtractPrefixes(re)
// prefixes = ["hello", "world"]
func New ¶
func New(config ExtractorConfig) *Extractor
New creates a new Extractor with the given configuration.
Example:
config := literal.DefaultConfig() config.MaxLiterals = 128 // Allow more literals extractor := literal.New(config)
func (*Extractor) ExtractInner ¶
ExtractInner extracts inner literals (not necessarily prefix/suffix). Useful for patterns like ".*foo.*" where foo must appear somewhere.
This is a simpler extraction that just looks for any required literals in the pattern, regardless of position.
Examples:
".*foo.*" → ["foo"] ".*(hello|world).*" → ["hello", "world"] "prefix.*middle.*suffix" → ["prefix", "middle", "suffix"] (first found)
Returns empty Seq if no inner literals can be extracted.
Example ¶
ExampleExtractor_ExtractInner demonstrates inner literal extraction for patterns where literals can appear anywhere.
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Pattern: .*error.*
// Inner literal should be "error"
re, _ := syntax.Parse(".*error.*", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
inner := extractor.ExtractInner(re)
fmt.Printf("Found %d inner literal(s):\n", inner.Len())
for i := 0; i < inner.Len(); i++ {
lit := inner.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: Found 1 inner literal(s): - "error"
func (*Extractor) ExtractInnerForReverseSearch ¶
func (e *Extractor) ExtractInnerForReverseSearch(re *syntax.Regexp) *InnerLiteralInfo
ExtractInnerForReverseSearch extracts inner literals suitable for ReverseInner strategy. Returns nil if no suitable inner literal found (only prefix/suffix available).
"Inner" means:
- NOT at the very start (otherwise use prefix strategy)
- NOT at the very end (otherwise use suffix strategy)
- Has wildcards/repetitions both before AND after
This is specifically for patterns like:
- `ERROR.*connection.*timeout` → inner literal: "connection"
- `func.*Error.*return` → inner literal: "Error"
- `prefix.*middle.*suffix` → inner literal: "middle"
Algorithm:
- Pattern must be OpConcat (concatenation of parts)
- Find the first literal that is: a. NOT at position 0 (has content before) b. NOT at last position (has content after) c. Both before and after have wildcards (.*|.+|.?)
- Prefer longer literals
Returns nil if:
- Not a concat pattern
- Only prefix or suffix literals available
- No wildcards before/after literals
Example:
// Pattern: `ERROR.*connection.*timeout` re, _ := syntax.Parse(`ERROR.*connection.*timeout`, syntax.Perl) extractor := literal.New(literal.DefaultConfig()) innerInfo := extractor.ExtractInnerForReverseSearch(re) // innerInfo.Literals = ["connection"] // innerInfo.InnerIdx = 2 (position in concat)
func (*Extractor) ExtractPrefixes ¶
ExtractPrefixes extracts prefix literals from the regex. Returns literals that must appear at the start of any match.
Handles these syntax.Op types:
- OpLiteral: direct literal string → returns it
- OpConcat: take first sub-expression
- OpAlternate: union of all alternatives (e.g., (foo|bar) → ["foo", "bar"])
- OpCharClass: expand small classes (e.g., [abc] → ["a", "b", "c"])
- OpCapture: ignore capture group, extract from sub-expression
- OpStar/OpQuest/OpPlus: repetition makes prefix optional → return empty
Examples:
"hello" → ["hello"] "(foo|bar)" → ["foo", "bar"] "[abc]test" → ["atest", "btest", "ctest"] "hello.*world" → ["hello"] ".*foo" → [] (no prefix requirement)
Returns empty Seq if no prefix literals can be extracted.
Example ¶
ExampleExtractor_ExtractPrefixes demonstrates basic prefix extraction from a simple literal pattern.
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Parse a simple pattern
re, _ := syntax.Parse("hello", syntax.Perl)
// Create extractor with default config
extractor := literal.New(literal.DefaultConfig())
// Extract prefixes
prefixes := extractor.ExtractPrefixes(re)
// Print results
fmt.Printf("Found %d prefix(es):\n", prefixes.Len())
for i := 0; i < prefixes.Len(); i++ {
lit := prefixes.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: Found 1 prefix(es): - "hello"
Example (Alternates) ¶
ExampleExtractor_ExtractPrefixes_alternates demonstrates prefix extraction from alternation patterns. Note: Go's regex parser may optimize patterns by factoring common prefixes (e.g., "bar|baz" becomes "ba[rz]").
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Pattern with alternations (using distinct prefixes to avoid parser optimization)
re, _ := syntax.Parse("(apple|banana|cherry)", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
prefixes := extractor.ExtractPrefixes(re)
fmt.Printf("Found %d prefix(es):\n", prefixes.Len())
for i := 0; i < prefixes.Len(); i++ {
lit := prefixes.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: Found 3 prefix(es): - "apple" - "banana" - "cherry"
Example (CharClass) ¶
ExampleExtractor_ExtractPrefixes_charClass demonstrates character class expansion for small classes.
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Small character class: [abc]
re, _ := syntax.Parse("[abc]", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
prefixes := extractor.ExtractPrefixes(re)
fmt.Printf("Found %d prefix(es):\n", prefixes.Len())
for i := 0; i < prefixes.Len(); i++ {
lit := prefixes.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: Found 3 prefix(es): - "a" - "b" - "c"
Example (HttpMethods) ¶
ExampleExtractor_ExtractPrefixes_httpMethods shows a real-world use case: extracting HTTP method literals for fast prefiltering in log parsers. Note: Parser may optimize "POST|PUT|PATCH" to "P(OST|UT|ATCH)".
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// HTTP method regex (using methods with distinct first letters to avoid parser optimization)
re, _ := syntax.Parse("(GET|HEAD|DELETE|OPTIONS)", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
prefixes := extractor.ExtractPrefixes(re)
fmt.Printf("HTTP methods extracted: %d\n", prefixes.Len())
fmt.Println("Can use these for prefilter optimization:")
for i := 0; i < prefixes.Len(); i++ {
lit := prefixes.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: HTTP methods extracted: 4 Can use these for prefilter optimization: - "GET" - "HEAD" - "DELETE" - "OPTIONS"
Example (NoPrefix) ¶
ExampleExtractor_ExtractPrefixes_noPrefix demonstrates a pattern with no extractable prefix (starts with wildcard).
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Pattern starts with wildcard: .*error
re, _ := syntax.Parse(".*error", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
prefixes := extractor.ExtractPrefixes(re)
if prefixes.IsEmpty() {
fmt.Println("No prefix literals found (pattern starts with wildcard)")
} else {
fmt.Printf("Found %d prefix(es)\n", prefixes.Len())
}
}
Output: No prefix literals found (pattern starts with wildcard)
func (*Extractor) ExtractSuffixes ¶
ExtractSuffixes extracts suffix literals from the regex. Returns literals that must appear at the end of any match.
Algorithm is similar to ExtractPrefixes but analyzes from the end.
Examples:
"world" → ["world"] "(foo|bar)" → ["foo", "bar"] "test[xyz]" → ["testx", "testy", "testz"] "hello.*world" → ["world"] "foo.*" → [] (no suffix requirement)
Returns empty Seq if no suffix literals can be extracted.
Example ¶
ExampleExtractor_ExtractSuffixes demonstrates suffix extraction from a pattern.
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Pattern: hello.*world
// Suffix should be "world"
re, _ := syntax.Parse("hello.*world", syntax.Perl)
extractor := literal.New(literal.DefaultConfig())
suffixes := extractor.ExtractSuffixes(re)
fmt.Printf("Found %d suffix(es):\n", suffixes.Len())
for i := 0; i < suffixes.Len(); i++ {
lit := suffixes.Get(i)
fmt.Printf(" - %q\n", string(lit.Bytes))
}
}
Output: Found 1 suffix(es): - "world"
type ExtractorConfig ¶
type ExtractorConfig struct {
// MaxLiterals limits the maximum number of literals to extract.
// For patterns with many alternations like (a|b|c|...|z), this prevents
// unbounded memory growth. Default: 64.
MaxLiterals int
// MaxLiteralLen limits the maximum length of each extracted literal.
// Very long literals hurt prefilter performance due to cache misses.
// Default: 64.
MaxLiteralLen int
// MaxClassSize limits the size of character classes to expand.
// Character classes like [abc] are expanded to ["a", "b", "c"].
// Large classes like [a-z] (26 chars) are NOT expanded if > MaxClassSize.
// Default: 10.
MaxClassSize int
// CrossProductLimit is the maximum total number of intermediate literals allowed
// during cross-product expansion in OpConcat traversal. When a concatenation
// contains small character classes (e.g., ag[act]gtaaa), the extractor computes
// the cross-product of accumulated literals with each class expansion.
// This limit prevents combinatorial explosion from patterns with many classes.
//
// When exceeded, literals are truncated to 4 bytes (Teddy fingerprint size),
// deduplicated, and marked as inexact. Default: 250 (matching Rust regex-syntax).
CrossProductLimit int
}
ExtractorConfig configures literal extraction limits.
These limits prevent excessive extraction from complex patterns:
- MaxLiterals: prevents memory bloat from alternations like (a|b|c|d|...)
- MaxLiteralLen: prevents extracting very long literals that hurt cache locality
- MaxClassSize: prevents expanding large character classes like [a-z]
Example:
config := literal.ExtractorConfig{
MaxLiterals: 64,
MaxLiteralLen: 64,
MaxClassSize: 10,
}
extractor := literal.New(config)
Example ¶
ExampleExtractorConfig demonstrates configuring extraction limits.
package main
import (
"fmt"
"regexp/syntax"
"github.com/donge/coregex/literal"
)
func main() {
// Create custom config with stricter limits
config := literal.DefaultConfig()
config.MaxLiterals = 2 // Only extract 2 literals max
config.MaxLiteralLen = 10 // Truncate literals > 10 bytes
config.MaxClassSize = 3 // Only expand classes with ≤ 3 chars
extractor := literal.New(config)
// Pattern with many alternations
re, _ := syntax.Parse("(one|two|three|four|five)", syntax.Perl)
prefixes := extractor.ExtractPrefixes(re)
// Should only get 2 literals due to MaxLiterals=2
fmt.Printf("Extracted %d literals (limited to %d)\n", prefixes.Len(), config.MaxLiterals)
}
Output: Extracted 2 literals (limited to 2)
func DefaultConfig ¶
func DefaultConfig() ExtractorConfig
DefaultConfig returns the default extractor configuration.
Defaults are tuned for typical regex patterns:
- MaxLiterals: 64 (handles most alternations without bloat)
- MaxLiteralLen: 64 (good cache locality for prefilters)
- MaxClassSize: 10 (small classes only, avoids [a-z] explosion)
Example:
extractor := literal.New(literal.DefaultConfig())
type InnerLiteralInfo ¶
type InnerLiteralInfo struct {
// Literals contains the inner literals for prefiltering
Literals *Seq
// InnerIdx is the index in concatenation where inner literal was found
InnerIdx int
// PrefixAST is the regex AST for the portion BEFORE the inner literal.
// This is used to build a reverse NFA for finding match start.
// For pattern `ERROR.*connection.*timeout`, PrefixAST represents `ERROR.*`
PrefixAST *syntax.Regexp
// SuffixAST is the regex AST for the portion FROM the inner literal onward.
// This is used to build a forward NFA for finding match end.
// For pattern `ERROR.*connection.*timeout`, SuffixAST represents `connection.*timeout`
SuffixAST *syntax.Regexp
}
InnerLiteralInfo contains information about an inner literal and its position. Used for ReverseInner strategy to identify literals suitable for bidirectional search.
The key insight from rust-regex: we need to split the AST into three parts:
- PrefixAST: the portion BEFORE the inner literal (for reverse NFA)
- Inner literal: for SIMD prefiltering
- SuffixAST: the portion FROM the inner literal onward (for forward NFA)
type Literal ¶
type Literal struct {
// Bytes contains the actual literal byte sequence.
Bytes []byte
// Complete indicates whether this literal represents the entire match.
// If true, matching this literal is sufficient (no regex engine needed).
// If false, this literal is just a necessary prefix/substring.
Complete bool
}
Literal represents a literal byte sequence extracted from a regex pattern. The Complete flag indicates whether this literal represents a complete match (true) or just a prefix/substring of potential matches (false).
Example:
- Pattern /hello/ → Literal{[]byte("hello"), true}
- Pattern /hello.*world/ → Literal{[]byte("hello"), false} (prefix only)
- Pattern /.*world/ → Literal{[]byte("world"), false} (suffix, but here treated as complete=false)
Example ¶
ExampleLiteral demonstrates basic Literal usage
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
// Complete literal - represents entire match
complete := literal.NewLiteral([]byte("hello"), true)
fmt.Printf("%s, length=%d\n", complete.String(), complete.Len())
// Incomplete literal - just a prefix
incomplete := literal.NewLiteral([]byte("world"), false)
fmt.Printf("%s, length=%d\n", incomplete.String(), incomplete.Len())
}
Output: literal{hello, complete=true}, length=5 literal{world, complete=false}, length=5
func NewLiteral ¶
NewLiteral creates a new Literal from the given byte sequence and completeness flag.
Example:
lit := literal.NewLiteral([]byte("hello"), true)
fmt.Printf("%s (complete=%v)\n", lit.Bytes, lit.Complete)
// Output: hello (complete=true)
func (Literal) Len ¶
Len returns the length of the literal in bytes.
Example:
lit := literal.NewLiteral([]byte("hello"), true)
fmt.Println(lit.Len()) // Output: 5
type Seq ¶
type Seq struct {
// contains filtered or unexported fields
}
Seq represents a sequence of alternative literals that can match. This is the foundation for prefilter optimization: we extract multiple possible literals from a regex (e.g., from alternations /foo|bar|baz/) and use them for fast candidate filtering.
Operations:
- Minimize: Remove redundant literals (e.g., "foo" makes "foobar" redundant for prefix matching)
- LongestCommonPrefix: Find shared prefix (e.g., "he" from ["hello", "help", "hero"])
- LongestCommonSuffix: Find shared suffix (e.g., "at" from ["cat", "bat", "rat"])
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("foo"), true),
literal.NewLiteral([]byte("bar"), true),
)
fmt.Printf("Sequence has %d literals\n", seq.Len()) // Output: Sequence has 2 literals
func NewSeq ¶
NewSeq creates a new sequence from the given literals.
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("hello"), true),
literal.NewLiteral([]byte("world"), true),
)
fmt.Println(seq.Len()) // Output: 2
Example with empty sequence:
seq := literal.NewSeq() fmt.Println(seq.IsEmpty()) // Output: true
func (*Seq) AllComplete ¶
AllComplete returns true if all literals in the sequence are complete. Complete literals represent full matches - no regex engine verification needed.
This is used to enable the "literal engine bypass" optimization: when all extracted literals are complete, we can skip DFA construction and use just the prefilter (Teddy/Memmem) for searching.
Example:
// For pattern "(foo|bar|baz)", literals are complete
literals := extractor.ExtractPrefixes(re)
if literals.AllComplete() {
// Use prefilter directly, skip DFA
}
func (*Seq) Clone ¶
Clone returns a deep copy of the sequence. All literals and their byte slices are duplicated.
Example:
original := literal.NewSeq(literal.NewLiteral([]byte("test"), true))
clone := original.Clone()
clone.Get(0).Bytes[0] = 'X' // Modifying clone doesn't affect original
fmt.Println(string(original.Get(0).Bytes)) // Output: test
Example ¶
ExampleSeq_Clone demonstrates deep copying
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
original := literal.NewSeq(
literal.NewLiteral([]byte("test"), true),
)
clone := original.Clone()
// Modify clone
clone.Minimize() // This won't affect original
fmt.Printf("Original length: %d\n", original.Len())
fmt.Printf("Clone length: %d\n", clone.Len())
}
Output: Original length: 1 Clone length: 1
func (*Seq) CrossForward ¶
CrossForward computes the cross-product of s with other, appending other's bytes to each literal in s. Only exact (Complete) literals in s are extended; inexact literals are kept as-is because they already represent truncated prefixes that cannot be meaningfully extended.
The result is Complete only if both the source literal and the other literal are Complete. This preserves the semantic that Complete means the literal captures the entire pattern up to this point.
If either s or other is empty, s is left unchanged.
Example:
s = ["ab", "cd"] (complete) other = ["x", "y"] (complete) s.CrossForward(other) → ["abx", "aby", "cdx", "cdy"]
func (*Seq) Dedup ¶
func (s *Seq) Dedup()
Dedup removes duplicate literals from the sequence, keeping the first occurrence. Two literals are considered duplicates if their byte content is identical, regardless of their Complete flag. When duplicates exist, the first occurrence is kept (which preserves the most accurate Complete status from earlier processing).
Example:
s = ["abc", "def", "abc", "ghi"] s.Dedup() → ["abc", "def", "ghi"]
func (*Seq) Get ¶
Get returns the literal at the specified index. Panics if index is out of bounds.
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("first"), true),
literal.NewLiteral([]byte("second"), true),
)
fmt.Println(string(seq.Get(0).Bytes)) // Output: first
fmt.Println(string(seq.Get(1).Bytes)) // Output: second
func (*Seq) IsEmpty ¶
IsEmpty returns true if the sequence has no literals.
Example:
empty := literal.NewSeq()
fmt.Println(empty.IsEmpty()) // Output: true
nonempty := literal.NewSeq(literal.NewLiteral([]byte("x"), true))
fmt.Println(nonempty.IsEmpty()) // Output: false
Example ¶
ExampleSeq_IsEmpty demonstrates empty sequence checks
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
empty := literal.NewSeq()
nonempty := literal.NewSeq(literal.NewLiteral([]byte("x"), true))
fmt.Printf("Empty sequence: %v\n", empty.IsEmpty())
fmt.Printf("Non-empty sequence: %v\n", nonempty.IsEmpty())
}
Output: Empty sequence: true Non-empty sequence: false
func (*Seq) IsFinite ¶
IsFinite returns true if the sequence represents a finite language. A sequence is finite if it has at least one literal.
In regex theory, a finite language is one with a bounded number of strings. For our purposes, any non-empty literal set represents a finite language.
Example:
seq := literal.NewSeq(literal.NewLiteral([]byte("hello"), true))
fmt.Println(seq.IsFinite()) // Output: true
empty := literal.NewSeq()
fmt.Println(empty.IsFinite()) // Output: false
Example ¶
ExampleSeq_IsFinite demonstrates finite language check
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
// A sequence with literals represents a finite language
finite := literal.NewSeq(literal.NewLiteral([]byte("test"), true))
// Empty sequence represents infinite/empty language
empty := literal.NewSeq()
fmt.Printf("Finite: %v\n", finite.IsFinite())
fmt.Printf("Empty: %v\n", empty.IsFinite())
}
Output: Finite: true Empty: false
func (*Seq) IsPartialCoverage ¶
IsPartialCoverage returns true if the literal set doesn't cover all alternation branches (due to overflow truncation). A partial-coverage prefilter cannot be used as a correctness gate in candidate loops — it would miss branches whose literals were not extracted. Rust avoids this by integrating prefilter as skip-ahead inside PikeVM, not as an external candidate loop.
func (*Seq) KeepFirstBytes ¶
KeepFirstBytes truncates all literals to at most n bytes. Truncated literals are marked as incomplete (Complete = false) since they no longer represent the full extracted sequence.
This is used for overflow handling when cross-product expansion exceeds limits: truncating to 4 bytes (Teddy fingerprint size) preserves prefilter effectiveness while bounding memory usage.
Example:
s = ["abcdef", "xy"] (complete) s.KeepFirstBytes(4) → ["abcd" (incomplete), "xy" (complete)]
func (*Seq) Len ¶
Len returns the number of literals in the sequence.
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("foo"), true),
literal.NewLiteral([]byte("bar"), true),
)
fmt.Println(seq.Len()) // Output: 2
func (*Seq) LongestCommonPrefix ¶
LongestCommonPrefix returns the longest common prefix of all literals in the sequence. If the sequence is empty or has no common prefix, returns an empty slice.
Algorithm:
- If sequence is empty, return empty slice
- Take first literal as candidate prefix
- For each other literal: - Find common prefix with current candidate - Update candidate to this shorter prefix
- Return final prefix
Time complexity: O(n * m) where n = number of literals, m = length of result prefix
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("hello"), true),
literal.NewLiteral([]byte("help"), true),
literal.NewLiteral([]byte("hero"), true),
)
prefix := seq.LongestCommonPrefix()
fmt.Println(string(prefix)) // Output: he
Example with no common prefix:
seq := literal.NewSeq(
literal.NewLiteral([]byte("abc"), true),
literal.NewLiteral([]byte("def"), true),
)
prefix := seq.LongestCommonPrefix()
fmt.Println(len(prefix)) // Output: 0
Example ¶
ExampleSeq_LongestCommonPrefix demonstrates finding common prefix
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
seq := literal.NewSeq(
literal.NewLiteral([]byte("hello"), true),
literal.NewLiteral([]byte("help"), true),
literal.NewLiteral([]byte("hero"), true),
)
prefix := seq.LongestCommonPrefix()
fmt.Printf("Common prefix: %s\n", prefix)
}
Output: Common prefix: he
Example (None) ¶
ExampleSeq_LongestCommonPrefix_none demonstrates no common prefix
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
seq := literal.NewSeq(
literal.NewLiteral([]byte("abc"), true),
literal.NewLiteral([]byte("def"), true),
)
prefix := seq.LongestCommonPrefix()
fmt.Printf("Common prefix length: %d\n", len(prefix))
}
Output: Common prefix length: 0
func (*Seq) LongestCommonSuffix ¶
LongestCommonSuffix returns the longest common suffix of all literals in the sequence. If the sequence is empty or has no common suffix, returns an empty slice.
Algorithm:
- Reverse all literals
- Find longest common prefix of reversed literals
- Reverse the result
Time complexity: O(n * m) where n = number of literals, m = length of result suffix
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("cat"), true),
literal.NewLiteral([]byte("bat"), true),
literal.NewLiteral([]byte("rat"), true),
)
suffix := seq.LongestCommonSuffix()
fmt.Println(string(suffix)) // Output: at
Example with no common suffix:
seq := literal.NewSeq(
literal.NewLiteral([]byte("abc"), true),
literal.NewLiteral([]byte("def"), true),
)
suffix := seq.LongestCommonSuffix()
fmt.Println(len(suffix)) // Output: 0
Example ¶
ExampleSeq_LongestCommonSuffix demonstrates finding common suffix
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
seq := literal.NewSeq(
literal.NewLiteral([]byte("cat"), true),
literal.NewLiteral([]byte("bat"), true),
literal.NewLiteral([]byte("rat"), true),
)
suffix := seq.LongestCommonSuffix()
fmt.Printf("Common suffix: %s\n", suffix)
}
Output: Common suffix: at
func (*Seq) Minimize ¶
func (s *Seq) Minimize()
Minimize removes redundant literals from the sequence.
For prefix matching, a literal L is redundant if there exists a shorter literal S that is a prefix of L. For example, in ["foo", "foobar"], "foo" makes "foobar" redundant because any string containing "foobar" also contains "foo".
Algorithm:
- Sort literals by length (shortest first)
- For each literal L: - Check if any shorter literal S is a prefix of L - If yes, L is redundant (skip it) - If no, keep L
Time complexity: O(n² * m) where n = number of literals, m = average literal length
Example:
seq := literal.NewSeq(
literal.NewLiteral([]byte("foo"), true),
literal.NewLiteral([]byte("foobar"), true),
)
seq.Minimize()
fmt.Println(seq.Len()) // Output: 1 (only "foo" remains)
Example with no redundancy:
seq := literal.NewSeq(
literal.NewLiteral([]byte("hello"), true),
literal.NewLiteral([]byte("world"), true),
)
seq.Minimize()
fmt.Println(seq.Len()) // Output: 2 (both remain)
Example ¶
ExampleSeq_Minimize demonstrates removing redundant literals
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
// For prefix matching, "foo" covers "foobar"
seq := literal.NewSeq(
literal.NewLiteral([]byte("foo"), true),
literal.NewLiteral([]byte("foobar"), true),
)
fmt.Printf("Before minimize: %d literals\n", seq.Len())
seq.Minimize()
fmt.Printf("After minimize: %d literals\n", seq.Len())
fmt.Printf("Remaining: %s\n", seq.Get(0).Bytes)
}
Output: Before minimize: 2 literals After minimize: 1 literals Remaining: foo
Example (Chain) ¶
ExampleSeq_Minimize_chain demonstrates chain redundancy removal
package main
import (
"fmt"
"github.com/donge/coregex/literal"
)
func main() {
// "a" covers "ab" which covers "abc"
seq := literal.NewSeq(
literal.NewLiteral([]byte("abc"), true),
literal.NewLiteral([]byte("ab"), true),
literal.NewLiteral([]byte("a"), true),
)
seq.Minimize()
fmt.Printf("Literals after minimize: %d\n", seq.Len())
fmt.Printf("Shortest literal wins: %s\n", seq.Get(0).Bytes)
}
Output: Literals after minimize: 1 Shortest literal wins: a