agcodex_ast/
parser_cache.rs1use crate::types::ParsedAst;
5use lru::LruCache;
6use std::num::NonZeroUsize;
7use std::path::Path;
8use std::path::PathBuf;
9use std::sync::Arc;
10
11#[derive(Debug)]
13pub struct ParserCache {
14 cache: LruCache<PathBuf, Arc<ParsedAst>>,
15 max_size_bytes: usize,
16 current_size_bytes: usize,
17}
18
19impl ParserCache {
20 pub fn new(max_size_bytes: usize) -> Self {
22 let cap =
25 NonZeroUsize::new(100).unwrap_or_else(|| unsafe { NonZeroUsize::new_unchecked(1) });
26 Self {
27 cache: LruCache::new(cap),
28 max_size_bytes,
29 current_size_bytes: 0,
30 }
31 }
32
33 pub fn get(&mut self, path: &Path) -> Option<Arc<ParsedAst>> {
35 self.cache.get(&path.to_path_buf()).cloned()
36 }
37
38 pub fn insert(&mut self, path: PathBuf, ast: ParsedAst) {
40 let size = Self::estimate_size(&ast);
41
42 while self.current_size_bytes + size > self.max_size_bytes && !self.cache.is_empty() {
44 if let Some((_, evicted)) = self.cache.pop_lru() {
45 self.current_size_bytes -= Self::estimate_size(&evicted);
46 }
47 }
48
49 let arc_ast = Arc::new(ast);
51 if let Some((_, old)) = self.cache.push(path, arc_ast) {
52 self.current_size_bytes -= Self::estimate_size(&old);
53 }
54 self.current_size_bytes += size;
55 }
56
57 pub fn clear(&mut self) {
59 self.cache.clear();
60 self.current_size_bytes = 0;
61 }
62
63 pub fn stats(&self) -> CacheStats {
65 CacheStats {
66 entries: self.cache.len(),
67 size_bytes: self.current_size_bytes,
68 max_size_bytes: self.max_size_bytes,
69 hit_rate: 0.0, }
71 }
72
73 const fn estimate_size(ast: &ParsedAst) -> usize {
75 ast.source.len() + (ast.root_node.children_count * 64)
77 }
78
79 pub fn invalidate(&mut self, path: &Path) {
81 if let Some(removed) = self.cache.pop(&path.to_path_buf()) {
82 self.current_size_bytes -= Self::estimate_size(&removed);
83 }
84 }
85
86 pub fn contains(&self, path: &Path) -> bool {
88 self.cache.contains(&path.to_path_buf())
89 }
90}
91
92impl Clone for ParserCache {
93 fn clone(&self) -> Self {
94 let cap =
96 NonZeroUsize::new(100).unwrap_or_else(|| unsafe { NonZeroUsize::new_unchecked(1) });
97 let mut new_cache = LruCache::new(cap);
98
99 for (k, v) in self.cache.iter() {
101 new_cache.push(k.clone(), v.clone());
102 }
103
104 Self {
105 cache: new_cache,
106 max_size_bytes: self.max_size_bytes,
107 current_size_bytes: self.current_size_bytes,
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct CacheStats {
115 pub entries: usize,
116 pub size_bytes: usize,
117 pub max_size_bytes: usize,
118 pub hit_rate: f64,
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::language_registry::Language;
125 use crate::language_registry::LanguageRegistry;
126
127 #[test]
128 fn test_cache_basic() {
129 let mut cache = ParserCache::new(1024 * 1024); let registry = LanguageRegistry::new();
131
132 let code = "fn main() { println!(\"Hello\"); }";
133 let ast = registry.parse(&Language::Rust, code).unwrap();
134 let path = PathBuf::from("test.rs");
135
136 cache.insert(path.clone(), ast.clone());
138 assert!(cache.contains(&path));
139
140 let cached = cache.get(&path).unwrap();
141 assert_eq!(cached.source, code);
142
143 cache.invalidate(&path);
145 assert!(!cache.contains(&path));
146 }
147
148 #[test]
149 fn test_cache_eviction() {
150 let mut cache = ParserCache::new(100); let registry = LanguageRegistry::new();
152
153 for i in 0..10 {
155 let code = format!("fn func{}() {{ /* some code */ }}", i);
156 let ast = registry.parse(&Language::Rust, &code).unwrap();
157 let path = PathBuf::from(format!("test{}.rs", i));
158 cache.insert(path, ast);
159 }
160
161 assert!(cache.cache.len() < 10);
163 assert!(cache.current_size_bytes <= cache.max_size_bytes);
164 }
165}