pub mod regex_decompose;
use crate::tokenizer::{build_covering, CoveringSet};
#[derive(Debug, Clone)]
pub enum GramQuery {
And(Vec<GramQuery>),
Or(Vec<GramQuery>),
Grams(Vec<u64>),
All,
None,
}
impl GramQuery {
pub fn simplify(self) -> Self {
match self {
GramQuery::And(children) => {
let simplified: Vec<GramQuery> = children
.into_iter()
.map(|c| c.simplify())
.filter(|c| !matches!(c, GramQuery::All))
.collect();
match simplified.len() {
0 => GramQuery::All,
1 => simplified.into_iter().next().unwrap(),
_ => GramQuery::And(simplified),
}
}
GramQuery::Or(children) => {
let simplified: Vec<GramQuery> =
children.into_iter().map(|c| c.simplify()).collect();
if simplified.iter().any(|c| matches!(c, GramQuery::All)) {
return GramQuery::All;
}
match simplified.len() {
0 => GramQuery::None,
1 => simplified.into_iter().next().unwrap(),
_ => GramQuery::Or(simplified),
}
}
other => other,
}
}
}
#[derive(Debug, Clone)]
pub enum QueryRoute {
Literal,
IndexedRegex(GramQuery),
FullScan,
}
fn hir_contains_literal_newline(hir: ®ex_syntax::hir::Hir) -> bool {
use regex_syntax::hir::HirKind;
match hir.kind() {
HirKind::Literal(lit) => lit.0.contains(&b'\n'),
HirKind::Concat(subs) | HirKind::Alternation(subs) => {
subs.iter().any(hir_contains_literal_newline)
}
HirKind::Repetition(rep) => hir_contains_literal_newline(&rep.sub),
HirKind::Capture(cap) => hir_contains_literal_newline(&cap.sub),
_ => false,
}
}
pub fn route_query(pattern: &str, case_insensitive: bool) -> Result<QueryRoute, String> {
if pattern.contains('\n') {
return Err("literal \\n not allowed".to_string());
}
if case_insensitive && is_literal(pattern) {
return Ok(match build_covering(pattern.as_bytes()) {
Some(covering) if !covering.required.is_empty() => {
QueryRoute::IndexedRegex(GramQuery::Grams(covering.required))
}
_ => QueryRoute::FullScan,
});
}
if !case_insensitive && is_literal(pattern) {
return Ok(QueryRoute::Literal);
}
let hir = regex_syntax::ParserBuilder::new()
.case_insensitive(case_insensitive)
.utf8(false)
.build()
.parse(pattern)
.map_err(|e| e.to_string())?;
if hir_contains_literal_newline(&hir) {
return Err("literal \\n not allowed".to_string());
}
let gram_query = regex_decompose::decompose_hir(&hir).simplify();
Ok(match gram_query {
GramQuery::All | GramQuery::None => QueryRoute::FullScan,
q => QueryRoute::IndexedRegex(q),
})
}
pub fn is_literal(pattern: &str) -> bool {
!pattern.chars().any(|c| {
matches!(
c,
'.' | '*' | '+' | '?' | '[' | ']' | '{' | '}' | '(' | ')' | '|' | '^' | '$' | '\\'
)
})
}
pub fn literal_grams(pattern: &str) -> Option<CoveringSet> {
build_covering(pattern.as_bytes())
}