oxur_repl/subprocess/
variable_store.rs

1//! Type-erased variable storage for subprocess runtime
2//!
3//! Provides type-erased storage for REPL variables that persists across
4//! evaluations within a subprocess session. Based on the evcxr pattern.
5//!
6//! Based on ODD-0026 Section 3.1 (VariableStore Pattern)
7
8use std::any::Any;
9use std::collections::HashMap;
10use std::sync::Mutex;
11
12/// Type-erased variable store
13///
14/// Stores variables with type erasure using `Box<dyn Any + 'static>`.
15/// Variables are owned by the store and cannot reference each other
16/// (which aligns with Lisp's immutable data structure semantics).
17///
18/// # Thread Safety
19///
20/// The store is protected by a Mutex and can be accessed from multiple
21/// threads safely. In practice, it's accessed via a global static in
22/// the subprocess binary.
23///
24/// # Constraints (ODD-0038 Decision 7)
25///
26/// - `Box<dyn Any + 'static>` requires owned data
27/// - No inter-variable references possible
28/// - Aligns with Lisp semantics (immutable data structures)
29///
30/// # Examples
31///
32/// ```
33/// use oxur_repl::subprocess::VariableStore;
34///
35/// let mut store = VariableStore::new();
36///
37/// // Store a value
38/// store.set("x".to_string(), 42i32);
39///
40/// // Retrieve with correct type
41/// let x: Option<&i32> = store.get("x");
42/// assert_eq!(x, Some(&42));
43///
44/// // Wrong type returns None
45/// let x: Option<&String> = store.get("x");
46/// assert_eq!(x, None);
47/// ```
48#[derive(Debug, Default)]
49pub struct VariableStore {
50    /// Map from variable name to type-erased value
51    variables: HashMap<String, Box<dyn Any + Send>>,
52}
53
54impl VariableStore {
55    /// Create a new empty variable store
56    pub fn new() -> Self {
57        Self { variables: HashMap::new() }
58    }
59
60    /// Store a variable with type erasure
61    ///
62    /// # Arguments
63    ///
64    /// * `name` - Variable name
65    /// * `value` - Value to store (must be `'static` and `Send`)
66    ///
67    /// # Type Parameters
68    ///
69    /// * `T` - Type of value (must be `'static` and `Send`)
70    pub fn set<T: 'static + Send>(&mut self, name: String, value: T) {
71        self.variables.insert(name, Box::new(value));
72    }
73
74    /// Get a variable with type checking
75    ///
76    /// Returns `Some(&T)` if the variable exists and has the correct type,
77    /// `None` otherwise.
78    ///
79    /// # Arguments
80    ///
81    /// * `name` - Variable name
82    ///
83    /// # Type Parameters
84    ///
85    /// * `T` - Expected type of variable
86    pub fn get<T: 'static>(&self, name: &str) -> Option<&T> {
87        self.variables.get(name).and_then(|boxed| boxed.downcast_ref::<T>())
88    }
89
90    /// Get a mutable reference to a variable
91    ///
92    /// Returns `Some(&mut T)` if the variable exists and has the correct type,
93    /// `None` otherwise.
94    ///
95    /// # Arguments
96    ///
97    /// * `name` - Variable name
98    ///
99    /// # Type Parameters
100    ///
101    /// * `T` - Expected type of variable
102    pub fn get_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T> {
103        self.variables.get_mut(name).and_then(|boxed| boxed.downcast_mut::<T>())
104    }
105
106    /// Check if a variable exists
107    pub fn contains(&self, name: &str) -> bool {
108        self.variables.contains_key(name)
109    }
110
111    /// Remove a variable
112    ///
113    /// Returns `true` if the variable existed and was removed
114    pub fn remove(&mut self, name: &str) -> bool {
115        self.variables.remove(name).is_some()
116    }
117
118    /// Get number of stored variables
119    pub fn len(&self) -> usize {
120        self.variables.len()
121    }
122
123    /// Check if store is empty
124    pub fn is_empty(&self) -> bool {
125        self.variables.is_empty()
126    }
127
128    /// Clear all variables
129    pub fn clear(&mut self) {
130        self.variables.clear();
131    }
132
133    /// List all variable names
134    pub fn names(&self) -> Vec<String> {
135        self.variables.keys().cloned().collect()
136    }
137}
138
139/// Thread-safe global variable store
140///
141/// Used in the subprocess binary to provide a global storage location
142/// for REPL variables. Protected by a Mutex for thread safety.
143#[allow(dead_code)] // Used in Phase 2 subprocess implementation
144static GLOBAL_STORE: Mutex<Option<VariableStore>> = Mutex::new(None);
145
146/// Global result value storage
147///
148/// Stores the result of the last expression evaluation for retrieval
149/// by the subprocess IPC protocol.
150static GLOBAL_RESULT: Mutex<Option<String>> = Mutex::new(None);
151
152/// Initialize the global variable store
153///
154/// Should be called once when the subprocess starts.
155#[allow(dead_code)] // Used in Phase 2 subprocess implementation
156pub fn init_global_store() {
157    let mut store = GLOBAL_STORE.lock().unwrap();
158    *store = Some(VariableStore::new());
159}
160
161/// Access the global variable store
162///
163/// Provides access to the global store via a closure. The store is
164/// locked while the closure executes.
165///
166/// # Panics
167///
168/// Panics if the global store has not been initialized.
169///
170/// # Examples
171///
172/// ```
173/// use oxur_repl::subprocess::{init_global_store, with_store};
174///
175/// init_global_store();
176///
177/// // Store a value
178/// with_store(|store| {
179///     store.set("x".to_string(), 42i32);
180/// });
181///
182/// // Retrieve a value
183/// let result = with_store(|store| {
184///     store.get::<i32>("x").copied()
185/// });
186///
187/// assert_eq!(result, Some(42));
188/// ```
189#[allow(dead_code)] // Used in Phase 2 subprocess implementation
190pub fn with_store<F, R>(f: F) -> R
191where
192    F: FnOnce(&mut VariableStore) -> R,
193{
194    let mut guard = GLOBAL_STORE.lock().unwrap();
195    let store =
196        guard.as_mut().expect("Global store not initialized - call init_global_store() first");
197    f(store)
198}
199
200/// Store the result of the last expression evaluation
201///
202/// Called by generated wrapper code to store the result value.
203/// The result is formatted as a Debug string for transmission.
204///
205/// # Examples
206///
207/// ```
208/// use oxur_repl::subprocess::set_result;
209///
210/// // Typically called by generated code:
211/// let value = 42;
212/// set_result(format!("{:?}", value));
213/// ```
214pub fn set_result(value: String) {
215    let mut result = GLOBAL_RESULT.lock().unwrap();
216    *result = Some(value);
217}
218
219/// Retrieve and clear the result of the last expression evaluation
220///
221/// Called by the subprocess IPC protocol after executing user code.
222/// Returns the result value and clears the global storage.
223///
224/// # Returns
225///
226/// The stored result string, or None if no result was stored.
227pub fn take_result() -> Option<String> {
228    let mut result = GLOBAL_RESULT.lock().unwrap();
229    result.take()
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_variable_store_basic() {
238        let mut store = VariableStore::new();
239
240        store.set("x".to_string(), 42i32);
241        assert_eq!(store.get::<i32>("x"), Some(&42));
242    }
243
244    #[test]
245    fn test_variable_store_type_mismatch() {
246        let mut store = VariableStore::new();
247
248        store.set("x".to_string(), 42i32);
249
250        // Wrong type returns None
251        assert_eq!(store.get::<String>("x"), None);
252        assert_eq!(store.get::<f64>("x"), None);
253    }
254
255    #[test]
256    fn test_variable_store_multiple_types() {
257        let mut store = VariableStore::new();
258
259        store.set("int_val".to_string(), 42i32);
260        store.set("str_val".to_string(), "hello".to_string());
261        store.set("bool_val".to_string(), true);
262
263        assert_eq!(store.get::<i32>("int_val"), Some(&42));
264        assert_eq!(store.get::<String>("str_val"), Some(&"hello".to_string()));
265        assert_eq!(store.get::<bool>("bool_val"), Some(&true));
266    }
267
268    #[test]
269    fn test_variable_store_overwrite() {
270        let mut store = VariableStore::new();
271
272        store.set("x".to_string(), 42i32);
273        assert_eq!(store.get::<i32>("x"), Some(&42));
274
275        store.set("x".to_string(), 100i32);
276        assert_eq!(store.get::<i32>("x"), Some(&100));
277    }
278
279    #[test]
280    fn test_variable_store_overwrite_different_type() {
281        let mut store = VariableStore::new();
282
283        store.set("x".to_string(), 42i32);
284        assert_eq!(store.get::<i32>("x"), Some(&42));
285
286        // Overwrite with different type
287        store.set("x".to_string(), "hello".to_string());
288        assert_eq!(store.get::<String>("x"), Some(&"hello".to_string()));
289        assert_eq!(store.get::<i32>("x"), None); // Old type no longer accessible
290    }
291
292    #[test]
293    fn test_variable_store_get_mut() {
294        let mut store = VariableStore::new();
295
296        store.set("x".to_string(), 42i32);
297
298        if let Some(x) = store.get_mut::<i32>("x") {
299            *x += 10;
300        }
301
302        assert_eq!(store.get::<i32>("x"), Some(&52));
303    }
304
305    #[test]
306    fn test_variable_store_contains() {
307        let mut store = VariableStore::new();
308
309        store.set("x".to_string(), 42i32);
310
311        assert!(store.contains("x"));
312        assert!(!store.contains("y"));
313    }
314
315    #[test]
316    fn test_variable_store_remove() {
317        let mut store = VariableStore::new();
318
319        store.set("x".to_string(), 42i32);
320        assert!(store.contains("x"));
321
322        assert!(store.remove("x"));
323        assert!(!store.contains("x"));
324        assert!(!store.remove("x")); // Second remove returns false
325    }
326
327    #[test]
328    fn test_variable_store_len() {
329        let mut store = VariableStore::new();
330
331        assert_eq!(store.len(), 0);
332        assert!(store.is_empty());
333
334        store.set("x".to_string(), 42i32);
335        assert_eq!(store.len(), 1);
336        assert!(!store.is_empty());
337
338        store.set("y".to_string(), "hello".to_string());
339        assert_eq!(store.len(), 2);
340    }
341
342    #[test]
343    fn test_variable_store_clear() {
344        let mut store = VariableStore::new();
345
346        store.set("x".to_string(), 42i32);
347        store.set("y".to_string(), "hello".to_string());
348        assert_eq!(store.len(), 2);
349
350        store.clear();
351        assert_eq!(store.len(), 0);
352        assert!(store.is_empty());
353    }
354
355    #[test]
356    fn test_variable_store_names() {
357        let mut store = VariableStore::new();
358
359        store.set("x".to_string(), 42i32);
360        store.set("y".to_string(), "hello".to_string());
361        store.set("z".to_string(), true);
362
363        let names = store.names();
364        assert_eq!(names.len(), 3);
365        assert!(names.contains(&"x".to_string()));
366        assert!(names.contains(&"y".to_string()));
367        assert!(names.contains(&"z".to_string()));
368    }
369
370    #[test]
371    fn test_variable_store_complex_types() {
372        let mut store = VariableStore::new();
373
374        // Store Vec
375        store.set("vec".to_string(), vec![1, 2, 3]);
376        assert_eq!(store.get::<Vec<i32>>("vec"), Some(&vec![1, 2, 3]));
377
378        // Store custom struct
379        #[derive(Debug, PartialEq)]
380        struct Point {
381            x: i32,
382            y: i32,
383        }
384
385        store.set("point".to_string(), Point { x: 10, y: 20 });
386        assert_eq!(store.get::<Point>("point"), Some(&Point { x: 10, y: 20 }));
387    }
388
389    #[test]
390    fn test_global_store() {
391        init_global_store();
392
393        // Store a value
394        with_store(|store| {
395            store.set("global_x".to_string(), 42i32);
396        });
397
398        // Retrieve the value
399        let result = with_store(|store| store.get::<i32>("global_x").copied());
400
401        assert_eq!(result, Some(42));
402
403        // Clear for next test
404        with_store(|store| store.clear());
405    }
406
407    #[test]
408    fn test_global_store_multiple_accesses() {
409        init_global_store();
410
411        with_store(|store| store.clear());
412
413        // Multiple accesses work correctly
414        with_store(|store| {
415            store.set("a".to_string(), 1i32);
416        });
417
418        with_store(|store| {
419            store.set("b".to_string(), 2i32);
420        });
421
422        let sum = with_store(|store| {
423            let a = store.get::<i32>("a").copied().unwrap_or(0);
424            let b = store.get::<i32>("b").copied().unwrap_or(0);
425            a + b
426        });
427
428        assert_eq!(sum, 3);
429    }
430}