use super::{CallGraph, FunctionCallCollector};
use crate::Compiler::AST::{QuickFuncsSection, Position};
use crate::Compiler::Core::OperationalSettings;
use crate::ErrorManager::{ErrorManager, SemanticErrorType};
use std::collections::HashSet;
pub struct CycleDetectionValidator<'a> {
error_manager: ErrorManager,
operational_settings: &'a OperationalSettings,
}
impl<'a> CycleDetectionValidator<'a> {
pub fn new(operational_settings: &'a OperationalSettings) -> Self {
Self::new_with_error_manager(operational_settings, ErrorManager::get_shared_instance())
}
pub fn new_with_error_manager(
operational_settings: &'a OperationalSettings,
error_manager: ErrorManager,
) -> Self {
CycleDetectionValidator {
error_manager,
operational_settings,
}
}
pub fn validate_function_calls(&self, section: &QuickFuncsSection) -> bool {
self.log_debug("Building function call graph...");
let call_graph = self.build_call_graph(section);
if self.operational_settings.debug_mode >= crate::Compiler::AST::DebugMode::Regular {
self.log_debug(&call_graph.to_debug_string());
let stats = call_graph.get_statistics();
self.log_debug(&format!("Graph statistics: {}", stats));
}
self.log_debug("Detecting cycles...");
let cycles = call_graph.detect_cycles();
let mut has_errors = false;
if !cycles.is_empty() {
self.error_manager.log_warning(&format!(
"Detected {} circular dependency cycle(s)",
cycles.len()
));
for cycle in &cycles {
self.report_cycle(cycle, &call_graph);
}
has_errors = true;
} else {
self.log_debug("No cycles detected - call graph is acyclic (DAG)");
}
self.log_debug("Validating that all called functions are defined...");
if !self.validate_all_calls_are_defined(section, &call_graph) {
has_errors = true;
}
if self.operational_settings.debug_mode >= crate::Compiler::AST::DebugMode::Regular {
let stats = call_graph.get_statistics();
self.error_manager.log_info(&format!(
"Call graph validation complete: {}",
stats
));
}
!has_errors
}
fn build_call_graph(&self, section: &QuickFuncsSection) -> CallGraph {
let mut call_graph = CallGraph::new();
let mut collector = FunctionCallCollector::new(&mut call_graph);
for func in §ion.functions {
collector.analyze_function(func);
}
call_graph
}
fn report_cycle(&self, cycle: &[String], call_graph: &CallGraph) {
if cycle.len() < 2 {
return; }
let cycle_path = cycle.join(" → ");
let message = format!("Circular function call detected: {}", cycle_path);
let suggestion = self.build_cycle_suggestion(cycle);
let position = self.get_cycle_position(cycle, call_graph);
self.error_manager.add_semantic_error(
SemanticErrorType::InvalidReference, message,
position.line as i32,
position.column as i32,
Some("QUICKFUNCS".to_string()),
Some(suggestion),
);
self.report_cycle_details(cycle, call_graph);
}
fn get_cycle_position(&self, cycle: &[String], call_graph: &CallGraph) -> Position {
if cycle.len() >= 2 {
let sites = call_graph.get_call_sites(&cycle[0], &cycle[1]);
if let Some(site) = sites.first() {
return site.position;
}
}
Position::UNKNOWN
}
fn build_cycle_suggestion(&self, cycle: &[String]) -> String {
let mut suggestion = String::from("Remove or break the circular dependency.\n");
if cycle.len() == 2 && cycle[0] == cycle[cycle.len() - 1] {
suggestion.push_str(&format!(
"Function '{}' calls itself directly (recursion).\n\
DixScript does not support recursion.\n\
Consider restructuring '{}' to avoid self-reference.",
cycle[0], cycle[0]
));
} else {
suggestion.push_str(
"This is an indirect circular dependency.\n\
Consider one of these approaches:\n"
);
if cycle.len() >= 2 {
let last_idx = cycle.len() - 2;
suggestion.push_str(&format!(
" 1. Remove the call from '{}' to '{}'\n\
2. Extract shared logic into a new utility function\n\
3. Restructure the functions to avoid the circular dependency",
cycle[last_idx], cycle[cycle.len() - 1]
));
}
}
suggestion
}
fn report_cycle_details(&self, cycle: &[String], call_graph: &CallGraph) {
self.error_manager.log_error(" Cycle details:");
for i in 0..cycle.len() - 1 {
let caller = &cycle[i];
let callee = &cycle[i + 1];
let call_sites = call_graph.get_call_sites(caller, callee);
if let Some(call_site) = call_sites.first() {
if call_site.position.is_valid() {
self.error_manager.log_error(&format!(
" {}. {} calls {} at {}",
i + 1,
caller,
callee,
call_site.position
));
} else {
self.error_manager.log_error(&format!(
" {}. {} calls {}",
i + 1,
caller,
callee
));
}
} else {
self.error_manager.log_error(&format!(
" {}. {} calls {} (location unknown)",
i + 1,
caller,
callee
));
}
}
}
fn validate_all_calls_are_defined(
&self,
section: &QuickFuncsSection,
call_graph: &CallGraph,
) -> bool {
let defined_functions: HashSet<&str> = section
.functions
.iter()
.map(|f| f.name.as_str())
.collect();
let mut all_valid = true;
for func in §ion.functions {
let callees = call_graph.get_callees(&func.name);
for callee in callees {
if !defined_functions.contains(callee) {
let call_sites = call_graph.get_call_sites(&func.name, callee);
let position = call_sites
.first()
.map(|cs| cs.position)
.unwrap_or(Position::UNKNOWN);
let message = format!(
"Function '{}' calls undefined function '{}'",
func.name, callee
);
let suggestion = format!(
"Define function '{}' in the @QUICKFUNCS section, \
or remove the call if it's not needed.",
callee
);
self.error_manager.add_semantic_error(
SemanticErrorType::UndefinedReference,
message,
position.line as i32,
position.column as i32,
Some("QUICKFUNCS".to_string()),
Some(suggestion),
);
all_valid = false;
}
}
}
all_valid
}
pub fn get_execution_order(&self, section: &QuickFuncsSection) -> Option<Vec<String>> {
let call_graph = self.build_call_graph(section);
call_graph.get_topological_sort()
}
fn log_debug(&self, message: &str) {
if self.operational_settings.debug_mode >= crate::Compiler::AST::DebugMode::Regular {
self.error_manager.log_debug(&format!("[Cycle Detector] {}", message));
}
}
}