formualizer_eval/engine/arena/
error_arena.rs

1/// Efficient storage for Excel errors with message preservation
2use super::string_interner::{StringId, StringInterner};
3use formualizer_common::{ExcelError, ExcelErrorKind};
4use rustc_hash::FxHashMap;
5
6/// Reference to an error in the arena
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
8pub struct ErrorRef(u32);
9
10impl ErrorRef {
11    pub fn as_u32(self) -> u32 {
12        self.0
13    }
14
15    pub fn from_raw(raw: u32) -> Self {
16        Self(raw)
17    }
18}
19
20/// Stored error data
21#[derive(Debug, Clone)]
22struct ErrorData {
23    kind: ExcelErrorKind,
24    message_id: Option<StringId>,
25}
26
27/// Arena for efficient error storage with deduplication
28#[derive(Debug)]
29pub struct ErrorArena {
30    /// All stored errors
31    errors: Vec<ErrorData>,
32
33    /// Deduplication cache by (kind, message_id)
34    dedup_cache: FxHashMap<(ExcelErrorKind, Option<StringId>), ErrorRef>,
35
36    /// String interner for error messages
37    strings: StringInterner,
38}
39
40impl ErrorArena {
41    pub fn new() -> Self {
42        Self {
43            errors: Vec::new(),
44            dedup_cache: FxHashMap::default(),
45            strings: StringInterner::new(),
46        }
47    }
48
49    pub fn with_capacity(estimated_errors: usize) -> Self {
50        Self {
51            errors: Vec::with_capacity(estimated_errors),
52            dedup_cache: FxHashMap::default(),
53            strings: StringInterner::with_capacity(estimated_errors / 2),
54        }
55    }
56
57    /// Store an error and return a reference
58    pub fn insert(&mut self, error: &ExcelError) -> ErrorRef {
59        // Intern the message if present
60        let message_id = error.message.as_ref().map(|msg| self.strings.intern(msg));
61
62        // Check deduplication cache
63        let cache_key = (error.kind, message_id);
64        if let Some(&error_ref) = self.dedup_cache.get(&cache_key) {
65            return error_ref;
66        }
67
68        // Create new error entry
69        let error_data = ErrorData {
70            kind: error.kind,
71            message_id,
72        };
73
74        let idx = self.errors.len() as u32;
75        self.errors.push(error_data);
76
77        let error_ref = ErrorRef(idx);
78        self.dedup_cache.insert(cache_key, error_ref);
79
80        error_ref
81    }
82
83    /// Retrieve an error by reference
84    pub fn get(&self, error_ref: ErrorRef) -> Option<ExcelError> {
85        let error_data = self.errors.get(error_ref.0 as usize)?;
86
87        let message = error_data
88            .message_id
89            .map(|id| self.strings.resolve(id).to_string());
90
91        let mut error = ExcelError::new(error_data.kind);
92        if let Some(msg) = message {
93            error = error.with_message(msg);
94        }
95
96        Some(error)
97    }
98
99    /// Get memory usage statistics
100    pub fn memory_usage(&self) -> usize {
101        std::mem::size_of::<ErrorData>() * self.errors.capacity()
102            + self.strings.memory_usage()
103            + self.dedup_cache.len()
104                * std::mem::size_of::<((ExcelErrorKind, Option<StringId>), ErrorRef)>()
105    }
106
107    /// Get number of stored errors
108    pub fn len(&self) -> usize {
109        self.errors.len()
110    }
111
112    pub fn is_empty(&self) -> bool {
113        self.errors.is_empty()
114    }
115
116    /// Clear all errors
117    pub fn clear(&mut self) {
118        self.errors.clear();
119        self.dedup_cache.clear();
120        self.strings.clear();
121    }
122}
123
124impl Default for ErrorArena {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_error_storage_and_retrieval() {
136        let mut arena = ErrorArena::new();
137
138        let error = ExcelError::new(ExcelErrorKind::Div).with_message("Cannot divide by zero");
139
140        let error_ref = arena.insert(&error);
141        let retrieved = arena.get(error_ref).unwrap();
142
143        assert_eq!(retrieved.kind, ExcelErrorKind::Div);
144        assert_eq!(retrieved.message, Some("Cannot divide by zero".to_string()));
145    }
146
147    #[test]
148    fn test_error_deduplication() {
149        let mut arena = ErrorArena::new();
150
151        let error1 = ExcelError::new(ExcelErrorKind::Value).with_message("Invalid type");
152        let error2 = ExcelError::new(ExcelErrorKind::Value).with_message("Invalid type");
153
154        let ref1 = arena.insert(&error1);
155        let ref2 = arena.insert(&error2);
156
157        assert_eq!(ref1, ref2);
158        assert_eq!(arena.len(), 1);
159    }
160
161    #[test]
162    fn test_different_messages_not_deduplicated() {
163        let mut arena = ErrorArena::new();
164
165        let error1 = ExcelError::new(ExcelErrorKind::Value).with_message("Message 1");
166        let error2 = ExcelError::new(ExcelErrorKind::Value).with_message("Message 2");
167
168        let ref1 = arena.insert(&error1);
169        let ref2 = arena.insert(&error2);
170
171        assert_ne!(ref1, ref2);
172        assert_eq!(arena.len(), 2);
173    }
174
175    #[test]
176    fn test_error_without_message() {
177        let mut arena = ErrorArena::new();
178
179        let error = ExcelError::new(ExcelErrorKind::Na);
180        let error_ref = arena.insert(&error);
181        let retrieved = arena.get(error_ref).unwrap();
182
183        assert_eq!(retrieved.kind, ExcelErrorKind::Na);
184        assert_eq!(retrieved.message, None);
185    }
186}