use rustc_hash::FxHashMap;
use std::collections::HashSet;
use tree_sitter::{Node, Query};
use crate::capture::Capture;
use crate::result::{CaptureResult, QueryResult};
use crate::util::parse_number_literal;
#[derive(Debug)]
pub struct QueryTree {
query: Query,
captures: Vec<Capture>,
negations: Vec<NegativeQuery>,
variables: HashSet<String>,
id: usize,
}
type Cache = FxHashMap<CacheKey, Vec<QueryResult>>;
#[derive(Debug)]
pub struct NegativeQuery {
pub qt: Box<QueryTree>,
pub previous_capture_index: i64,
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct CacheKey {
query_id: usize,
node_id: usize,
}
impl<'a> QueryTree {
pub fn new(
query: Query,
captures: Vec<Capture>,
variables: HashSet<String>,
negations: Vec<NegativeQuery>,
id: usize,
) -> QueryTree {
QueryTree {
query,
captures,
variables,
negations,
id,
}
}
pub fn variables(&self) -> HashSet<String> {
let mut result = HashSet::new();
for c in &self.captures {
match c {
Capture::Variable(s, _) => {
result.insert(s.to_string());
}
Capture::Subquery(t) => {
let sub_vars = t.variables();
result.extend(sub_vars);
}
_ => (),
}
}
for neg in &self.negations {
result.extend(neg.qt.variables())
}
result
}
pub fn identifiers(&self) -> Vec<String> {
let mut result = Vec::new();
for c in &self.captures {
match c {
Capture::Check(s) => result.push(s.to_string()),
Capture::Subquery(t) => {
let mut sub_identifiers = t.identifiers();
result.append(&mut sub_identifiers);
}
_ => (),
}
}
result
}
pub fn matches(&self, root: Node, source: &str) -> Vec<QueryResult> {
let mut cache: Cache = FxHashMap::default();
let mut results = self.match_internal(root, source, &mut cache);
results.dedup();
results
}
fn match_internal(&self, root: Node, source: &str, cache: &mut Cache) -> Vec<QueryResult> {
let mut qc = tree_sitter::QueryCursor::new();
let num_patterns = self.query.pattern_count();
let mut pattern_results = Vec::with_capacity(num_patterns + 1);
for _ in 0..num_patterns {
pattern_results.push(Vec::new());
}
for m in qc.matches(&self.query, root, source.as_bytes()) {
pattern_results[m.pattern_index].extend(self.process_match(cache, source, &m));
}
let have_failed_pattern = pattern_results.iter().any(|pr| pr.is_empty());
if have_failed_pattern {
return vec![];
}
let mut merged_results = Vec::new();
for pr in pattern_results {
if merged_results.is_empty() {
merged_results.extend(pr)
} else {
merged_results = QueryTree::merge_query_results(&merged_results, &pr, source, true);
if merged_results.is_empty() {
return merged_results;
}
}
}
merged_results
.into_iter()
.filter(|result| {
let negative_query_matched = self.negations.iter().any(|neg| {
let negative_results = neg.qt.match_internal(root, source, cache);
negative_results.into_iter().any(|n| {
if n.merge(result, source, false).is_none() {
return false;
}
let index = neg.previous_capture_index;
if let Some(c) = result.get_capture_result(self.id, index as u32) {
if n.start_offset() < c.range.end {
return false;
}
};
if let Some(c) = result.get_capture_result(self.id, (index + 1) as u32) {
if n.start_offset() > c.range.start {
return false;
}
}
true
})
});
!negative_query_matched
})
.collect()
}
fn process_match(
&self,
cache: &mut Cache,
source: &str,
m: &tree_sitter::QueryMatch,
) -> Vec<QueryResult> {
let mut r = Vec::with_capacity(m.captures.len());
let mut vars: FxHashMap<String, usize> =
FxHashMap::with_capacity_and_hasher(self.variables.len(), Default::default());
let mut subqueries = Vec::new();
for c in m.captures {
let capture = &self.captures[c.index as usize];
let capture_result = CaptureResult {
range: c.node.byte_range(),
query_id: self.id,
capture_idx: c.index,
};
if !matches!(capture, Capture::Subquery(_)) {
r.push(capture_result)
}
match capture {
Capture::Variable(s, regex_constraint) => {
if let Some((negative, regex)) = regex_constraint {
let m = regex.is_match(&source[c.node.byte_range()]);
if (m && *negative) || (!m && !*negative) {
return vec![];
}
}
vars.insert(s.clone(), r.len() - 1);
}
Capture::Subquery(t) => {
subqueries.push((t, c));
}
Capture::Number(i) => {
if let Some(y) = parse_number_literal(&source[c.node.byte_range()]) {
if *i != y {
return vec![];
}
} else {
return vec![];
}
}
_ => (),
}
}
let function = if let Some(c) = r.first() {
c.range.clone()
} else {
0usize..0usize
};
let qr = QueryResult::new(r, vars, function);
let query_results = subqueries.iter().fold(vec![qr], |results, (t, c)| {
if results.is_empty() {
return results;
}
let key = CacheKey {
query_id: t.id,
node_id: c.node.id(),
};
let sub_results = match cache.get(&key) {
None => {
let v = t.match_internal(c.node, source, cache);
cache.insert(key.clone(), v);
cache.get(&key).unwrap()
}
Some(r) => r,
};
QueryTree::merge_query_results(&results, sub_results, source, false)
});
query_results
}
fn merge_query_results(
results: &[QueryResult],
sub_results: &[QueryResult],
source: &str,
enforce_ordering: bool,
) -> Vec<QueryResult> {
results
.iter()
.flat_map(move |r| {
sub_results
.iter()
.filter_map(move |s| r.merge(s, source, enforce_ordering))
})
.collect()
}
}