formualizer_eval/engine/arena/
error_arena.rs1use super::string_interner::{StringId, StringInterner};
3use formualizer_common::{ExcelError, ExcelErrorKind};
4use rustc_hash::FxHashMap;
5
6#[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#[derive(Debug, Clone)]
22struct ErrorData {
23 kind: ExcelErrorKind,
24 message_id: Option<StringId>,
25}
26
27#[derive(Debug)]
29pub struct ErrorArena {
30 errors: Vec<ErrorData>,
32
33 dedup_cache: FxHashMap<(ExcelErrorKind, Option<StringId>), ErrorRef>,
35
36 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 pub fn insert(&mut self, error: &ExcelError) -> ErrorRef {
59 let message_id = error.message.as_ref().map(|msg| self.strings.intern(msg));
61
62 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 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 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 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 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 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}