#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use crate::checker::Checker;
use crate::node::Ancestors;
use crate::alterator::Alterator;
use crate::getter::Getter;
use crate::c_macro;
use crate::langs::*;
use crate::node::{Node, Tree};
use crate::preproc::{PreprocResults, visible_macros};
use crate::traits::*;
#[derive(Debug)]
pub struct Parser<T: LanguageInfo + Alterator + Checker + Getter> {
code: Vec<u8>,
tree: Tree,
phantom: PhantomData<T>,
}
type FilterFn<'a> = dyn for<'t, 'c> Fn(&Node<'t>, Ancestors<'t, 'c>) -> bool + 'a;
pub struct Filter<'a> {
filters: Vec<Box<FilterFn<'a>>>,
}
impl Filter<'_> {
#[must_use]
pub fn any<'t>(&self, node: &Node<'t>, ancestors: Ancestors<'t, '_>) -> bool {
for f in &self.filters {
if f(node, ancestors) {
return true;
}
}
false
}
}
#[inline]
fn get_fake_code<T: LanguageInfo>(
code: &[u8],
path: &Path,
pr: Option<Arc<PreprocResults>>,
) -> Option<Vec<u8>> {
if let Some(pr) = pr {
match T::lang() {
LANG::C | LANG::Cpp | LANG::Mozcpp => {
let macros = visible_macros(path, &pr.files);
c_macro::replace(code, ¯os)
}
_ => None,
}
} else {
None
}
}
impl<T: 'static + LanguageInfo + Alterator + Checker + Getter> ParserTrait for Parser<T> {
type Checker = T;
type Getter = T;
fn new(code: Vec<u8>, path: &Path, pr: Option<Arc<PreprocResults>>) -> Self {
let fake_code = get_fake_code::<T>(&code, path, pr);
let code = if let Some(fake) = fake_code {
fake
} else {
code
};
let tree = Tree::new::<T>(&code);
Self {
code,
tree,
phantom: PhantomData,
}
}
#[inline]
fn root(&self) -> Node<'_> {
self.tree.get_root()
}
#[inline]
fn code(&self) -> &[u8] {
&self.code
}
fn filters(&self, requested: &[String]) -> Filter<'_> {
let code = self.code();
let mut res: Vec<Box<FilterFn<'_>>> = Vec::new();
for f in requested {
let f = f.as_str();
match f {
"all" => res.push(Box::new(|_: &Node, _| -> bool { true })),
"call" => res.push(Box::new(|node: &Node, _| T::is_call(node))),
"comment" => res.push(Box::new(|node: &Node, _| T::is_comment(node))),
"error" => res.push(Box::new(|node: &Node, _| T::is_error(node))),
"string" => res.push(Box::new(move |node: &Node, ancestors| {
T::is_string_with_code(node, code, ancestors)
})),
"function" => res.push(Box::new(move |node: &Node, ancestors| {
T::is_func_with_code(node, code, ancestors)
})),
_ => {
if let Ok(n) = f.parse::<u16>() {
res.push(Box::new(move |node: &Node, _| -> bool {
node.kind_id() == n
}));
} else {
let f = f.to_owned();
res.push(Box::new(move |node: &Node, _| -> bool { node.kind() == f }));
}
}
}
}
if res.is_empty() {
res.push(Box::new(|_: &Node, _| -> bool { true }));
}
Filter { filters: res }
}
}
impl<T: 'static + LanguageInfo + Alterator + Checker + Getter> Parser<T> {
#[must_use]
pub fn from_tree(tree: tree_sitter::Tree, code: Vec<u8>) -> Self {
Self {
code,
tree: Tree::from_ts_tree(tree),
phantom: PhantomData,
}
}
#[must_use]
pub fn ts_tree(&self) -> &tree_sitter::Tree {
self.tree.as_ts_tree()
}
}
#[cfg(test)]
mod tests {
use crate::count::count;
use crate::langs::PythonParser;
use crate::traits::ParserTrait;
use std::path::PathBuf;
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
fn parse_python(source: &str) -> PythonParser {
PythonParser::new(source.as_bytes().to_vec(), &PathBuf::from("t.py"), None)
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
fn count_kind(source: &str, filter: &str) -> usize {
count(&parse_python(source), &[filter.to_string()]).0
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
#[test]
fn get_filters_exact_match_hits_named_kind() {
let src = "if x:\n pass\nelif y:\n pass\nelse:\n pass\n";
assert_eq!(count_kind(src, "if_statement"), 1);
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
#[test]
fn get_filters_no_substring_match() {
let src = "x = 1 + 2\ny = foo(3)\n";
assert_eq!(
count_kind(src, "expression"),
0,
"exact match must not collapse all *_expression kinds"
);
assert_eq!(count_kind(src, "assignment"), 2);
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
#[test]
fn get_filters_unknown_kind_returns_empty() {
let src = "x = 1\n";
assert_eq!(count_kind(src, "definitely_not_a_python_kind"), 0);
}
#[cfg(all(feature = "c", feature = "cpp", feature = "mozcpp", feature = "python"))]
#[test]
fn get_filters_empty_request_matches_every_node() {
let src = "if x:\n pass\nelse:\n y = foo(1 + 2)\n";
let parser = parse_python(src);
let everything = count(&parser, &["all".to_string()]).0;
let unfiltered = count(&parser, &[]).0;
assert!(
everything > 1,
"fixture must hold several nodes or this asserts nothing; got {everything}"
);
assert_eq!(
unfiltered, everything,
"an empty filter request must match what `all` matches"
);
}
}