literal

package
v0.0.0-...-2f994d5 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: MIT Imports: 4 Imported by: 0

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

Examples

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:

  1. Parse regex to AST (caller uses regexp/syntax.Parse)
  2. Walk AST to extract literals based on operation type (OpLiteral, OpConcat, etc.)
  3. Apply limits (MaxLiterals, MaxLiteralLen, MaxClassSize)
  4. 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

func (e *Extractor) ExtractInner(re *syntax.Regexp) *Seq

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:

  1. Pattern must be OpConcat (concatenation of parts)
  2. 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 (.*|.+|.?)
  3. 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

func (e *Extractor) ExtractPrefixes(re *syntax.Regexp) *Seq

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

func (e *Extractor) ExtractSuffixes(re *syntax.Regexp) *Seq

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

func NewLiteral(b []byte, complete bool) Literal

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

func (l Literal) Len() int

Len returns the length of the literal in bytes.

Example:

lit := literal.NewLiteral([]byte("hello"), true)
fmt.Println(lit.Len()) // Output: 5

func (Literal) String

func (l Literal) String() string

String returns a string representation of the literal for debugging purposes. Format: "literal{bytes, complete=true/false}"

Example:

lit := literal.NewLiteral([]byte("test"), true)
fmt.Println(lit.String()) // Output: literal{test, complete=true}

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

func NewSeq(lits ...Literal) *Seq

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

func (s *Seq) AllComplete() bool

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

func (s *Seq) Clone() *Seq

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

func (s *Seq) CrossForward(other *Seq)

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

func (s *Seq) Get(i int) Literal

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

func (s *Seq) IsEmpty() bool

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

func (s *Seq) IsFinite() bool

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

func (s *Seq) IsPartialCoverage() bool

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

func (s *Seq) KeepFirstBytes(n int)

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

func (s *Seq) Len() int

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

func (s *Seq) LongestCommonPrefix() []byte

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:

  1. If sequence is empty, return empty slice
  2. Take first literal as candidate prefix
  3. For each other literal: - Find common prefix with current candidate - Update candidate to this shorter prefix
  4. 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

func (s *Seq) LongestCommonSuffix() []byte

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:

  1. Reverse all literals
  2. Find longest common prefix of reversed literals
  3. 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:

  1. Sort literals by length (shortest first)
  2. 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

Jump to

Keyboard shortcuts

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