use super::call_site::CallSite;
use std::collections::{HashMap, HashSet};
use std::fmt;
use crate::Compiler::AST::Position;
#[derive(Debug, Clone)]
pub struct CallGraph {
adjacency_list: HashMap<String, HashSet<String>>,
call_sites: HashMap<String, Vec<CallSite>>,
all_functions: HashSet<String>,
}
impl CallGraph {
pub fn new() -> Self {
CallGraph {
adjacency_list: HashMap::new(),
call_sites: HashMap::new(),
all_functions: HashSet::new(),
}
}
pub fn add_function(&mut self, function_name: String) {
self.all_functions.insert(function_name.clone());
self.adjacency_list.entry(function_name).or_default();
}
pub fn add_edge(&mut self, caller: String, callee: String, position: Position) {
self.add_function(caller.clone());
self.add_function(callee.clone());
self.adjacency_list
.get_mut(&caller)
.unwrap()
.insert(callee.clone());
self.call_sites
.entry(caller.clone())
.or_default()
.push(CallSite::new(caller, callee, position));
}
pub fn has_cycles(&self) -> bool {
!self.detect_cycles().is_empty()
}
pub fn detect_cycles(&self) -> Vec<Vec<String>> {
let mut cycles = Vec::new();
let mut visited = HashSet::new();
let mut recursion_stack = HashSet::new();
let mut current_path = Vec::new();
for node in self.adjacency_list.keys() {
if !visited.contains(node) {
self.dfs(
node,
&mut visited,
&mut recursion_stack,
&mut current_path,
&mut cycles,
);
}
}
cycles
}
fn dfs(
&self,
node: &str,
visited: &mut HashSet<String>,
recursion_stack: &mut HashSet<String>,
current_path: &mut Vec<String>,
cycles: &mut Vec<Vec<String>>,
) -> bool {
visited.insert(node.to_string());
recursion_stack.insert(node.to_string());
current_path.push(node.to_string());
if let Some(neighbors) = self.adjacency_list.get(node) {
for neighbor in neighbors {
if !visited.contains(neighbor) {
if self.dfs(neighbor, visited, recursion_stack, current_path, cycles) {
return true;
}
} else if recursion_stack.contains(neighbor) {
if let Some(cycle_start_index) = current_path.iter().position(|n| *n == *neighbor) {
let mut cycle = current_path[cycle_start_index..].to_vec();
cycle.push(neighbor.clone());
cycles.push(cycle);
}
return true;
}
}
}
current_path.pop();
recursion_stack.remove(node);
false
}
pub fn get_callees(&self, function_name: &str) -> Vec<&str> {
self.adjacency_list
.get(function_name)
.map(|set| set.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn get_callers(&self, function_name: &str) -> Vec<&str> {
self.adjacency_list
.iter()
.filter(|(_, callees)| callees.contains(function_name))
.map(|(caller, _)| caller.as_str())
.collect()
}
pub fn is_function_called(&self, function_name: &str) -> bool {
self.adjacency_list
.values()
.any(|callees| callees.contains(function_name))
}
pub fn get_call_sites(&self, caller: &str, callee: &str) -> Vec<&CallSite> {
self.call_sites
.get(caller)
.map(|sites| {
sites
.iter()
.filter(|cs| cs.callee == callee)
.collect()
})
.unwrap_or_default()
}
pub fn get_all_call_sites(&self, caller: &str) -> Vec<&CallSite> {
self.call_sites
.get(caller)
.map(|sites| sites.iter().collect())
.unwrap_or_default()
}
pub fn get_topological_sort(&self) -> Option<Vec<String>> {
if self.has_cycles() {
return None;
}
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut result = Vec::new();
let mut queue = Vec::new();
for func in &self.all_functions {
in_degree.insert(func.clone(), 0);
}
for callees in self.adjacency_list.values() {
for callee in callees {
*in_degree.get_mut(callee).unwrap() += 1;
}
}
for (func, °ree) in &in_degree {
if degree == 0 {
queue.push(func.clone());
}
}
while let Some(node) = queue.pop() {
result.push(node.clone());
if let Some(neighbors) = self.adjacency_list.get(&node) {
for neighbor in neighbors {
let degree = in_degree.get_mut(neighbor).unwrap();
*degree -= 1;
if *degree == 0 {
queue.push(neighbor.clone());
}
}
}
}
if result.len() == self.all_functions.len() {
Some(result)
} else {
None
}
}
pub fn get_statistics(&self) -> CallGraphStats {
let total_edges = self.adjacency_list.values().map(|v| v.len()).sum();
let max_out_degree = self.adjacency_list.values().map(|v| v.len()).max().unwrap_or(0);
let functions_with_no_calls = self.adjacency_list.values().filter(|v| v.is_empty()).count();
let functions_never_called = self.all_functions
.iter()
.filter(|f| !self.is_function_called(f))
.count();
let has_cycles = self.has_cycles();
let cycle_count = self.detect_cycles().len();
CallGraphStats {
total_functions: self.all_functions.len(),
total_edges,
max_out_degree,
functions_with_no_calls,
functions_never_called,
has_cycles,
cycle_count,
}
}
pub fn to_debug_string(&self) -> String {
use std::fmt::Write;
let mut output = String::new();
writeln!(output, "Call Graph:").unwrap();
writeln!(output, " Functions: {}", self.all_functions.len()).unwrap();
writeln!(
output,
" Edges: {}",
self.adjacency_list.values().map(|v| v.len()).sum::<usize>()
).unwrap();
writeln!(output).unwrap();
let mut functions: Vec<_> = self.all_functions.iter().collect();
functions.sort();
for func in functions {
let callees = self.get_callees(func);
if !callees.is_empty() {
writeln!(output, " {} calls:", func).unwrap();
let mut sorted_callees = callees.clone();
sorted_callees.sort();
for callee in sorted_callees {
let sites = self.get_call_sites(func, callee);
if let Some(site) = sites.first() {
if site.position.is_valid() {
writeln!(output, " → {} at {}", callee, site.position).unwrap();
} else {
writeln!(output, " → {}", callee).unwrap();
}
} else {
writeln!(output, " → {}", callee).unwrap();
}
}
} else {
writeln!(output, " {} (no calls)", func).unwrap();
}
}
output
}
}
impl Default for CallGraph {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for CallGraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_debug_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CallGraphStats {
pub total_functions: usize,
pub total_edges: usize,
pub max_out_degree: usize,
pub functions_with_no_calls: usize,
pub functions_never_called: usize,
pub has_cycles: bool,
pub cycle_count: usize,
}
impl fmt::Display for CallGraphStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Functions: {}, Edges: {}, Max Out-Degree: {}, Leaf Functions: {}, Root Functions: {}, Cycles: {}",
self.total_functions,
self.total_edges,
self.max_out_degree,
self.functions_with_no_calls,
self.functions_never_called,
if self.has_cycles {
format!("{} detected", self.cycle_count)
} else {
"None".to_string()
}
)
}
}