use crate::types::{Language, NodeKind};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ParsedQuery {
pub text: String,
pub kinds: Vec<NodeKind>,
pub languages: Vec<Language>,
pub path_filters: Vec<String>,
pub name_filters: Vec<String>,
}
fn unquote(value: &str) -> String {
if value.len() >= 2 && value.starts_with('"') && value.ends_with('"') {
value[1..value.len() - 1].to_string()
} else {
value.to_string()
}
}
fn tokenize(raw: &str) -> Vec<String> {
let chars: Vec<(usize, char)> = raw.char_indices().collect();
let mut tokens = Vec::new();
let mut pos = 0;
while pos < chars.len() {
while pos < chars.len() && chars[pos].1.is_whitespace() {
pos += 1;
}
if pos >= chars.len() {
break;
}
let start_byte = chars[pos].0;
let mut end_byte = raw.len();
while pos < chars.len() && !chars[pos].1.is_whitespace() {
if chars[pos].1 == '"' {
pos += 1;
while pos < chars.len() && chars[pos].1 != '"' {
pos += 1;
}
if pos >= chars.len() {
end_byte = raw.len();
break;
}
pos += 1;
if pos >= chars.len() {
end_byte = raw.len();
break;
}
continue;
}
pos += 1;
end_byte = if pos < chars.len() {
chars[pos].0
} else {
raw.len()
};
}
if pos < chars.len() {
end_byte = chars[pos].0;
}
tokens.push(raw[start_byte..end_byte].to_string());
}
tokens
}
pub fn parse_query(raw: &str) -> ParsedQuery {
let mut out = ParsedQuery::default();
let mut text_parts = Vec::new();
for tok in tokenize(raw) {
let Some(colon) = tok.find(':') else {
text_parts.push(tok);
continue;
};
if colon == 0 || colon == tok.len() - 1 {
text_parts.push(tok);
continue;
}
let key = tok[..colon].to_ascii_lowercase();
let value_raw = unquote(&tok[colon + 1..]);
if value_raw.is_empty() {
text_parts.push(tok);
continue;
}
match key.as_str() {
"kind" => match parse_node_kind(&value_raw) {
Ok(kind) => out.kinds.push(kind),
Err(_) => text_parts.push(tok),
},
"lang" | "language" => {
let lower = value_raw.to_ascii_lowercase();
match parse_language(&lower) {
Ok(language) => out.languages.push(language),
Err(_) => text_parts.push(tok),
}
}
"path" => out.path_filters.push(value_raw),
"name" => out.name_filters.push(value_raw),
_ => text_parts.push(tok),
}
}
out.text = text_parts.join(" ").trim().to_string();
out
}
fn parse_node_kind(value: &str) -> Result<NodeKind, serde_json::Error> {
serde_json::from_value(serde_json::Value::String(value.to_string()))
}
fn parse_language(value: &str) -> Result<Language, serde_json::Error> {
serde_json::from_value(serde_json::Value::String(value.to_string()))
}
pub fn bounded_edit_distance(a: &str, b: &str, max_dist: usize) -> usize {
if a == b {
return 0;
}
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let al = a_chars.len();
let bl = b_chars.len();
if al.abs_diff(bl) > max_dist {
return max_dist + 1;
}
if al == 0 {
return bl;
}
if bl == 0 {
return al;
}
let mut prev: Vec<usize> = (0..=bl).collect();
let mut cur = vec![0; bl + 1];
for i in 1..=al {
cur[0] = i;
let mut row_min = cur[0];
for j in 1..=bl {
let cost = usize::from(a_chars[i - 1] != b_chars[j - 1]);
let insertion = cur[j - 1] + 1;
let deletion = prev[j] + 1;
let substitution = prev[j - 1] + cost;
cur[j] = insertion.min(deletion).min(substitution);
row_min = row_min.min(cur[j]);
}
if row_min > max_dist {
return max_dist + 1;
}
std::mem::swap(&mut prev, &mut cur);
}
prev[bl]
}