Skip to main content

cairo_vm/hint_processor/builtin_hint_processor/
dict_manager.rs

1use std::collections::HashMap;
2
3use crate::{
4    types::relocatable::{MaybeRelocatable, Relocatable},
5    vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
6};
7
8#[derive(PartialEq, Eq, Debug, Clone)]
9///Manages dictionaries in a Cairo program.
10///Uses the segment index to associate the corresponding python dict with the Cairo dict.
11pub struct DictManager {
12    pub trackers: HashMap<isize, DictTracker>,
13}
14
15#[derive(PartialEq, Eq, Debug, Clone)]
16///Tracks the python dict associated with a Cairo dict.
17pub struct DictTracker {
18    //Dictionary.
19    pub data: Dictionary,
20    //Pointer to the first unused position in the dict segment.
21    pub current_ptr: Relocatable,
22}
23
24#[derive(PartialEq, Eq, Debug, Clone)]
25pub enum Dictionary {
26    SimpleDictionary(HashMap<MaybeRelocatable, MaybeRelocatable>),
27    DefaultDictionary {
28        dict: HashMap<MaybeRelocatable, MaybeRelocatable>,
29        default_value: MaybeRelocatable,
30    },
31}
32
33impl Dictionary {
34    fn get(&mut self, key: &MaybeRelocatable) -> Option<&MaybeRelocatable> {
35        match self {
36            Self::SimpleDictionary(dict) => dict.get(key),
37            Self::DefaultDictionary {
38                dict,
39                default_value,
40            } => Some(
41                dict.entry(key.clone())
42                    .or_insert_with(|| default_value.clone()),
43            ),
44        }
45    }
46
47    fn insert(&mut self, key: &MaybeRelocatable, value: &MaybeRelocatable) {
48        let dict = match self {
49            Self::SimpleDictionary(dict) => dict,
50            Self::DefaultDictionary {
51                dict,
52                default_value: _,
53            } => dict,
54        };
55        dict.insert(key.clone(), value.clone());
56    }
57}
58
59impl DictManager {
60    pub fn new() -> Self {
61        DictManager {
62            trackers: HashMap::<isize, DictTracker>::new(),
63        }
64    }
65    //Creates a new Cairo dictionary. The values of initial_dict can be integers, tuples or
66    //lists. See MemorySegments.gen_arg().
67    pub fn new_dict(
68        &mut self,
69        vm: &mut VirtualMachine,
70        initial_dict: HashMap<MaybeRelocatable, MaybeRelocatable>,
71    ) -> Result<MaybeRelocatable, HintError> {
72        let base = vm.add_memory_segment();
73        if self.trackers.contains_key(&base.segment_index) {
74            return Err(HintError::CantCreateDictionaryOnTakenSegment(
75                base.segment_index,
76            ));
77        };
78
79        self.trackers.insert(
80            base.segment_index,
81            DictTracker::new_with_initial(base, initial_dict),
82        );
83        Ok(MaybeRelocatable::RelocatableValue(base))
84    }
85
86    //Creates a new Cairo default dictionary
87    pub fn new_default_dict(
88        &mut self,
89        vm: &mut VirtualMachine,
90        default_value: &MaybeRelocatable,
91        initial_dict: Option<HashMap<MaybeRelocatable, MaybeRelocatable>>,
92    ) -> Result<MaybeRelocatable, HintError> {
93        let base = vm.add_memory_segment();
94        if self.trackers.contains_key(&base.segment_index) {
95            return Err(HintError::CantCreateDictionaryOnTakenSegment(
96                base.segment_index,
97            ));
98        }
99        self.trackers.insert(
100            base.segment_index,
101            DictTracker::new_default_dict(base, default_value, initial_dict),
102        );
103        Ok(MaybeRelocatable::RelocatableValue(base))
104    }
105
106    //Returns the tracker which's current_ptr matches with the given dict_ptr
107    pub fn get_tracker_mut(
108        &mut self,
109        dict_ptr: Relocatable,
110    ) -> Result<&mut DictTracker, HintError> {
111        let tracker = self
112            .trackers
113            .get_mut(&dict_ptr.segment_index)
114            .ok_or(HintError::NoDictTracker(dict_ptr.segment_index))?;
115        if tracker.current_ptr != dict_ptr {
116            return Err(HintError::MismatchedDictPtr(Box::new((
117                tracker.current_ptr,
118                dict_ptr,
119            ))));
120        }
121        Ok(tracker)
122    }
123
124    //Returns the tracker which's current_ptr matches with the given dict_ptr
125    pub fn get_tracker(&self, dict_ptr: Relocatable) -> Result<&DictTracker, HintError> {
126        let tracker = self
127            .trackers
128            .get(&dict_ptr.segment_index)
129            .ok_or(HintError::NoDictTracker(dict_ptr.segment_index))?;
130        if tracker.current_ptr != dict_ptr {
131            return Err(HintError::MismatchedDictPtr(Box::new((
132                tracker.current_ptr,
133                dict_ptr,
134            ))));
135        }
136        Ok(tracker)
137    }
138}
139
140impl Default for DictManager {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146impl DictTracker {
147    pub fn new_empty(base: Relocatable) -> Self {
148        DictTracker {
149            data: Dictionary::SimpleDictionary(HashMap::new()),
150            current_ptr: base,
151        }
152    }
153
154    pub fn new_default_dict(
155        base: Relocatable,
156        default_value: &MaybeRelocatable,
157        initial_dict: Option<HashMap<MaybeRelocatable, MaybeRelocatable>>,
158    ) -> Self {
159        DictTracker {
160            data: Dictionary::DefaultDictionary {
161                dict: initial_dict.unwrap_or_default(),
162                default_value: default_value.clone(),
163            },
164            current_ptr: base,
165        }
166    }
167
168    pub fn new_with_initial(
169        base: Relocatable,
170        initial_dict: HashMap<MaybeRelocatable, MaybeRelocatable>,
171    ) -> Self {
172        DictTracker {
173            data: Dictionary::SimpleDictionary(initial_dict),
174            current_ptr: base,
175        }
176    }
177
178    //Returns a copy of the contained dictionary, losing the dictionary type in the process
179    pub fn get_dictionary_copy(&self) -> HashMap<MaybeRelocatable, MaybeRelocatable> {
180        match &self.data {
181            Dictionary::SimpleDictionary(dict) => dict.clone(),
182            Dictionary::DefaultDictionary {
183                dict,
184                default_value: _,
185            } => dict.clone(),
186        }
187    }
188
189    //Returns a reference to the contained dictionary, losing the dictionary type in the process
190    pub fn get_dictionary_ref(&self) -> &HashMap<MaybeRelocatable, MaybeRelocatable> {
191        match &self.data {
192            Dictionary::SimpleDictionary(dict) => dict,
193            Dictionary::DefaultDictionary {
194                dict,
195                default_value: _,
196            } => dict,
197        }
198    }
199
200    pub fn get_value(&mut self, key: &MaybeRelocatable) -> Result<&MaybeRelocatable, HintError> {
201        self.data
202            .get(key)
203            .ok_or_else(|| HintError::NoValueForKey(Box::new(key.clone())))
204    }
205
206    pub fn insert_value(&mut self, key: &MaybeRelocatable, val: &MaybeRelocatable) {
207        self.data.insert(key, val)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::{relocatable, utils::test_utils::*};
215    use assert_matches::assert_matches;
216
217    #[test]
218    fn create_dict_manager() {
219        let dict_manager = DictManager::new();
220        assert_eq!(dict_manager.trackers, HashMap::new());
221    }
222
223    #[test]
224    fn create_dict_tracker_empty() {
225        let dict_tracker = DictTracker::new_empty(relocatable!(1, 0));
226        assert_eq!(
227            dict_tracker.data,
228            Dictionary::SimpleDictionary(HashMap::new())
229        );
230        assert_eq!(dict_tracker.current_ptr, relocatable!(1, 0));
231    }
232
233    #[test]
234    fn create_dict_tracker_default() {
235        let dict_tracker =
236            DictTracker::new_default_dict(relocatable!(1, 0), &MaybeRelocatable::from(5), None);
237        assert_eq!(
238            dict_tracker.data,
239            Dictionary::DefaultDictionary {
240                dict: HashMap::new(),
241                default_value: MaybeRelocatable::from(5)
242            }
243        );
244        assert_eq!(dict_tracker.current_ptr, relocatable!(1, 0));
245    }
246
247    #[test]
248    fn dict_manager_new_dict_empty() {
249        let mut vm = vm!();
250        let mut dict_manager = DictManager::new();
251        let base = dict_manager.new_dict(&mut vm, HashMap::new());
252        assert_matches!(base, Ok(x) if x == MaybeRelocatable::from((0, 0)));
253        assert!(dict_manager.trackers.contains_key(&0));
254        assert_eq!(
255            dict_manager.trackers.get(&0),
256            Some(&DictTracker::new_empty(relocatable!(0, 0)))
257        );
258        assert_eq!(vm.segments.num_segments(), 1);
259    }
260
261    #[test]
262    fn dict_manager_new_dict_default() {
263        let mut dict_manager = DictManager::new();
264        let mut vm = vm!();
265        let base = dict_manager.new_default_dict(&mut vm, &MaybeRelocatable::from(5), None);
266        assert_matches!(base, Ok(x) if x == MaybeRelocatable::from((0, 0)));
267        assert!(dict_manager.trackers.contains_key(&0));
268        assert_eq!(
269            dict_manager.trackers.get(&0),
270            Some(&DictTracker::new_default_dict(
271                relocatable!(0, 0),
272                &MaybeRelocatable::from(5),
273                None
274            ))
275        );
276        assert_eq!(vm.segments.num_segments(), 1);
277    }
278
279    #[test]
280    fn dict_manager_new_dict_with_initial_dict() {
281        let mut dict_manager = DictManager::new();
282        let mut vm = vm!();
283        let mut initial_dict = HashMap::<MaybeRelocatable, MaybeRelocatable>::new();
284        initial_dict.insert(MaybeRelocatable::from(5), MaybeRelocatable::from(5));
285        let base = dict_manager.new_dict(&mut vm, initial_dict.clone());
286        assert_matches!(base, Ok(x) if x == MaybeRelocatable::from((0, 0)));
287        assert!(dict_manager.trackers.contains_key(&0));
288        assert_eq!(
289            dict_manager.trackers.get(&0),
290            Some(&DictTracker::new_with_initial(
291                relocatable!(0, 0),
292                initial_dict
293            ))
294        );
295        assert_eq!(vm.segments.num_segments(), 1);
296    }
297
298    #[test]
299    fn dict_manager_new_default_dict_with_initial_dict() {
300        let mut dict_manager = DictManager::new();
301        let mut initial_dict = HashMap::<MaybeRelocatable, MaybeRelocatable>::new();
302        let mut vm = vm!();
303        initial_dict.insert(MaybeRelocatable::from(5), MaybeRelocatable::from(5));
304        let base = dict_manager.new_default_dict(
305            &mut vm,
306            &MaybeRelocatable::from(7),
307            Some(initial_dict.clone()),
308        );
309        assert_matches!(base, Ok(x) if x == MaybeRelocatable::from((0, 0)));
310        assert!(dict_manager.trackers.contains_key(&0));
311        assert_eq!(
312            dict_manager.trackers.get(&0),
313            Some(&DictTracker::new_default_dict(
314                relocatable!(0, 0),
315                &MaybeRelocatable::from(7),
316                Some(initial_dict)
317            ))
318        );
319        assert_eq!(vm.segments.num_segments(), 1);
320    }
321
322    #[test]
323    fn dict_manager_new_dict_empty_same_segment() {
324        let mut dict_manager = DictManager::new();
325        dict_manager
326            .trackers
327            .insert(0, DictTracker::new_empty(relocatable!(0, 0)));
328        let mut vm = vm!();
329        assert_matches!(
330            dict_manager.new_dict(&mut vm, HashMap::new()),
331            Err(HintError::CantCreateDictionaryOnTakenSegment(0))
332        );
333    }
334
335    #[test]
336    fn dict_manager_new_default_dict_empty_same_segment() {
337        let mut dict_manager = DictManager::new();
338        dict_manager.trackers.insert(
339            0,
340            DictTracker::new_default_dict(relocatable!(0, 0), &MaybeRelocatable::from(6), None),
341        );
342        let mut vm = vm!();
343        assert_matches!(
344            dict_manager.new_dict(&mut vm, HashMap::new()),
345            Err(HintError::CantCreateDictionaryOnTakenSegment(0))
346        );
347    }
348
349    #[test]
350    fn dictionary_get_insert_simple() {
351        let mut dictionary = Dictionary::SimpleDictionary(HashMap::new());
352        dictionary.insert(&MaybeRelocatable::from(1), &MaybeRelocatable::from(2));
353        assert_eq!(
354            dictionary.get(&MaybeRelocatable::from(1)),
355            Some(&MaybeRelocatable::from(2))
356        );
357        assert_eq!(dictionary.get(&MaybeRelocatable::from(2)), None);
358    }
359
360    #[test]
361    fn dictionary_get_insert_default() {
362        let mut dictionary = Dictionary::DefaultDictionary {
363            dict: HashMap::new(),
364            default_value: MaybeRelocatable::from(7),
365        };
366        dictionary.insert(&MaybeRelocatable::from(1), &MaybeRelocatable::from(2));
367        assert_eq!(
368            dictionary.get(&MaybeRelocatable::from(1)),
369            Some(&MaybeRelocatable::from(2))
370        );
371        assert_eq!(
372            dictionary.get(&MaybeRelocatable::from(2)),
373            Some(&MaybeRelocatable::from(7))
374        );
375    }
376}