1use crate::ast::Program;
5use std::collections::HashMap;
6use std::collections::hash_map::DefaultHasher;
7use std::hash::{Hash, Hasher};
8
9#[derive(Debug)]
11pub struct ASTCache {
12 cache: HashMap<u64, Program>,
14 max_size: usize,
16 hits: usize,
18 misses: usize,
20}
21
22impl ASTCache {
23 pub fn new() -> Self {
25 Self::with_capacity(100)
26 }
27
28 pub fn with_capacity(max_size: usize) -> Self {
30 ASTCache {
31 cache: HashMap::with_capacity(max_size.min(100)),
32 max_size,
33 hits: 0,
34 misses: 0,
35 }
36 }
37
38 fn hash_code(code: &str) -> u64 {
40 let mut hasher = DefaultHasher::new();
41 code.hash(&mut hasher);
42 hasher.finish()
43 }
44
45 pub fn get(&mut self, code: &str) -> Option<Program> {
47 let hash = Self::hash_code(code);
48 if let Some(program) = self.cache.get(&hash) {
49 self.hits += 1;
50 Some(program.clone())
51 } else {
52 self.misses += 1;
53 None
54 }
55 }
56
57 pub fn insert(&mut self, code: &str, program: Program) {
59 let hash = Self::hash_code(code);
60
61 if self.cache.len() >= self.max_size {
63 let to_remove = (self.max_size / 10).max(1);
65 let keys_to_remove: Vec<u64> = self.cache.keys().take(to_remove).copied().collect();
66 for key in keys_to_remove {
67 self.cache.remove(&key);
68 }
69 }
70
71 self.cache.insert(hash, program);
72 }
73
74 pub fn clear(&mut self) {
76 self.cache.clear();
77 self.hits = 0;
78 self.misses = 0;
79 }
80
81 pub fn stats(&self) -> CacheStats {
83 CacheStats {
84 size: self.cache.len(),
85 max_size: self.max_size,
86 hits: self.hits,
87 misses: self.misses,
88 hit_rate: if self.hits + self.misses > 0 {
89 self.hits as f64 / (self.hits + self.misses) as f64
90 } else {
91 0.0
92 },
93 }
94 }
95}
96
97impl Default for ASTCache {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct CacheStats {
106 pub size: usize,
108 pub max_size: usize,
110 pub hits: usize,
112 pub misses: usize,
114 pub hit_rate: f64,
116}
117
118impl std::fmt::Display for CacheStats {
119 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
120 write!(
121 f,
122 "Cache Stats: size={}/{}, hits={}, misses={}, hit_rate={:.2}%",
123 self.size,
124 self.max_size,
125 self.hits,
126 self.misses,
127 self.hit_rate * 100.0
128 )
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_cache_basic() {
138 let mut cache = ASTCache::new();
139 let code = "Set X 10";
140
141 assert!(cache.get(code).is_none());
143 assert_eq!(cache.stats().misses, 1);
144
145 let program: Program = vec![];
147 cache.insert(code, program.clone());
148 assert!(cache.get(code).is_some());
149 assert_eq!(cache.stats().hits, 1);
150 }
151
152 #[test]
153 fn test_cache_capacity() {
154 let mut cache = ASTCache::with_capacity(5);
155 let program: Program = vec![];
156
157 for i in 0..10 {
159 let code = format!("Set X {}", i);
160 cache.insert(&code, program.clone());
161 }
162
163 assert!(cache.stats().size <= 5);
165 }
166}