#[derive(PartialEq, Eq, Debug)]
pub enum FuzzyMatch<'a> {
Term(FuzzyTerm<'a>),
Or(Vec<FuzzyTerm<'a>>),
}
#[derive(PartialEq, Eq, Debug)]
pub struct FuzzyTerm<'a> {
pub kind: FuzzyTermKind,
pub term: &'a str,
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum FuzzyTermKind {
#[default]
Fuzzy,
Exact,
ExactBoundary,
PrefixExact,
SuffixExact,
InverseExact,
InversePrefixExact,
InverseSuffixExact,
}
pub fn parse_fuzzy_query(query: &str) -> Vec<FuzzyMatch<'_>> {
let mut matches: Vec<FuzzyMatch<'_>> = Vec::new();
let mut current_or_group: Vec<FuzzyTerm<'_>> = Vec::new();
let mut last_token_was_term = false;
for token_str in query.split_whitespace() {
if token_str == "|" {
if last_token_was_term {
if current_or_group.is_empty() {
if let Some(FuzzyMatch::Term(term)) = matches.pop() {
current_or_group.push(term);
} else {
last_token_was_term = false;
continue;
}
}
last_token_was_term = false;
} else {
last_token_was_term = false;
}
} else {
if let Some(parsed_term) = parse_individual_term(token_str) {
if !last_token_was_term && !current_or_group.is_empty() {
current_or_group.push(parsed_term);
} else {
if !current_or_group.is_empty() {
matches.push(FuzzyMatch::Or(std::mem::take(&mut current_or_group)));
}
matches.push(FuzzyMatch::Term(parsed_term));
}
last_token_was_term = true;
} else {
last_token_was_term = false;
}
}
}
if !current_or_group.is_empty() {
matches.push(FuzzyMatch::Or(current_or_group));
}
matches
}
fn parse_individual_term(token_str: &str) -> Option<FuzzyTerm<'_>> {
let fuzzy = if let Some(term) = token_str.strip_prefix("!^") {
FuzzyTerm {
kind: FuzzyTermKind::InversePrefixExact,
term,
}
} else if token_str.starts_with('!') && token_str.ends_with('$') {
FuzzyTerm {
kind: FuzzyTermKind::InverseSuffixExact,
term: &token_str[1..(token_str.len() - 1)],
}
} else if token_str.starts_with('\'') && token_str.ends_with('\'') {
FuzzyTerm {
kind: FuzzyTermKind::ExactBoundary,
term: &token_str[1..(token_str.len() - 1)],
}
} else if let Some(term) = token_str.strip_prefix('\'') {
FuzzyTerm {
kind: FuzzyTermKind::Exact,
term,
}
} else if let Some(term) = token_str.strip_prefix('^') {
FuzzyTerm {
kind: FuzzyTermKind::PrefixExact,
term,
}
} else if let Some(term) = token_str.strip_suffix('$') {
FuzzyTerm {
kind: FuzzyTermKind::SuffixExact,
term,
}
} else if let Some(term) = token_str.strip_prefix('!') {
FuzzyTerm {
kind: FuzzyTermKind::InverseExact,
term,
}
} else {
FuzzyTerm {
kind: FuzzyTermKind::Fuzzy,
term: token_str,
}
};
if fuzzy.term.is_empty() { None } else { Some(fuzzy) }
}