use std::collections::{HashSet, VecDeque};
use petgraph::graph::NodeIndex;
use petgraph::visit::EdgeRef;
use petgraph::Direction;
use crate::core::entity::EdgeKind;
use super::graph::{SymbolGraph, SymbolNode};
impl SymbolGraph {
pub fn callers_of(&self, symbol: &str, hops: usize) -> Vec<(String, String)> {
self.bfs_neighbors(symbol, hops, Direction::Incoming)
}
pub fn callees_of(&self, symbol: &str, hops: usize) -> Vec<(String, String)> {
self.bfs_neighbors(symbol, hops, Direction::Outgoing)
}
pub fn neighbors_by_edge(
&self,
symbol: &str,
edge_kinds: &[EdgeKind],
hops: usize,
) -> Vec<(String, String, EdgeKind)> {
let Some(start) = self.start_index(symbol, hops) else {
return Vec::new();
};
if edge_kinds.is_empty() {
return Vec::new();
}
let allowed: HashSet<&EdgeKind> = edge_kinds.iter().collect();
let mut out: Vec<(String, String, EdgeKind)> = Vec::new();
self.bfs_walk(
start,
hops,
&[Direction::Outgoing, Direction::Incoming],
|edge| allowed.contains(edge.weight()),
|node, edge| {
out.push((
node.symbol.clone(),
node.chunk_id.clone(),
edge.weight().clone(),
));
},
);
out
}
#[allow(clippy::type_complexity)]
pub fn graph_neighbors(
&self,
symbol: &str,
dirs: &[Direction],
edge_kinds: Option<&[EdgeKind]>,
hops: usize,
) -> Vec<(String, String, Option<String>, String)> {
let Some(start) = self.start_index(symbol, hops) else {
return Vec::new();
};
let allowed: Option<HashSet<&EdgeKind>> = edge_kinds.map(|ks| ks.iter().collect());
let mut out: Vec<(String, String, Option<String>, String)> = Vec::new();
self.bfs_walk(
start,
hops,
dirs,
|edge| allowed.as_ref().is_none_or(|a| a.contains(edge.weight())),
|node, edge| {
out.push((
node.symbol.clone(),
node.chunk_id.clone(),
node.kind.clone(),
edge.weight().tag().to_string(),
));
},
);
out
}
fn bfs_neighbors(&self, symbol: &str, hops: usize, dir: Direction) -> Vec<(String, String)> {
let Some(start) = self.start_index(symbol, hops) else {
return Vec::new();
};
let mut out: Vec<(String, String)> = Vec::new();
self.bfs_walk(
start,
hops,
&[dir],
|edge| edge.weight() == &EdgeKind::CallsFunction,
|node, _edge| {
out.push((node.symbol.clone(), node.chunk_id.clone()));
},
);
out
}
fn start_index(&self, symbol: &str, hops: usize) -> Option<NodeIndex> {
if hops == 0 {
return None;
}
self.by_symbol.get(symbol).copied()
}
fn bfs_walk<F, V>(
&self,
start: NodeIndex,
hops: usize,
dirs: &[Direction],
edge_filter: F,
mut on_visit: V,
) where
F: Fn(petgraph::graph::EdgeReference<'_, EdgeKind>) -> bool,
V: FnMut(&SymbolNode, petgraph::graph::EdgeReference<'_, EdgeKind>),
{
let mut visited: HashSet<NodeIndex> = HashSet::new();
visited.insert(start);
let mut queue: VecDeque<(NodeIndex, usize)> = VecDeque::new();
queue.push_back((start, 0));
while let Some((node, depth)) = queue.pop_front() {
if depth >= hops {
continue;
}
self.expand_node(
node,
depth,
dirs,
&edge_filter,
&mut on_visit,
&mut visited,
&mut queue,
);
}
}
#[allow(clippy::too_many_arguments)]
fn expand_node<F, V>(
&self,
node: NodeIndex,
depth: usize,
dirs: &[Direction],
edge_filter: &F,
on_visit: &mut V,
visited: &mut HashSet<NodeIndex>,
queue: &mut VecDeque<(NodeIndex, usize)>,
) where
F: Fn(petgraph::graph::EdgeReference<'_, EdgeKind>) -> bool,
V: FnMut(&SymbolNode, petgraph::graph::EdgeReference<'_, EdgeKind>),
{
for &dir in dirs {
for edge in self.graph.edges_directed(node, dir) {
if !edge_filter(edge) {
continue;
}
let nb = Self::neighbor_in_direction(edge, dir);
self.record_neighbor(nb, edge, depth, on_visit, visited, queue);
}
}
}
fn neighbor_in_direction(
edge: petgraph::graph::EdgeReference<'_, EdgeKind>,
dir: Direction,
) -> NodeIndex {
match dir {
Direction::Outgoing => edge.target(),
Direction::Incoming => edge.source(),
}
}
fn record_neighbor<V>(
&self,
nb: NodeIndex,
edge: petgraph::graph::EdgeReference<'_, EdgeKind>,
depth: usize,
on_visit: &mut V,
visited: &mut HashSet<NodeIndex>,
queue: &mut VecDeque<(NodeIndex, usize)>,
) where
V: FnMut(&SymbolNode, petgraph::graph::EdgeReference<'_, EdgeKind>),
{
if visited.insert(nb) {
let n = &self.graph[nb];
on_visit(n, edge);
queue.push_back((nb, depth + 1));
}
}
}