use crate::ir;
use crate::ir::transform::constants::is_primitive_type;
use indexmap::{IndexMap, IndexSet};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{LazyLock, RwLock};
use super::ClassDict;
pub(super) static FILE_HASH_CACHE: LazyLock<RwLock<HashMap<String, String>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub(super) fn get_cached_file_hash(file_path: &str) -> String {
{
let cache = FILE_HASH_CACHE
.read()
.expect("file hash cache lock poisoned");
if let Some(h) = cache.get(file_path) {
return h.clone();
}
}
let path = std::path::Path::new(file_path);
let hash = if let Ok(content) = std::fs::read(path) {
format!("{:x}", chksum_md5::hash(&content))
} else {
"invalid".to_string() };
FILE_HASH_CACHE
.write()
.expect("file hash cache lock poisoned")
.insert(file_path.to_string(), hash.clone());
hash
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FileDependencies {
pub files: IndexMap<String, String>,
}
impl FileDependencies {
pub fn new() -> Self {
Self {
files: IndexMap::new(),
}
}
pub fn record(&mut self, file_path: &str, file_hash: &str) {
if !file_path.is_empty() && !self.files.contains_key(file_path) {
self.files
.insert(file_path.to_string(), file_hash.to_string());
}
}
pub fn is_valid(&self) -> bool {
for (file_path, expected_hash) in &self.files {
let current_hash = get_cached_file_hash(file_path);
if current_hash != *expected_hash {
return false;
}
}
true
}
}
pub(super) fn record_file_dep(deps: &mut FileDependencies, file_name: &str) {
if file_name.is_empty() || file_name == "<test>" {
return;
}
if deps.files.contains_key(file_name) {
return;
}
let hash = get_cached_file_hash(file_name);
deps.record(file_name, &hash);
}
#[derive(Debug, Clone)]
pub struct FlattenResult {
pub class: ir::ast::ClassDefinition,
pub dependencies: FileDependencies,
}
pub(super) fn compute_def_hash(def: &ir::ast::StoredDefinition) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
for (name, class) in &def.class_list {
hash_class_content(name, class, &mut hasher);
}
hasher.finish()
}
fn hash_class_content(name: &str, class: &ir::ast::ClassDefinition, hasher: &mut impl Hasher) {
name.hash(hasher);
std::mem::discriminant(&class.class_type).hash(hasher);
for (comp_name, comp) in &class.components {
comp_name.hash(hasher);
comp.type_name.to_string().hash(hasher);
std::mem::discriminant(&comp.variability).hash(hasher);
std::mem::discriminant(&comp.causality).hash(hasher);
comp.shape.hash(hasher);
}
for ext in &class.extends {
ext.comp.to_string().hash(hasher);
}
class.equations.len().hash(hasher);
for eq in &class.equations {
format!("{:?}", eq).hash(hasher);
}
class.algorithms.len().hash(hasher);
for stmt in &class.algorithms {
format!("{:?}", stmt).hash(hasher);
}
for (nested_name, nested_class) in &class.classes {
hash_class_content(nested_name, nested_class, hasher);
}
}
pub(super) fn build_dependency_graph(class_dict: &ClassDict) -> HashMap<String, Vec<String>> {
let mut deps: HashMap<String, Vec<String>> = HashMap::new();
for (class_name, class_def) in class_dict.iter() {
let mut parents = Vec::new();
for extend in &class_def.extends {
let parent_name = extend.comp.to_string();
if !is_primitive_type(&parent_name) {
if class_dict.contains_key(&parent_name) {
parents.push(parent_name);
} else {
let parts: Vec<&str> = class_name.split('.').collect();
for i in (0..parts.len()).rev() {
let prefix = parts[..i].join(".");
let candidate = if prefix.is_empty() {
parent_name.clone()
} else {
format!("{}.{}", prefix, parent_name)
};
if class_dict.contains_key(&candidate) {
parents.push(candidate);
break;
}
}
}
}
}
deps.insert(class_name.clone(), parents);
}
deps
}
pub(super) fn compute_dependency_levels(
class_dict: &ClassDict,
deps: &HashMap<String, Vec<String>>,
) -> Vec<Vec<String>> {
let mut levels: Vec<Vec<String>> = Vec::new();
let mut assigned: HashMap<String, usize> = HashMap::new();
let mut remaining: IndexSet<String> = class_dict.keys().cloned().collect();
while !remaining.is_empty() {
let mut current_level = Vec::new();
for class_name in remaining.iter() {
let parents = deps.get(class_name).map(|v| v.as_slice()).unwrap_or(&[]);
let all_parents_assigned = parents.iter().all(|p| {
assigned.contains_key(p) || !class_dict.contains_key(p)
});
if all_parents_assigned {
current_level.push(class_name.clone());
}
}
if current_level.is_empty() {
current_level = remaining.iter().cloned().collect();
}
let level_num = levels.len();
for class_name in ¤t_level {
assigned.insert(class_name.clone(), level_num);
remaining.swap_remove(class_name);
}
levels.push(current_level);
}
levels
}