use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::RwLock;
use std::time::SystemTime;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
use windjammer::{
analyzer::{AnalyzedFunction, Analyzer},
lexer::Lexer,
parser::{Parser, Program},
};
use crate::symbol_table::SymbolTable;
pub struct AnalysisDatabase {
cache: RwLock<HashMap<Url, FileAnalysis>>,
}
#[derive(Clone)]
#[allow(dead_code)] struct FileAnalysis {
source_hash: u64,
source: String,
program: Option<Program>,
analyzed_functions: Vec<AnalyzedFunction>,
symbol_table: SymbolTable,
diagnostics: Vec<Diagnostic>,
timestamp: SystemTime,
}
impl AnalysisDatabase {
pub fn new() -> Self {
Self {
cache: RwLock::new(HashMap::new()),
}
}
pub fn analyze_file(&self, uri: &Url, content: &str) -> Vec<Diagnostic> {
let new_hash = Self::calculate_hash(content);
{
let cache = self.cache.read().unwrap();
if let Some(cached) = cache.get(uri) {
if cached.source_hash == new_hash {
tracing::debug!("File {} unchanged (hash match), using cached analysis", uri);
return cached.diagnostics.clone();
}
}
}
tracing::debug!("File {} changed or first analysis, re-analyzing", uri);
let (diagnostics, program, analyzed_functions) = self.full_analysis(content);
let mut symbol_table = SymbolTable::new();
if let Some(ref prog) = program {
symbol_table.build_from_program(prog, uri);
symbol_table.build_references_from_source(content, uri);
}
let analysis = FileAnalysis {
source_hash: new_hash,
source: content.to_string(),
program,
analyzed_functions,
symbol_table,
diagnostics: diagnostics.clone(),
timestamp: SystemTime::now(),
};
self.cache.write().unwrap().insert(uri.clone(), analysis);
diagnostics
}
fn calculate_hash(content: &str) -> u64 {
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
fn full_analysis(
&self,
content: &str,
) -> (Vec<Diagnostic>, Option<Program>, Vec<AnalyzedFunction>) {
let mut diagnostics = Vec::new();
let mut program_result = None;
let mut analyzed_functions = Vec::new();
let mut lexer = Lexer::new(content);
let tokens = lexer.tokenize_with_locations();
let mut parser = Parser::new(tokens);
match parser.parse() {
Ok(program) => {
tracing::debug!("File parsed successfully");
let mut analyzer = Analyzer::new();
match analyzer.analyze_program(&program) {
Ok((functions, _registry)) => {
tracing::debug!(
"Ownership analysis complete: {} functions",
functions.len()
);
analyzed_functions = functions;
}
Err(error) => {
tracing::warn!("Ownership analysis error: {}", error);
}
}
program_result = Some(program);
}
Err(error) => {
tracing::debug!("Parse error: {}", error);
diagnostics.push(Diagnostic {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 100,
},
},
severity: Some(DiagnosticSeverity::ERROR),
code: None,
code_description: None,
source: Some("windjammer".to_string()),
message: format!("Parse error: {}", error),
related_information: None,
tags: None,
data: None,
});
}
}
(diagnostics, program_result, analyzed_functions)
}
#[allow(dead_code)]
pub fn get_analysis(&self, uri: &Url) -> Option<Vec<Diagnostic>> {
self.cache
.read()
.unwrap()
.get(uri)
.map(|analysis| analysis.diagnostics.clone())
}
pub fn get_program(&self, uri: &Url) -> Option<Program> {
self.cache
.read()
.unwrap()
.get(uri)
.and_then(|analysis| analysis.program.clone())
}
pub fn get_analyzed_functions(&self, uri: &Url) -> Vec<AnalyzedFunction> {
self.cache
.read()
.unwrap()
.get(uri)
.map(|analysis| analysis.analyzed_functions.clone())
.unwrap_or_default()
}
pub fn get_symbol_table(&self, uri: &Url) -> Option<SymbolTable> {
self.cache
.read()
.unwrap()
.get(uri)
.map(|analysis| analysis.symbol_table.clone())
}
}
impl Default for AnalysisDatabase {
fn default() -> Self {
Self::new()
}
}