mathhook_core/parser/
cache.rs

1//! Thread-local caching for parser performance optimization
2//!
3//! This module provides thread-local storage for expensive parser operations
4//! to avoid repeated allocations and computations. Based on Rust Performance
5//! Book recommendations for high-performance parsing.
6
7use crate::core::Expression;
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::thread_local;
11
12thread_local! {
13    /// Cache for parsed function names to avoid repeated string allocations
14    ///
15    /// This cache stores constructed function names like "bessel_j_indexed"
16    /// to avoid repeated format! calls during parsing.
17    static FUNCTION_NAME_CACHE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
18
19    /// Pre-allocated Vec for expression lists to avoid repeated allocations
20    ///
21    /// This buffer is reused for building Vec<Expression> during parsing
22    /// to minimize heap allocations.
23    static EXPR_LIST_BUFFER: RefCell<Vec<Expression>> = RefCell::new(Vec::with_capacity(16));
24
25    /// Cache for commonly used Expression instances
26    ///
27    /// Stores frequently used expressions like constants and simple operations
28    /// to avoid repeated construction.
29    static COMMON_EXPRESSIONS: RefCell<HashMap<&'static str, Expression>> = RefCell::new({
30        let mut map = HashMap::new();
31        map.insert("0", Expression::integer(0));
32        map.insert("1", Expression::integer(1));
33        map.insert("-1", Expression::integer(-1));
34        map.insert("2", Expression::integer(2));
35        map.insert("pi", Expression::pi());
36        map.insert("e", Expression::e());
37        map.insert("i", Expression::i());
38        map.insert("infinity", Expression::infinity());
39        map
40    });
41}
42
43/// Efficient function name construction with caching
44///
45/// Constructs function names like "bessel_j_indexed" and caches them
46/// to avoid repeated string allocations.
47///
48/// # Examples
49///
50/// ```rust
51/// use mathhook_core::parser::cache::get_cached_function_name;
52///
53/// let name1 = get_cached_function_name("bessel", "j_indexed");
54/// let name2 = get_cached_function_name("bessel", "j_indexed");
55/// // Second call reuses cached result
56/// ```
57///
58/// # Performance
59///
60/// - First call: O(n) string construction + HashMap insertion
61/// - Subsequent calls: O(1) HashMap lookup + clone
62pub fn get_cached_function_name(base: &str, suffix: &str) -> String {
63    FUNCTION_NAME_CACHE.with(|cache| {
64        let key = format!("{}_{}", base, suffix);
65        let mut cache = cache.borrow_mut();
66
67        cache.entry(key.clone()).or_insert_with(|| key).clone()
68    })
69}
70
71/// Reuse Vec allocations for expression lists
72///
73/// Builds a `Vec<Expression>` using a thread-local buffer to minimize
74/// heap allocations during parsing.
75///
76/// # Examples
77///
78/// ```rust
79/// use mathhook_core::parser::cache::build_expr_list;
80/// use mathhook_core::Expression;
81///
82/// let exprs = vec![Expression::integer(1), Expression::integer(2)];
83/// let result = build_expr_list(exprs);
84/// ```
85///
86/// # Performance
87///
88/// - Reuses pre-allocated Vec capacity
89/// - Single clone operation instead of multiple allocations
90/// - Thread-local storage avoids synchronization overhead
91pub fn build_expr_list(exprs: impl IntoIterator<Item = Expression>) -> Vec<Expression> {
92    EXPR_LIST_BUFFER.with(|buffer| {
93        let mut buffer = buffer.borrow_mut();
94        buffer.clear();
95        buffer.extend(exprs);
96        buffer.clone() // Clone the populated buffer
97    })
98}
99
100/// Get a commonly used expression from cache
101///
102/// Returns cached instances of frequently used expressions to avoid
103/// repeated construction.
104///
105/// # Examples
106///
107/// ```rust
108/// use mathhook_core::parser::cache::get_cached_expression;
109///
110/// let zero = get_cached_expression("0");
111/// let pi = get_cached_expression("pi");
112/// ```
113///
114/// # Performance
115///
116/// - O(1) HashMap lookup for cached expressions
117/// - Avoids repeated Expression construction for constants
118pub fn get_cached_expression(key: &'static str) -> Option<Expression> {
119    COMMON_EXPRESSIONS.with(|cache| cache.borrow().get(key).cloned())
120}
121
122/// Build a function expression with cached name construction
123///
124/// Combines function name caching with expression construction for
125/// optimal performance in indexed function parsing.
126///
127/// # Examples
128///
129/// ```rust
130/// use mathhook_core::parser::cache::build_cached_function;
131/// use mathhook_core::Expression;
132///
133/// let args = vec![Expression::integer(1), Expression::symbol("x")];
134/// let func = build_cached_function("bessel", "j_indexed", args);
135/// ```
136pub fn build_cached_function(base: &str, suffix: &str, args: Vec<Expression>) -> Expression {
137    let name = get_cached_function_name(base, suffix);
138    Expression::function(name, args)
139}
140
141/// Clear all thread-local caches
142///
143/// Useful for testing or when memory usage needs to be minimized.
144/// Should rarely be needed in normal operation.
145pub fn clear_caches() {
146    FUNCTION_NAME_CACHE.with(|cache| cache.borrow_mut().clear());
147    EXPR_LIST_BUFFER.with(|buffer| buffer.borrow_mut().clear());
148    COMMON_EXPRESSIONS.with(|cache| cache.borrow_mut().clear());
149}
150
151/// Get cache statistics for monitoring
152///
153/// Returns information about cache usage for performance monitoring
154/// and optimization.
155#[derive(Debug, Clone)]
156pub struct CacheStats {
157    pub function_name_cache_size: usize,
158    pub expr_list_buffer_capacity: usize,
159    pub common_expressions_size: usize,
160}
161
162/// Get current cache statistics
163///
164/// # Examples
165///
166/// ```rust
167/// use mathhook_core::parser::cache::get_cache_stats;
168///
169/// let stats = get_cache_stats();
170/// println!("Function name cache size: {}", stats.function_name_cache_size);
171/// ```
172pub fn get_cache_stats() -> CacheStats {
173    let function_name_cache_size = FUNCTION_NAME_CACHE.with(|cache| cache.borrow().len());
174    let expr_list_buffer_capacity = EXPR_LIST_BUFFER.with(|buffer| buffer.borrow().capacity());
175    let common_expressions_size = COMMON_EXPRESSIONS.with(|cache| cache.borrow().len());
176
177    CacheStats {
178        function_name_cache_size,
179        expr_list_buffer_capacity,
180        common_expressions_size,
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_function_name_caching() {
190        clear_caches(); // Start with clean cache
191
192        let name1 = get_cached_function_name("test", "function");
193        let name2 = get_cached_function_name("test", "function");
194
195        assert_eq!(name1, name2);
196        assert_eq!(name1, "test_function");
197
198        let stats = get_cache_stats();
199        assert_eq!(stats.function_name_cache_size, 1);
200    }
201
202    #[test]
203    fn test_expr_list_building() {
204        let exprs = vec![
205            Expression::integer(1),
206            Expression::integer(2),
207            Expression::integer(3),
208        ];
209
210        let result = build_expr_list(exprs.clone());
211        assert_eq!(result.len(), 3);
212        assert_eq!(result, exprs);
213    }
214
215    #[test]
216    fn test_cached_expressions() {
217        let zero = get_cached_expression("0");
218        let pi = get_cached_expression("pi");
219        let unknown = get_cached_expression("unknown");
220
221        assert!(zero.is_some());
222        assert!(pi.is_some());
223        assert!(unknown.is_none());
224    }
225
226    #[test]
227    fn test_cached_function_building() {
228        let args = vec![Expression::integer(1), Expression::symbol("x")];
229        let func = build_cached_function("bessel", "j", args.clone());
230
231        match func {
232            Expression::Function {
233                name,
234                args: func_args,
235            } => {
236                assert_eq!(name, "bessel_j");
237                assert_eq!(*func_args, args);
238            }
239            _ => panic!("Expected function expression"),
240        }
241    }
242
243    #[test]
244    fn test_cache_clearing() {
245        // Populate caches
246        let _name = get_cached_function_name("test", "clear");
247        let _expr = build_expr_list(vec![Expression::integer(1)]);
248
249        let stats_before = get_cache_stats();
250        assert!(stats_before.function_name_cache_size > 0);
251
252        clear_caches();
253
254        let stats_after = get_cache_stats();
255        assert_eq!(stats_after.function_name_cache_size, 0);
256    }
257}