1use crate::types::SemanticInfo;
10use std::collections::HashMap;
11use std::sync::{Arc, RwLock};
12use std::time::{SystemTime, UNIX_EPOCH};
13use tracing::debug;
14
15#[derive(Debug, Clone)]
17struct CacheEntry<T> {
18 value: T,
20 timestamp: u64,
22 input_hash: u64,
24}
25
26#[derive(Debug, Clone, Default)]
28pub struct CacheMetrics {
29 pub hits: u64,
31 pub misses: u64,
33 pub invalidations: u64,
35 pub avg_analysis_time_ms: f64,
37}
38
39impl CacheMetrics {
40 pub fn hit_rate(&self) -> f64 {
42 let total = self.hits + self.misses;
43 if total == 0 {
44 0.0
45 } else {
46 (self.hits as f64 / total as f64) * 100.0
47 }
48 }
49}
50
51pub struct SemanticCache {
53 entries: Arc<RwLock<HashMap<String, CacheEntry<SemanticInfo>>>>,
55 metrics: Arc<RwLock<CacheMetrics>>,
57 max_size: usize,
59 current_size: Arc<RwLock<usize>>,
61}
62
63impl SemanticCache {
64 pub fn new() -> Self {
66 Self::with_size(100 * 1024 * 1024)
67 }
68
69 pub fn with_size(max_size: usize) -> Self {
71 Self {
72 entries: Arc::new(RwLock::new(HashMap::new())),
73 metrics: Arc::new(RwLock::new(CacheMetrics::default())),
74 max_size,
75 current_size: Arc::new(RwLock::new(0)),
76 }
77 }
78
79 pub fn get(&self, uri: &str, input_hash: u64) -> Option<SemanticInfo> {
81 let entries = self.entries.read().unwrap();
82
83 if let Some(entry) = entries.get(uri) {
84 if entry.input_hash == input_hash {
85 debug!("Cache hit for {}", uri);
86 let mut metrics = self.metrics.write().unwrap();
87 metrics.hits += 1;
88 return Some(entry.value.clone());
89 }
90 }
91
92 let mut metrics = self.metrics.write().unwrap();
93 metrics.misses += 1;
94 None
95 }
96
97 pub fn put(&self, uri: String, input_hash: u64, value: SemanticInfo) {
99 let estimated_size = self.estimate_size(&value);
100
101 let mut current_size = self.current_size.write().unwrap();
103 if *current_size + estimated_size > self.max_size {
104 self.evict_oldest();
105 *current_size = self.calculate_total_size();
106 }
107
108 let timestamp = SystemTime::now()
109 .duration_since(UNIX_EPOCH)
110 .unwrap()
111 .as_secs();
112
113 let entry = CacheEntry {
114 value,
115 timestamp,
116 input_hash,
117 };
118
119 let mut entries = self.entries.write().unwrap();
120 entries.insert(uri.clone(), entry);
121 *current_size += estimated_size;
122
123 debug!("Cached semantic info for {}", uri);
124 }
125
126 pub fn invalidate(&self, uri: &str) {
128 let mut entries = self.entries.write().unwrap();
129 if entries.remove(uri).is_some() {
130 let mut metrics = self.metrics.write().unwrap();
131 metrics.invalidations += 1;
132 debug!("Invalidated cache for {}", uri);
133 }
134 }
135
136 pub fn clear(&self) {
138 let mut entries = self.entries.write().unwrap();
139 entries.clear();
140 let mut current_size = self.current_size.write().unwrap();
141 *current_size = 0;
142 debug!("Cache cleared");
143 }
144
145 pub fn metrics(&self) -> CacheMetrics {
147 self.metrics.read().unwrap().clone()
148 }
149
150 fn estimate_size(&self, info: &SemanticInfo) -> usize {
152 let symbols_size = info.symbols.len() * 200;
154 let imports_size = info.imports.len() * 100;
155 let definitions_size = info.definitions.len() * 100;
156 let references_size = info.references.len() * 100;
157
158 symbols_size + imports_size + definitions_size + references_size
159 }
160
161 fn calculate_total_size(&self) -> usize {
163 let entries = self.entries.read().unwrap();
164 entries
165 .values()
166 .map(|entry| self.estimate_size(&entry.value))
167 .sum()
168 }
169
170 fn evict_oldest(&self) {
172 let mut entries = self.entries.write().unwrap();
173
174 if let Some((oldest_uri, _)) = entries
175 .iter()
176 .min_by_key(|(_, entry)| entry.timestamp)
177 .map(|(uri, entry)| (uri.clone(), entry.timestamp))
178 {
179 entries.remove(&oldest_uri);
180 debug!("Evicted oldest cache entry: {}", oldest_uri);
181 }
182 }
183}
184
185impl Default for SemanticCache {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191pub struct AstCache {
193 entries: Arc<RwLock<HashMap<String, CacheEntry<String>>>>,
195 metrics: Arc<RwLock<CacheMetrics>>,
197}
198
199impl AstCache {
200 pub fn new() -> Self {
202 Self {
203 entries: Arc::new(RwLock::new(HashMap::new())),
204 metrics: Arc::new(RwLock::new(CacheMetrics::default())),
205 }
206 }
207
208 pub fn get(&self, uri: &str, input_hash: u64) -> Option<String> {
210 let entries = self.entries.read().unwrap();
211
212 if let Some(entry) = entries.get(uri) {
213 if entry.input_hash == input_hash {
214 debug!("AST cache hit for {}", uri);
215 let mut metrics = self.metrics.write().unwrap();
216 metrics.hits += 1;
217 return Some(entry.value.clone());
218 }
219 }
220
221 let mut metrics = self.metrics.write().unwrap();
222 metrics.misses += 1;
223 None
224 }
225
226 pub fn put(&self, uri: String, input_hash: u64, value: String) {
228 let timestamp = SystemTime::now()
229 .duration_since(UNIX_EPOCH)
230 .unwrap()
231 .as_secs();
232
233 let entry = CacheEntry {
234 value,
235 timestamp,
236 input_hash,
237 };
238
239 let mut entries = self.entries.write().unwrap();
240 entries.insert(uri.clone(), entry);
241
242 debug!("Cached AST for {}", uri);
243 }
244
245 pub fn invalidate(&self, uri: &str) {
247 let mut entries = self.entries.write().unwrap();
248 if entries.remove(uri).is_some() {
249 let mut metrics = self.metrics.write().unwrap();
250 metrics.invalidations += 1;
251 debug!("Invalidated AST cache for {}", uri);
252 }
253 }
254
255 pub fn clear(&self) {
257 let mut entries = self.entries.write().unwrap();
258 entries.clear();
259 debug!("AST cache cleared");
260 }
261
262 pub fn metrics(&self) -> CacheMetrics {
264 self.metrics.read().unwrap().clone()
265 }
266}
267
268impl Default for AstCache {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274type SymbolIndexEntries = Arc<RwLock<HashMap<String, CacheEntry<HashMap<String, usize>>>>>;
276
277pub struct SymbolIndexCache {
279 entries: SymbolIndexEntries,
281 metrics: Arc<RwLock<CacheMetrics>>,
283}
284
285impl SymbolIndexCache {
286 pub fn new() -> Self {
288 Self {
289 entries: Arc::new(RwLock::new(HashMap::new())),
290 metrics: Arc::new(RwLock::new(CacheMetrics::default())),
291 }
292 }
293
294 pub fn get(&self, uri: &str, input_hash: u64) -> Option<HashMap<String, usize>> {
296 let entries = self.entries.read().unwrap();
297
298 if let Some(entry) = entries.get(uri) {
299 if entry.input_hash == input_hash {
300 debug!("Symbol index cache hit for {}", uri);
301 let mut metrics = self.metrics.write().unwrap();
302 metrics.hits += 1;
303 return Some(entry.value.clone());
304 }
305 }
306
307 let mut metrics = self.metrics.write().unwrap();
308 metrics.misses += 1;
309 None
310 }
311
312 pub fn put(&self, uri: String, input_hash: u64, value: HashMap<String, usize>) {
314 let timestamp = SystemTime::now()
315 .duration_since(UNIX_EPOCH)
316 .unwrap()
317 .as_secs();
318
319 let entry = CacheEntry {
320 value,
321 timestamp,
322 input_hash,
323 };
324
325 let mut entries = self.entries.write().unwrap();
326 entries.insert(uri.clone(), entry);
327
328 debug!("Cached symbol index for {}", uri);
329 }
330
331 pub fn invalidate(&self, uri: &str) {
333 let mut entries = self.entries.write().unwrap();
334 if entries.remove(uri).is_some() {
335 let mut metrics = self.metrics.write().unwrap();
336 metrics.invalidations += 1;
337 debug!("Invalidated symbol index cache for {}", uri);
338 }
339 }
340
341 pub fn clear(&self) {
343 let mut entries = self.entries.write().unwrap();
344 entries.clear();
345 debug!("Symbol index cache cleared");
346 }
347
348 pub fn metrics(&self) -> CacheMetrics {
350 self.metrics.read().unwrap().clone()
351 }
352}
353
354impl Default for SymbolIndexCache {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360pub fn hash_input(input: &str) -> u64 {
362 use std::collections::hash_map::DefaultHasher;
363 use std::hash::{Hash, Hasher};
364
365 let mut hasher = DefaultHasher::new();
366 input.hash(&mut hasher);
367 hasher.finish()
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use crate::types::{Position, Range, Symbol, SymbolKind};
374
375 #[test]
376 fn test_semantic_cache_hit() {
377 let cache = SemanticCache::new();
378 let mut info = SemanticInfo::new();
379 info.symbols.push(Symbol::new(
380 "test".to_string(),
381 SymbolKind::Function,
382 Range::new(Position::new(0, 0), Position::new(0, 4)),
383 ));
384
385 let hash = hash_input("test code");
386 cache.put("file://test.rs".to_string(), hash, info.clone());
387
388 let cached = cache.get("file://test.rs", hash);
389 assert!(cached.is_some());
390 assert_eq!(cached.unwrap().symbols.len(), 1);
391
392 let metrics = cache.metrics();
393 assert_eq!(metrics.hits, 1);
394 assert_eq!(metrics.misses, 0);
395 }
396
397 #[test]
398 fn test_semantic_cache_miss() {
399 let cache = SemanticCache::new();
400 let info = SemanticInfo::new();
401
402 let hash = hash_input("test code");
403 cache.put("file://test.rs".to_string(), hash, info);
404
405 let cached = cache.get("file://test.rs", hash + 1);
407 assert!(cached.is_none());
408
409 let metrics = cache.metrics();
410 assert_eq!(metrics.hits, 0);
411 assert_eq!(metrics.misses, 1);
412 }
413
414 #[test]
415 fn test_semantic_cache_invalidation() {
416 let cache = SemanticCache::new();
417 let info = SemanticInfo::new();
418
419 let hash = hash_input("test code");
420 cache.put("file://test.rs".to_string(), hash, info);
421
422 cache.invalidate("file://test.rs");
423
424 let cached = cache.get("file://test.rs", hash);
425 assert!(cached.is_none());
426
427 let metrics = cache.metrics();
428 assert_eq!(metrics.invalidations, 1);
429 }
430
431 #[test]
432 fn test_ast_cache() {
433 let cache = AstCache::new();
434 let ast = "fn main() {}".to_string();
435
436 let hash = hash_input("fn main() {}");
437 cache.put("file://test.rs".to_string(), hash, ast.clone());
438
439 let cached = cache.get("file://test.rs", hash);
440 assert_eq!(cached, Some(ast));
441 }
442
443 #[test]
444 fn test_symbol_index_cache() {
445 let cache = SymbolIndexCache::new();
446 let mut index = HashMap::new();
447 index.insert("main".to_string(), 0);
448
449 let hash = hash_input("fn main() {}");
450 cache.put("file://test.rs".to_string(), hash, index.clone());
451
452 let cached = cache.get("file://test.rs", hash);
453 assert_eq!(cached, Some(index));
454 }
455
456 #[test]
457 fn test_cache_metrics_hit_rate() {
458 let metrics = CacheMetrics {
459 hits: 80,
460 misses: 20,
461 invalidations: 0,
462 avg_analysis_time_ms: 0.0,
463 };
464
465 assert_eq!(metrics.hit_rate(), 80.0);
466 }
467
468 #[test]
469 fn test_cache_clear() {
470 let cache = SemanticCache::new();
471 let info = SemanticInfo::new();
472
473 let hash = hash_input("test code");
474 cache.put("file://test.rs".to_string(), hash, info);
475
476 cache.clear();
477
478 let cached = cache.get("file://test.rs", hash);
479 assert!(cached.is_none());
480 }
481}