use std::collections::BTreeMap;
use super::merge::same_symbol;
use super::model::CallRelation;
use crate::registry_core::declaration::EvidenceKind;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallTreeNode {
pub symbol: String,
pub level: i32,
pub lane: usize,
pub parent: Option<usize>,
pub cut_callers: usize,
pub cut_callees: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CallTreeEdge {
pub caller: usize,
pub callee: usize,
pub evidence: EvidenceKind,
pub forward: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CallTree {
pub nodes: Vec<CallTreeNode>,
pub edges: Vec<CallTreeEdge>,
pub truncated: bool,
}
impl CallTree {
pub fn rows(&self) -> usize {
self.nodes
.iter()
.map(|node| node.lane + 1)
.max()
.unwrap_or(1)
}
pub fn level_span(&self) -> (i32, i32) {
self.nodes.iter().fold((0, 0), |(low, high), node| {
(low.min(node.level), high.max(node.level))
})
}
pub fn index_of(&self, symbol: &str) -> Option<usize> {
self.nodes
.iter()
.position(|node| same_symbol(&node.symbol, symbol))
}
pub fn backward_edges(&self) -> usize {
self.edges.iter().filter(|edge| !edge.forward).count()
}
}
pub fn call_tree(focus: &str, relations: &[CallRelation], depth: usize, limit: usize) -> CallTree {
let limit = limit.max(1);
let mut nodes = vec![CallTreeNode {
symbol: focus.to_owned(),
level: 0,
lane: 0,
parent: None,
cut_callers: 0,
cut_callees: 0,
}];
let mut truncated = false;
let mut queue = std::collections::VecDeque::from([0usize]);
while let Some(index) = queue.pop_front() {
let level = nodes[index].level;
if level.unsigned_abs() as usize >= depth {
continue;
}
let symbol = nodes[index].symbol.clone();
if level >= 0 {
for relation in relations
.iter()
.filter(|relation| same_symbol(&relation.caller, &symbol))
{
if let Some(placed) = place(
&relation.callee,
level + 1,
index,
&mut nodes,
limit,
&mut truncated,
) {
queue.push_back(placed);
}
}
}
if level <= 0 {
for relation in relations
.iter()
.filter(|relation| same_symbol(&relation.callee, &symbol))
{
if let Some(placed) = place(
&relation.caller,
level - 1,
index,
&mut nodes,
limit,
&mut truncated,
) {
queue.push_back(placed);
}
}
}
}
count_cuts(&mut nodes, relations);
let edges = collect_edges(&nodes, relations);
assign_lanes(&mut nodes);
CallTree {
nodes,
edges,
truncated,
}
}
fn place(
symbol: &str,
level: i32,
parent: usize,
nodes: &mut Vec<CallTreeNode>,
limit: usize,
truncated: &mut bool,
) -> Option<usize> {
if nodes.iter().any(|node| same_symbol(&node.symbol, symbol)) {
return None;
}
if nodes.len() >= limit {
*truncated = true;
return None;
}
nodes.push(CallTreeNode {
symbol: symbol.to_owned(),
level,
lane: 0,
parent: Some(parent),
cut_callers: 0,
cut_callees: 0,
});
Some(nodes.len() - 1)
}
fn count_cuts(nodes: &mut [CallTreeNode], relations: &[CallRelation]) {
let symbols = nodes
.iter()
.map(|node| node.symbol.clone())
.collect::<Vec<_>>();
for index in 0..nodes.len() {
let symbol = symbols[index].clone();
let mut missing_callers: Vec<String> = Vec::new();
let mut missing_callees: Vec<String> = Vec::new();
for relation in relations {
if same_symbol(&relation.callee, &symbol)
&& !symbols
.iter()
.any(|placed| same_symbol(placed, &relation.caller))
&& !missing_callers
.iter()
.any(|seen| same_symbol(seen, &relation.caller))
{
missing_callers.push(relation.caller.clone());
}
if same_symbol(&relation.caller, &symbol)
&& !symbols
.iter()
.any(|placed| same_symbol(placed, &relation.callee))
&& !missing_callees
.iter()
.any(|seen| same_symbol(seen, &relation.callee))
{
missing_callees.push(relation.callee.clone());
}
}
nodes[index].cut_callers = missing_callers.len();
nodes[index].cut_callees = missing_callees.len();
}
}
fn collect_edges(nodes: &[CallTreeNode], relations: &[CallRelation]) -> Vec<CallTreeEdge> {
let mut edges: Vec<CallTreeEdge> = Vec::new();
for relation in relations {
let Some(caller) = nodes
.iter()
.position(|node| same_symbol(&node.symbol, &relation.caller))
else {
continue;
};
let Some(callee) = nodes
.iter()
.position(|node| same_symbol(&node.symbol, &relation.callee))
else {
continue;
};
let forward = nodes[callee].level > nodes[caller].level;
match edges
.iter_mut()
.find(|edge| edge.caller == caller && edge.callee == callee)
{
Some(edge) => {
if strength(relation.evidence) > strength(edge.evidence) {
edge.evidence = relation.evidence;
}
}
None => edges.push(CallTreeEdge {
caller,
callee,
evidence: relation.evidence,
forward,
}),
}
}
edges
}
fn strength(evidence: EvidenceKind) -> u8 {
match evidence {
EvidenceKind::Unknown => 0,
EvidenceKind::External => 1,
EvidenceKind::Source => 2,
EvidenceKind::Mir => 3,
EvidenceKind::Live => 4,
}
}
fn assign_lanes(nodes: &mut [CallTreeNode]) {
let mut children: Vec<Vec<usize>> = vec![Vec::new(); nodes.len()];
for (index, node) in nodes.iter().enumerate() {
if let Some(parent) = node.parent {
children[parent].push(index);
}
}
let mut taken: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
let mut next: BTreeMap<i32, usize> = BTreeMap::new();
assign_lane(0, nodes, &children, &mut taken, &mut next);
}
fn assign_lane(
index: usize,
nodes: &mut [CallTreeNode],
children: &[Vec<usize>],
taken: &mut BTreeMap<i32, Vec<usize>>,
next: &mut BTreeMap<i32, usize>,
) {
let level = nodes[index].level;
let base = if children[index].is_empty() {
let counter = next.entry(level).or_insert(0);
let lane = *counter;
*counter += 1;
lane
} else {
for child in children[index].clone() {
assign_lane(child, nodes, children, taken, next);
}
let lanes = children[index]
.iter()
.map(|child| nodes[*child].lane)
.collect::<Vec<_>>();
let low = lanes.iter().copied().min().unwrap_or(0);
let high = lanes.iter().copied().max().unwrap_or(0);
(low + high) / 2
};
let column = taken.entry(level).or_default();
let mut lane = base;
while column.contains(&lane) {
lane += 1;
}
column.push(lane);
nodes[index].lane = lane;
}
#[cfg(test)]
#[path = "call_tree_tests.rs"]
mod call_tree_tests;