use crate::fxhash::FxHashMap;
use regex::Regex;
use std::sync::{Arc, LazyLock, RwLock};
const THREAD_LOCAL_CACHE_CAPACITY: usize = 128;
const GLOBAL_CACHE_CAPACITY: usize = 512;
static COMMON_REGEX_PATTERNS: LazyLock<FxHashMap<&'static str, Arc<Regex>>> = LazyLock::new(|| {
let mut patterns = FxHashMap::with_capacity_and_hasher(50, Default::default());
let common_patterns = [
(r"\s+", "Multiple whitespace"),
(r"^\s+|\s+$", "Leading/trailing whitespace"),
(r"\s", "Any whitespace"),
(r"\n+", "Multiple newlines"),
(r"\r\n", "Windows line ending"),
(r"[^\w\s]", "Non-word, non-space characters"),
(r"[^a-zA-Z0-9]", "Non-alphanumeric"),
(r"[^a-zA-Z0-9_]", "Non-alphanumeric except underscore"),
(
r"[^a-zA-Z0-9_-]",
"Non-alphanumeric except underscore and hyphen",
),
(r"[A-Z]", "Uppercase letters"),
(r"[a-z]", "Lowercase letters"),
(r"\d+", "Digits"),
(r"_+", "Multiple underscores"),
(r"-+", "Multiple hyphens"),
(r"\.+", "Multiple dots"),
(r"@", "At symbol (emails)"),
(r"\.", "Dot (domains, decimals)"),
(r"://", "Protocol separator"),
(r"https?://", "HTTP/HTTPS protocol"),
(r"\d{4}-\d{2}-\d{2}", "ISO date (YYYY-MM-DD)"),
(r"\d{2}:\d{2}:\d{2}", "Time format (HH:MM:SS)"),
(r"\d{4}/\d{2}/\d{2}", "US date format (YYYY/MM/DD)"),
(r"\d{2}/\d{2}/\d{4}", "Date format (MM/DD/YYYY)"),
(
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
"UUID format",
),
(r"[0-9a-fA-F]{32}", "UUID without hyphens"),
(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", "IPv4 address"),
(r"([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}", "IPv6 address"),
(r"[a-z]+([A-Z][a-z]+)*", "camelCase"),
(r"[a-z]+(_[a-z]+)*", "snake_case"),
(r"[a-z]+(-[a-z]+)*", "kebab-case"),
(r"[A-Z]+([A-Z][a-z]+)*", "PascalCase"),
(r"\$\d+(\.\d{2})?", "USD currency"),
(r"€\d+(\.\d{2})?", "EUR currency"),
(r"£\d+(\.\d{2})?", "GBP currency"),
(r"\d+(\.\d{2})?\s*(USD|EUR|GBP)", "Currency with code"),
(r"\d+\.\d+\.\d+", "Semantic version"),
(r"v\d+\.\d+", "Version prefix"),
(r"\.\w+$", "File extension"),
(r"/[^/]+", "Path segment"),
(r"\\[^\\]+", "Windows path segment"),
(r"\[.*?\]", "Square brackets with content"),
(r"\{.*?\}", "Curly braces with content"),
(r"\(.*?\)", "Parentheses with content"),
(r#"".*?""#, "Double quoted string"),
(r"'.*?'", "Single quoted string"),
(r"^(user|admin)_", "User/admin prefix"),
(r"^(User|Admin)_", "User/Admin prefix (capitalized)"),
(r"(user|admin)_", "User/admin anywhere"),
(r"(User|Admin)_", "User/Admin anywhere (capitalized)"),
(r"@example\.com", "Example email domain"),
(r"@example\.org", "Example org domain"),
(r"@company\.org", "Company org domain"),
(r"@company\.com", "Company com domain"),
(r"_id$", "Trailing _id suffix"),
(r"_ids$", "Trailing _ids suffix"),
(r"^id_", "Leading id_ prefix"),
(r"_at$", "Timestamp suffix (_at)"),
(r"_on$", "Date suffix (_on)"),
(r"^created_", "Created prefix"),
(r"^updated_", "Updated prefix"),
(r"^deleted_", "Deleted prefix"),
(r"^is_", "Boolean prefix (is_)"),
(r"^has_", "Boolean prefix (has_)"),
(r"^can_", "Boolean prefix (can_)"),
];
for (pattern, _) in &common_patterns {
if let Ok(regex) = Regex::new(pattern) {
patterns.insert(*pattern, Arc::new(regex));
}
}
patterns
});
static REGEX_CACHE: LazyLock<RwLock<FxHashMap<Arc<str>, Arc<Regex>>>> = LazyLock::new(|| {
RwLock::new(FxHashMap::with_capacity_and_hasher(
GLOBAL_CACHE_CAPACITY,
Default::default(),
))
});
thread_local! {
static THREAD_LOCAL_REGEX_CACHE: std::cell::RefCell<FxHashMap<Arc<str>, Arc<Regex>>> =
std::cell::RefCell::new(FxHashMap::with_capacity_and_hasher(THREAD_LOCAL_CACHE_CAPACITY, Default::default()));
}
#[inline]
fn evict_thread_local_half(cache: &mut FxHashMap<Arc<str>, Arc<Regex>>) {
let mut keep = true;
cache.retain(|_, _| {
keep = !keep;
keep
});
}
#[inline]
fn insert_thread_local(pattern: &str, regex: &Arc<Regex>) {
THREAD_LOCAL_REGEX_CACHE.with(|cache| {
let mut cache_ref = cache.borrow_mut();
if cache_ref.len() >= THREAD_LOCAL_CACHE_CAPACITY {
evict_thread_local_half(&mut cache_ref);
}
cache_ref.insert(Arc::from(pattern), Arc::clone(regex));
});
}
pub(crate) enum ParsedPattern<'a> {
Regex(&'a str),
Literal(&'a str),
}
#[inline]
pub(crate) fn parse_pattern(pattern: &str) -> ParsedPattern<'_> {
if pattern.len() >= 3 && pattern.starts_with("r'") && pattern.ends_with('\'') {
ParsedPattern::Regex(&pattern[2..pattern.len() - 1])
} else {
ParsedPattern::Literal(pattern)
}
}
pub(crate) fn get_cached_regex(pattern: &str) -> Result<Arc<Regex>, regex::Error> {
if let Some(regex) = COMMON_REGEX_PATTERNS.get(pattern) {
return Ok(Arc::clone(regex));
}
let thread_local_result = THREAD_LOCAL_REGEX_CACHE.with(|cache| {
let cache_ref = cache.borrow();
cache_ref.get(pattern).map(Arc::clone)
});
if let Some(regex) = thread_local_result {
return Ok(regex);
}
{
if let Some(regex) = REGEX_CACHE.read().unwrap().get(pattern) {
let regex_arc = Arc::clone(regex);
insert_thread_local(pattern, ®ex_arc);
return Ok(regex_arc);
}
}
let regex = Arc::new(Regex::new(pattern)?);
{
let mut cache = REGEX_CACHE.write().unwrap();
if let Some(existing) = cache.get(pattern) {
return Ok(Arc::clone(existing));
}
if cache.len() >= GLOBAL_CACHE_CAPACITY {
if let Some(key) = cache.keys().next().cloned() {
cache.remove(&key);
}
}
cache.insert(Arc::from(pattern), Arc::clone(®ex));
}
insert_thread_local(pattern, ®ex);
Ok(regex)
}