Rework to support a basic query language.

Currently broken and needs tests/debugging.
This commit is contained in:
Stu Black 2025-04-11 22:44:42 -04:00
parent 6ec952ba16
commit 68fa7d2810
6 changed files with 561 additions and 291 deletions

46
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
@ -154,6 +163,12 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.21.3"
@ -178,6 +193,35 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "shatterc"
version = "0.1.0"
@ -186,6 +230,8 @@ dependencies = [
"clang",
"clap",
"ghost-cell",
"glob",
"regex",
]
[[package]]

View File

@ -8,3 +8,5 @@ bumpalo = { version = "3.17", features = ["collections"] }
clang = { version = "2.0", features = ["clang_10_0"] }
clap = { version = "4.5", features = ["derive"] }
ghost-cell = "0.2.6"
glob = "0.3.2"
regex = "1.11"

View File

@ -262,7 +262,7 @@ pub struct File<'arena, 'id> {
}
impl<'arena, 'id> File<'arena, 'id> {
pub fn path(&self) -> &Path {
pub fn path(&self) -> &'arena Path {
&self.path
}
@ -393,10 +393,7 @@ impl<'arena, 'id> Graph<'arena, 'id> {
}
}
pub fn find_fn_definition(
&mut self,
fn_name: &str,
) -> Option<FunctionDefinitionRef<'arena, 'id>> {
pub fn find_fn_definition(&self, fn_name: &str) -> Option<FunctionDefinitionRef<'arena, 'id>> {
self.fns.get(fn_name).copied()
}

View File

@ -1,49 +1,82 @@
use clang::{Clang, EntityKind, Index};
use clap::Parser;
use std::path::PathBuf;
use std::{collections::HashSet, path::PathBuf};
use crate::query::Target;
mod call_graph;
mod discover;
mod link;
mod output;
mod query;
#[derive(Parser, Debug)]
struct Args {
/// Files defining symbols to exclude. All symbols discovered in or
/// referenced by these files will be ignored.
/// Files or symbols to ignore, specified with a prefix (file:, fn:, enum:,
/// struct:, or type:).
#[arg(long)]
ignore: Vec<PathBuf>,
ignore: Vec<String>,
/// Files to search for symbols. The dependency graph of symbols will be loaded from these files.
/// Files to ignore. All symbols reachable from these files will also be
/// ignored. File name globbing ("/usr/include/*.h") and path globbing
/// ("/usr/include/linux/**/*.h") are supported.
#[arg(long)]
ignore_paths: Vec<String>,
/// Files, functions, and types to search for. All items reachable from them
/// will be output. If not specified, all files and symbols will be output.
#[arg(short, long)]
search: Vec<String>,
/// Files to search. Directories will be traversed, recursively. The
/// dependency graph of symbols will be loaded from these files. File name
/// globbing and path globbing are supported.
#[arg()]
files: Vec<PathBuf>,
search_paths: Vec<String>,
}
fn find_files(pattern: &str) -> Result<HashSet<PathBuf>, String> {
let mut paths = HashSet::new();
for entry in glob::glob(pattern).map_err(|e| format!("{}", e))? {
match entry {
Ok(path) => paths.insert(path),
Err(e) => return Err(format!("{}", e)),
};
}
Ok(paths)
}
fn main() -> Result<(), String> {
let args = Args::parse();
let mut search_paths = HashSet::new();
for pattern in args.search_paths.iter() {
search_paths.extend(find_files(pattern).map_err(|e| format!("{}", e))?);
}
let search_paths = search_paths;
let mut ignore_paths = HashSet::new();
for pattern in &args.ignore_paths {
ignore_paths.extend(find_files(pattern).map_err(|e| format!("{}", e))?);
}
let ignore_paths = ignore_paths;
let clang = Clang::new()?;
let index = Index::new(&clang, /*exclude=*/ false, /*diagnostics=*/ false);
let arena = bumpalo::Bump::new();
call_graph::new(&arena, |mut tok, mut graph| {
// Parse first, ask questions later.
let mut parsed = Vec::with_capacity(args.files.len());
for file in args.files.iter().chain(args.ignore.iter()) {
match index.parser(file).parse() {
Ok(p) => {
let file = match graph.find_or_add_file_with_path(&file) {
Ok(f) => f,
Err(_) => {
eprintln!("could not canonicalize file path: `{}`", file.display());
continue;
}
};
parsed.push((file, p));
}
Err(_) => eprintln!("could not parse file: `{}`", file.display()),
}
let all_paths: HashSet<_> = search_paths.union(&ignore_paths).collect();
let mut parsed = Vec::with_capacity(all_paths.len());
for file in &all_paths {
parsed.push((
graph
.find_or_add_file_with_path(&file)
.map_err(|e| format!("{}", e))?,
index.parser(file).parse()?,
));
}
let parsed = parsed;
@ -98,7 +131,39 @@ fn main() -> Result<(), String> {
}
let mut stdout = std::io::stdout();
output::to_dot(&graph, &tok, &args.ignore, &mut stdout).map_err(|e| format!("{:?}", e))?;
let mut q = query::Query::new();
q.ignore_defaults(&graph);
if args.search.is_empty() {
for target in graph.files().map(Target::from) {
q.find_reachable_from(target);
}
} else {
for target in args
.search
.iter()
.filter_map(|n| query::search_by_name(&graph, &n))
{
q.find_reachable_from(target);
}
}
for ignore in args
.ignore
.iter()
.filter_map(|n| query::search_by_name(&graph, n))
{
q.ignore(ignore);
}
for ignore_target in ignore_paths
.iter()
.filter_map(|p| graph.find_file_with_path(p))
.map(Target::from)
{
q.ignore_reachable_from(ignore_target);
}
let results = q.search(&tok);
eprintln!("found {} results", results.len());
output::to_dot(&tok, &results, &mut stdout).map_err(|e| format!("{:?}", e))?;
Ok(())
})

View File

@ -1,5 +1,4 @@
use std::{
collections::HashSet,
fmt,
io::{self, Write},
path::Path,
@ -7,107 +6,10 @@ use std::{
use ghost_cell::GhostToken;
use crate::call_graph::{Graph, Symbol, TypeDefinitionRef};
const IGNORE_FUNCTIONS: &'static [&'static str] = &[
"__bswap_16",
"__bswap_32",
"__bswap_64",
"__uint16_identity",
"__uint32_identity",
"__uint64_identity",
];
const IGNORE_TYPEDEFS: &[&'static str] = &[
"_Float32x",
"__mode_t",
"__suseconds64_t",
"__syscall_ulong_t",
"__blksize_t",
"__ino64_t",
"__socklen_t",
"_IO_lock_t",
"cookie_io_functions_t",
"FILE",
"__fsblkcnt64_t",
"__loff_t",
"_Float32",
"__gid_t",
"__intmax_t",
"__uint_least64_t",
"__timer_t",
"__dev_t",
"__cfloat128",
"__fsid_t",
"__uint_least32_t",
"__u_short",
"__time_t",
"__uint16_t",
"__suseconds_t",
"__uint8_t",
"__int_least16_t",
"__clockid_t",
"__blkcnt64_t",
"__fpos_t",
"__fsblkcnt_t",
"__fsfilcnt_t",
"__int_least64_t",
"__int16_t",
"__rlim64_t",
"__int64_t",
"__int_least32_t",
"__fpos64_t",
"__ino_t",
"cookie_seek_function_t",
"__u_quad_t",
"__sig_atomic_t",
"__FILE",
"__quad_t",
"fpos_t",
"__u_char",
"__uintmax_t",
"__uint_least8_t",
"__useconds_t",
"__fsfilcnt64_t",
"__fsword_t",
"__id_t",
"__caddr_t",
"off_t",
"__uid_t",
"va_list",
"__key_t",
"__intptr_t",
"_Float128",
"__rlim_t",
"__nlink_t",
"__int8_t",
"__int_least8_t",
"ssize_t",
"__clock_t",
"__syscall_slong_t",
"__uint64_t",
"__u_long",
"cookie_close_function_t",
"__blkcnt_t",
"__ssize_t",
"__off_t",
"__gnuc_va_list",
"size_t",
"cookie_write_function_t",
"__mbstate_t",
"_Float64",
"__uint32_t",
"cookie_read_function_t",
"__daddr_t",
"__int32_t",
"_Float64x",
"__off64_t",
"__uint_least16_t",
"__u_int",
"__pid_t",
];
const IGNORE_STRUCTS: &[&'static str] = &["_IO_FILE"];
use crate::{
call_graph::Symbol,
query::{Target, targets_from},
};
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
enum DisplayVertex<'a> {
@ -117,6 +19,7 @@ enum DisplayVertex<'a> {
Struct(&'a str),
Typedef(&'a str),
}
impl<'a> fmt::Display for DisplayVertex<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@ -129,178 +32,31 @@ impl<'a> fmt::Display for DisplayVertex<'a> {
}
}
fn find_reachable_from<'arena, 'id>(
graph: &Graph<'arena, 'id>,
tok: &GhostToken<'id>,
files: &[impl AsRef<Path>],
) -> HashSet<Symbol<'arena, 'id>> {
let mut reachable = HashSet::new();
for file in files {
let file = match graph.find_file_with_path(file.as_ref()) {
Some(f) => f,
None => return reachable,
};
let mut frontier = Vec::new();
let symbols = file
.borrow(tok)
.structs()
.map(Symbol::from)
.chain(file.borrow(tok).enums().map(Symbol::from))
.chain(file.borrow(tok).typedefs().map(Symbol::from))
.chain(file.borrow(tok).fns().map(Symbol::from));
for s in symbols {
if reachable.insert(s) {
frontier.push(s);
}
}
while let Some(s) = frontier.pop() {
match s {
Symbol::Enum(_) => (),
Symbol::Struct(s) => {
for s in s.borrow(tok).types().map(Symbol::from) {
if reachable.insert(s) {
frontier.push(s);
}
}
}
Symbol::Typedef(s) => {
for typ in s.borrow(tok).target() {
let s = Symbol::from(*typ);
if reachable.insert(s) {
frontier.push(s);
}
}
}
Symbol::Function(s) => {
let symbols = s
.borrow(tok)
.types()
.map(Symbol::from)
.chain(s.borrow(tok).fn_calls().map(Symbol::from));
for s in symbols {
if reachable.insert(s) {
frontier.push(s);
}
}
}
}
impl<'arena, 'id> From<(&GhostToken<'id>, Target<'arena, 'id>)> for DisplayVertex<'arena> {
fn from((tok, target): (&GhostToken<'id>, Target<'arena, 'id>)) -> Self {
match target {
Target::File(f) => DisplayVertex::File(f.borrow(tok).path()),
Target::Symbol(Symbol::Enum(e)) => DisplayVertex::Enum(e.borrow(tok).name()),
Target::Symbol(Symbol::Function(fun)) => DisplayVertex::Fun(fun.borrow(tok).name()),
Target::Symbol(Symbol::Struct(s)) => DisplayVertex::Struct(s.borrow(tok).name()),
Target::Symbol(Symbol::Typedef(t)) => DisplayVertex::Typedef(t.borrow(tok).name()),
}
}
reachable
}
pub fn to_dot<'arena, 'id>(
graph: &Graph<'arena, 'id>,
tok: &GhostToken<'id>,
ignore_paths: &[impl AsRef<Path>],
items: &[Target<'arena, 'id>],
out: &mut impl Write,
) -> io::Result<()> {
let ignore = find_reachable_from(graph, tok, ignore_paths);
eprintln!(
"will ignore {} symbol(s) from {} ignore path(s)",
ignore.len(),
ignore_paths.len()
);
let mut edges = HashSet::new();
writeln!(out, "digraph {{")?;
for file in graph.files() {
let file_name = file.borrow(tok).path();
for fun in file.borrow(tok).fns() {
let fn_name = fun.borrow(tok).name();
if IGNORE_FUNCTIONS.contains(&fn_name) || ignore.contains(&Symbol::Function(fun)) {
continue;
}
edges.insert((DisplayVertex::File(file_name), DisplayVertex::Fun(fn_name)));
for typ in fun.borrow(tok).types() {
match typ {
TypeDefinitionRef::Enum(typ) => {
let type_name = typ.borrow(tok).name();
if !ignore.contains(&Symbol::Enum(typ)) {
edges.insert((
DisplayVertex::Fun(fn_name),
DisplayVertex::Enum(type_name),
));
}
}
TypeDefinitionRef::Struct(typ) => {
let type_name = typ.borrow(tok).name();
if !ignore.contains(&Symbol::Struct(typ)) {
edges.insert((
DisplayVertex::Fun(fn_name),
DisplayVertex::Struct(type_name),
));
}
}
TypeDefinitionRef::Typedef(typ) => {
let type_name = typ.borrow(tok).name();
if !(IGNORE_TYPEDEFS.contains(&type_name)
|| ignore.contains(&Symbol::Typedef(typ)))
{
edges.insert((
DisplayVertex::Fun(fn_name),
DisplayVertex::Typedef(type_name),
));
}
}
}
}
for callee in fun.borrow(tok).fn_calls() {
let callee_name = callee.borrow(tok).name();
if IGNORE_FUNCTIONS.contains(&callee_name) {
continue;
}
edges.insert((DisplayVertex::Fun(fn_name), DisplayVertex::Fun(callee_name)));
}
}
for typ in file.borrow(tok).typedefs() {
let type_name = typ.borrow(tok).name();
if !(IGNORE_TYPEDEFS.contains(&type_name) || ignore.contains(&Symbol::Typedef(typ))) {
edges.insert((
DisplayVertex::File(file_name),
DisplayVertex::Typedef(type_name),
));
}
}
for struct_def in graph.structs() {
let struct_name = struct_def.borrow(tok).name();
if IGNORE_STRUCTS.contains(&struct_name) || ignore.contains(&Symbol::Struct(struct_def))
{
continue;
}
for typ in struct_def.borrow(tok).types() {
match typ {
TypeDefinitionRef::Enum(typ) => {
let type_name = typ.borrow(tok).name();
edges.insert((
DisplayVertex::Struct(struct_name),
DisplayVertex::Enum(type_name),
));
}
TypeDefinitionRef::Struct(typ) => {
let type_name = typ.borrow(tok).name();
edges.insert((
DisplayVertex::Struct(struct_name),
DisplayVertex::Struct(type_name),
));
}
TypeDefinitionRef::Typedef(typ) => {
let type_name = typ.borrow(tok).name();
if !IGNORE_TYPEDEFS.contains(&type_name) {
edges.insert((
DisplayVertex::Struct(struct_name),
DisplayVertex::Typedef(type_name),
));
}
}
}
}
}
}
for (src, dest) in &edges {
writeln!(out, "\"{}\" -> \"{}\"", src, dest)?;
for src in items {
let src_vertex = DisplayVertex::from((tok, *src));
for dest in targets_from(tok, *src) {
let dest = DisplayVertex::from((tok, dest));
writeln!(out, "\"{}\" -> \"{}\"", src_vertex, dest)?;
}
}
writeln!(out, "}}")

404
src/query.rs Normal file
View File

@ -0,0 +1,404 @@
use crate::call_graph::{
EnumDefinitionRef, FileRef, FunctionDefinitionRef, Graph, StructDefinitionRef, Symbol,
TypeDefinitionRef, TypedefRef,
};
use ghost_cell::GhostToken;
use regex::Regex;
use std::{
collections::HashSet,
hash::{Hash, Hasher},
iter,
path::Path,
ptr,
sync::OnceLock,
};
static SYMBOL_NAME: OnceLock<Regex> = OnceLock::new();
const IGNORE_FUNCTIONS: &'static [&'static str] = &[
"__bswap_16",
"__bswap_32",
"__bswap_64",
"__uint16_identity",
"__uint32_identity",
"__uint64_identity",
];
const IGNORE_TYPEDEFS: &[&'static str] = &[
"_Float32x",
"__mode_t",
"__suseconds64_t",
"__syscall_ulong_t",
"__blksize_t",
"__ino64_t",
"__socklen_t",
"_IO_lock_t",
"cookie_io_functions_t",
"FILE",
"__fsblkcnt64_t",
"__loff_t",
"_Float32",
"__gid_t",
"__intmax_t",
"__uint_least64_t",
"__timer_t",
"__dev_t",
"__cfloat128",
"__fsid_t",
"__uint_least32_t",
"__u_short",
"__time_t",
"__uint16_t",
"__suseconds_t",
"__uint8_t",
"__int_least16_t",
"__clockid_t",
"__blkcnt64_t",
"__fpos_t",
"__fsblkcnt_t",
"__fsfilcnt_t",
"__int_least64_t",
"__int16_t",
"__rlim64_t",
"__int64_t",
"__int_least32_t",
"__fpos64_t",
"__ino_t",
"cookie_seek_function_t",
"__u_quad_t",
"__sig_atomic_t",
"__FILE",
"__quad_t",
"fpos_t",
"__u_char",
"__uintmax_t",
"__uint_least8_t",
"__useconds_t",
"__fsfilcnt64_t",
"__fsword_t",
"__id_t",
"__caddr_t",
"off_t",
"__uid_t",
"va_list",
"__key_t",
"__intptr_t",
"_Float128",
"__rlim_t",
"__nlink_t",
"__int8_t",
"__int_least8_t",
"ssize_t",
"__clock_t",
"__syscall_slong_t",
"__uint64_t",
"__u_long",
"cookie_close_function_t",
"__blkcnt_t",
"__ssize_t",
"__off_t",
"__gnuc_va_list",
"size_t",
"cookie_write_function_t",
"__mbstate_t",
"_Float64",
"__uint32_t",
"cookie_read_function_t",
"__daddr_t",
"__int32_t",
"_Float64x",
"__off64_t",
"__uint_least16_t",
"__u_int",
"__pid_t",
];
const IGNORE_STRUCTS: &[&'static str] = &["_IO_FILE"];
pub fn default_ignores<'arena, 'id>(graph: &Graph<'arena, 'id>) -> Vec<Target<'arena, 'id>> {
IGNORE_FUNCTIONS
.iter()
.filter_map(|x| graph.find_fn_definition(x))
.map(Target::from)
.chain(
IGNORE_TYPEDEFS
.iter()
.filter_map(|x| graph.find_typedef(x))
.map(Target::from),
)
.chain(
IGNORE_STRUCTS
.iter()
.filter_map(|x| graph.find_struct_definition(x).map(Target::from)),
)
.collect()
}
#[derive(Clone, Copy)]
pub enum Target<'arena, 'id> {
File(FileRef<'arena, 'id>),
Symbol(Symbol<'arena, 'id>),
}
impl<'arena, 'id> From<FileRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(f: FileRef<'arena, 'id>) -> Self {
Target::File(f)
}
}
impl<'arena, 'id> From<StructDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(s: StructDefinitionRef<'arena, 'id>) -> Self {
Target::Symbol(Symbol::Struct(s))
}
}
impl<'arena, 'id> From<EnumDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(e: EnumDefinitionRef<'arena, 'id>) -> Self {
Target::Symbol(Symbol::Enum(e))
}
}
impl<'arena, 'id> From<FunctionDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(f: FunctionDefinitionRef<'arena, 'id>) -> Self {
Target::Symbol(Symbol::Function(f))
}
}
impl<'arena, 'id> From<TypedefRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(t: TypedefRef<'arena, 'id>) -> Self {
Target::Symbol(Symbol::Typedef(t))
}
}
impl<'arena, 'id> From<TypeDefinitionRef<'arena, 'id>> for Target<'arena, 'id> {
fn from(t: TypeDefinitionRef<'arena, 'id>) -> Self {
match t {
TypeDefinitionRef::Enum(e) => Target::from(e),
TypeDefinitionRef::Struct(s) => Target::from(s),
TypeDefinitionRef::Typedef(t) => Target::from(t),
}
}
}
impl<'arena, 'id> PartialEq for Target<'arena, 'id> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Target::File(a), Target::File(b)) => ptr::addr_eq(*a, *b),
(Target::Symbol(a), Target::Symbol(b)) => a == b,
_ => false,
}
}
}
impl<'arena, 'id> Eq for Target<'arena, 'id> {}
impl<'arena, 'id> Hash for Target<'arena, 'id> {
fn hash<H: Hasher>(&self, h: &mut H) {
match self {
Target::File(f) => ptr::hash(*f, h),
Target::Symbol(s) => Hash::hash(s, h),
}
}
}
pub fn search_by_name<'arena, 'id>(
graph: &Graph<'arena, 'id>,
name: &str,
) -> Option<Target<'arena, 'id>> {
let regex =
SYMBOL_NAME.get_or_init(|| Regex::new(r"^(file|fn|enum|struct|type):(.+?)").unwrap());
for (_, [typ, name]) in regex.captures_iter(name).map(|c| c.extract()) {
if typ == "file" {
return graph
.find_file_with_path(Path::new(name))
.map(|f| Target::File(f));
} else if typ == "fn" {
return graph
.find_fn_definition(name)
.map(|f| Target::Symbol(Symbol::Function(f)));
} else if typ == "enum" {
return graph
.find_enum_definition(name)
.map(|e| Target::Symbol(Symbol::Enum(e)));
} else if typ == "struct" {
return graph
.find_struct_definition(name)
.map(|s| Target::Symbol(Symbol::Struct(s)));
} else if typ == "type" {
return graph
.find_typedef(name)
.map(|t| Target::Symbol(Symbol::Typedef(t)));
}
}
None
}
pub fn targets_from<'a, 'arena: 'a, 'id>(
tok: &'a GhostToken<'id>,
src: Target<'arena, 'id>,
) -> Box<dyn Iterator<Item = Target<'arena, 'id>> + 'a> {
match src {
Target::File(f) => Box::new(
f.borrow(tok)
.structs()
.map(Target::from)
.chain(f.borrow(tok).enums().map(Target::from))
.chain(f.borrow(tok).structs().map(Target::from))
.chain(f.borrow(tok).typedefs().map(Target::from)),
),
Target::Symbol(Symbol::Enum(_)) => Box::new(iter::empty()),
Target::Symbol(Symbol::Function(f)) => Box::new(
f.borrow(tok)
.fn_calls()
.map(Target::from)
.chain(f.borrow(tok).types().map(Target::from)),
),
Target::Symbol(Symbol::Struct(s)) => Box::new(s.borrow(tok).types().map(Target::from)),
Target::Symbol(Symbol::Typedef(t)) => {
Box::new(t.borrow(tok).target().iter().copied().map(Target::from))
}
}
}
fn find_reachable_from<'arena, 'id>(
tok: &GhostToken<'id>,
targets: impl Iterator<Item = Target<'arena, 'id>>,
) -> HashSet<Target<'arena, 'id>> {
let mut reachable = HashSet::new();
let mut frontier: Vec<_> = targets.collect();
while let Some(t) = frontier.pop() {
for target in targets_from(tok, t) {
if reachable.insert(target) {
frontier.push(target);
}
}
}
reachable
}
#[derive(Default)]
pub struct Query<'arena, 'id> {
roots: HashSet<Target<'arena, 'id>>,
transitive_ignores: HashSet<Target<'arena, 'id>>,
ignores: HashSet<Target<'arena, 'id>>,
}
impl<'arena, 'id> Query<'arena, 'id> {
pub fn new() -> Self {
Self::default()
}
pub fn ignore_defaults(&mut self, graph: &Graph<'arena, 'id>) -> &mut Self {
self.ignores.extend(default_ignores(graph).into_iter());
self
}
pub fn find_reachable_from(&mut self, root: Target<'arena, 'id>) -> &mut Self {
self.roots.insert(root);
self
}
pub fn ignore_reachable_from(&mut self, ignore: Target<'arena, 'id>) -> &mut Self {
self.transitive_ignores.insert(ignore);
self
}
pub fn ignore(&mut self, ignore: Target<'arena, 'id>) -> &mut Self {
self.ignores.insert(ignore);
self
}
pub fn search(&self, tok: &GhostToken<'id>) -> Vec<Target<'arena, 'id>> {
let ignore: HashSet<_> = find_reachable_from(tok, self.ignores.iter().copied())
.union(&self.ignores)
.copied()
.collect();
eprintln!("will ignore up to {} items", ignore.len());
find_reachable_from(tok, self.roots.iter().copied())
.difference(&ignore)
.copied()
.collect()
}
}
#[cfg(test)]
mod test {
// use super::*;
// use std::collections::HashSet;
// use std::hash::{self, Hash};
#[test]
fn distinct_target_hash() {
todo!()
// let arena = bumpalo::Bump::new();
// new(&arena, |mut tok, mut graph| {
// let file = graph.find_or_add_file_with_path("/").unwrap();
// let f1 =
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "my_function"));
// let f2 =
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "my_function"));
// let mut h1 = hash::DefaultHasher::new();
// let mut h2 = hash::DefaultHasher::new();
// f1.hash(&mut h1);
// f1.hash(&mut h2);
// let h1 = h1.finish();
// let h2 = h2.finish();
// assert_eq!(h1, h2);
// let mut h2 = hash::DefaultHasher::new();
// f2.hash(&mut h2);
// let h2 = h2.finish();
// assert_eq!(h1, h2);
// });
}
#[test]
fn target_equality() {
todo!()
// let arena = bumpalo::Bump::new();
// new(&arena, |mut tok, mut graph| {
// let file = graph.find_or_add_file_with_path("/").unwrap();
// let f1 = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
// let f2 = graph.find_fn_definition("my_function").unwrap();
// assert!(ptr::addr_eq(f1, f2));
// let f2 = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
// assert!(ptr::addr_eq(f1, f2));
// let f1 = Symbol::Function(f1);
// let f2 = Symbol::Function(f2);
// assert_eq!(f1, f2);
// let f3 =
// Symbol::Function(graph.find_or_add_fn_definition(&mut tok, file, "other_function"));
// assert_ne!(f1, f3);
// });
}
#[test]
fn target_hashset_population() {
todo!()
// let arena = bumpalo::Bump::new();
// new(&arena, |mut tok, mut graph| {
// let mut set = HashSet::new();
// let file = graph.find_or_add_file_with_path("/").unwrap();
// let fun = graph.find_or_add_fn_definition(&mut tok, file, "my_function");
// set.insert(Symbol::Function(fun));
// assert_eq!(set.len(), 1);
// let typ = graph.find_or_add_typedef(&mut tok, file, "my_type");
// set.insert(Symbol::Typedef(typ));
// assert_eq!(set.len(), 2);
// let fun = graph.find_fn_definition("my_function").unwrap();
// set.insert(Symbol::Function(fun));
// assert_eq!(set.len(), 2);
// });
}
}