Skip to main content

cairo_vm/types/
exec_scope.rs

1use crate::{
2    any_box,
3    hint_processor::builtin_hint_processor::dict_manager::DictManager,
4    vm::errors::{exec_scope_errors::ExecScopeError, hint_errors::HintError},
5};
6use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc};
7
8#[derive(Debug)]
9pub struct ExecutionScopes {
10    pub data: Vec<HashMap<String, Box<dyn Any>>>,
11}
12
13impl ExecutionScopes {
14    pub fn new() -> ExecutionScopes {
15        ExecutionScopes {
16            data: vec![HashMap::new()],
17        }
18    }
19
20    pub fn enter_scope(&mut self, new_scope_locals: HashMap<String, Box<dyn Any>>) {
21        self.data.push(new_scope_locals);
22    }
23
24    pub fn exit_scope(&mut self) -> Result<(), ExecScopeError> {
25        if self.data.len() == 1 {
26            return Err(ExecScopeError::ExitMainScopeError);
27        }
28        self.data.pop();
29
30        Ok(())
31    }
32
33    ///Returns a mutable reference to the dictionary containing the variables present in the current scope
34    pub fn get_local_variables_mut(
35        &mut self,
36    ) -> Result<&mut HashMap<String, Box<dyn Any>>, HintError> {
37        self.data
38            .last_mut()
39            .ok_or(HintError::FromScopeError(ExecScopeError::NoScopeError))
40    }
41
42    ///Returns a dictionary containing the variables present in the current scope
43    pub fn get_local_variables(&self) -> Result<&HashMap<String, Box<dyn Any>>, HintError> {
44        self.data
45            .last()
46            .ok_or(HintError::FromScopeError(ExecScopeError::NoScopeError))
47    }
48
49    ///Removes a variable from the current scope given its name
50    pub fn delete_variable(&mut self, var_name: &str) {
51        if let Ok(local_variables) = self.get_local_variables_mut() {
52            local_variables.remove(var_name);
53        }
54    }
55
56    ///Creates or updates an existing variable given its name and boxed value
57    pub fn assign_or_update_variable(&mut self, var_name: &str, var_value: Box<dyn Any>) {
58        if let Ok(local_variables) = self.get_local_variables_mut() {
59            local_variables.insert(var_name.to_string(), var_value);
60        }
61    }
62
63    ///Returns the value in the current execution scope that matches the name and is of the given generic type
64    pub fn get<T: Any + Clone>(&self, name: &str) -> Result<T, HintError> {
65        let mut val: Option<T> = None;
66        if let Some(variable) = self.get_local_variables()?.get(name) {
67            if let Some(int) = variable.downcast_ref::<T>() {
68                val = Some(int.clone());
69            }
70        }
71        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
72    }
73
74    ///Returns a reference to the value in the current execution scope that matches the name and is of the given generic type
75    pub fn get_ref<T: Any>(&self, name: &str) -> Result<&T, HintError> {
76        let mut val: Option<&T> = None;
77        if let Some(variable) = self.get_local_variables()?.get(name) {
78            if let Some(int) = variable.downcast_ref::<T>() {
79                val = Some(int);
80            }
81        }
82        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
83    }
84
85    ///Returns a mutable reference to the value in the current execution scope that matches the name and is of the given generic type
86    pub fn get_mut_ref<T: Any>(&mut self, name: &str) -> Result<&mut T, HintError> {
87        let mut val: Option<&mut T> = None;
88        if let Some(variable) = self.get_local_variables_mut()?.get_mut(name) {
89            if let Some(int) = variable.downcast_mut::<T>() {
90                val = Some(int);
91            }
92        }
93        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
94    }
95
96    ///Returns the value in the current execution scope that matches the name
97    pub fn get_any_boxed_ref(&self, name: &str) -> Result<&Box<dyn Any>, HintError> {
98        if let Some(variable) = self.get_local_variables()?.get(name) {
99            return Ok(variable);
100        }
101        Err(HintError::VariableNotInScopeError(
102            name.to_string().into_boxed_str(),
103        ))
104    }
105
106    ///Returns the value in the current execution scope that matches the name
107    pub fn get_any_boxed_mut(&mut self, name: &str) -> Result<&mut Box<dyn Any>, HintError> {
108        if let Some(variable) = self.get_local_variables_mut()?.get_mut(name) {
109            return Ok(variable);
110        }
111        Err(HintError::VariableNotInScopeError(
112            name.to_string().into_boxed_str(),
113        ))
114    }
115
116    ///Returns the value in the current execution scope that matches the name and is of type List
117    pub fn get_list<T: Any + Clone>(&self, name: &str) -> Result<Vec<T>, HintError> {
118        let mut val: Option<Vec<T>> = None;
119        if let Some(variable) = self.get_local_variables()?.get(name) {
120            if let Some(list) = variable.downcast_ref::<Vec<T>>() {
121                val = Some(list.clone());
122            }
123        }
124        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
125    }
126
127    ///Returns a reference to the value in the current execution scope that matches the name and is of type List
128    pub fn get_list_ref<T: Any>(&self, name: &str) -> Result<&Vec<T>, HintError> {
129        let mut val: Option<&Vec<T>> = None;
130        if let Some(variable) = self.get_local_variables()?.get(name) {
131            if let Some(list) = variable.downcast_ref::<Vec<T>>() {
132                val = Some(list);
133            }
134        }
135        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
136    }
137
138    ///Returns a mutable reference to the value in the current execution scope that matches the name and is of type List
139    pub fn get_mut_list_ref<T: Any>(&mut self, name: &str) -> Result<&mut Vec<T>, HintError> {
140        let mut val: Option<&mut Vec<T>> = None;
141        if let Some(variable) = self.get_local_variables_mut()?.get_mut(name) {
142            if let Some(list) = variable.downcast_mut::<Vec<T>>() {
143                val = Some(list);
144            }
145        }
146        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
147    }
148
149    ///Returns the value in the dict manager
150    pub fn get_dict_manager(&self) -> Result<Rc<RefCell<DictManager>>, HintError> {
151        let mut val: Option<Rc<RefCell<DictManager>>> = None;
152        if let Some(variable) = self.get_local_variables()?.get("dict_manager") {
153            if let Some(dict_manager) = variable.downcast_ref::<Rc<RefCell<DictManager>>>() {
154                val = Some(dict_manager.clone());
155            }
156        }
157        val.ok_or_else(|| {
158            HintError::VariableNotInScopeError("dict_manager".to_string().into_boxed_str())
159        })
160    }
161
162    ///Returns a mutable reference to the value in the current execution scope that matches the name and is of the given type
163    pub fn get_mut_dict_ref<K: Any, V: Any>(
164        &mut self,
165        name: &str,
166    ) -> Result<&mut HashMap<K, V>, HintError> {
167        let mut val: Option<&mut HashMap<K, V>> = None;
168        if let Some(variable) = self.get_local_variables_mut()?.get_mut(name) {
169            if let Some(dict) = variable.downcast_mut::<HashMap<K, V>>() {
170                val = Some(dict);
171            }
172        }
173        val.ok_or_else(|| HintError::VariableNotInScopeError(name.to_string().into_boxed_str()))
174    }
175
176    ///Inserts the boxed value into the current scope
177    pub fn insert_box(&mut self, name: &str, value: Box<dyn Any>) {
178        self.assign_or_update_variable(name, value);
179    }
180
181    ///Inserts the value into the current scope
182    pub fn insert_value<T: 'static>(&mut self, name: &str, value: T) {
183        self.assign_or_update_variable(name, any_box!(value));
184    }
185}
186
187impl Default for ExecutionScopes {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use crate::Felt252;
197    use assert_matches::assert_matches;
198
199    #[test]
200    fn initialize_execution_scopes() {
201        let scopes = ExecutionScopes::new();
202        assert_eq!(scopes.data.len(), 1);
203    }
204
205    #[test]
206    fn get_local_variables_test() {
207        let var_name = String::from("a");
208        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
209
210        let scope = HashMap::from([(var_name, var_value)]);
211
212        let scopes = ExecutionScopes { data: vec![scope] };
213        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
214        assert_eq!(
215            scopes
216                .get_local_variables()
217                .unwrap()
218                .get("a")
219                .unwrap()
220                .downcast_ref::<Felt252>(),
221            Some(&Felt252::from(2))
222        );
223    }
224
225    #[test]
226    fn enter_new_scope_test() {
227        let var_name = String::from("a");
228        let var_value: Box<dyn Any> = Box::new(Felt252::from(2_i32));
229
230        let new_scope = HashMap::from([(var_name, var_value)]);
231
232        let mut scopes = ExecutionScopes {
233            data: vec![HashMap::from([(
234                String::from("b"),
235                (Box::new(Felt252::ONE) as Box<dyn Any>),
236            )])],
237        };
238
239        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
240        assert_eq!(
241            scopes
242                .get_local_variables()
243                .unwrap()
244                .get("b")
245                .unwrap()
246                .downcast_ref::<Felt252>(),
247            Some(&Felt252::ONE)
248        );
249
250        scopes.enter_scope(new_scope);
251
252        // check that variable `b` can't be accessed now
253        assert!(scopes.get_local_variables().unwrap().get("b").is_none());
254
255        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
256        assert_eq!(
257            scopes
258                .get_local_variables()
259                .unwrap()
260                .get("a")
261                .unwrap()
262                .downcast_ref::<Felt252>(),
263            Some(&Felt252::from(2))
264        );
265    }
266
267    #[test]
268    fn exit_scope_test() {
269        let var_name = String::from("a");
270        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
271
272        let new_scope = HashMap::from([(var_name, var_value)]);
273
274        // this initializes an empty main scope
275        let mut scopes = ExecutionScopes::new();
276
277        // enter one extra scope
278        scopes.enter_scope(new_scope);
279
280        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
281        assert_eq!(
282            scopes
283                .get_local_variables()
284                .unwrap()
285                .get("a")
286                .unwrap()
287                .downcast_ref::<Felt252>(),
288            Some(&Felt252::from(2))
289        );
290
291        // exit the current scope
292        let exit_scope_result = scopes.exit_scope();
293
294        assert!(exit_scope_result.is_ok());
295
296        // assert that variable `a` is no longer available
297        assert!(scopes.get_local_variables().unwrap().get("a").is_none());
298
299        // assert that we recovered the older scope
300        assert!(scopes.get_local_variables().unwrap().is_empty());
301    }
302
303    #[test]
304    fn assign_local_variable_test() {
305        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
306
307        let mut scopes = ExecutionScopes::new();
308
309        scopes.assign_or_update_variable("a", var_value);
310
311        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
312        assert_eq!(
313            scopes
314                .get_local_variables()
315                .unwrap()
316                .get("a")
317                .unwrap()
318                .downcast_ref::<Felt252>(),
319            Some(&Felt252::from(2))
320        );
321    }
322
323    #[test]
324    fn re_assign_local_variable_test() {
325        let var_name = String::from("a");
326        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
327
328        let scope = HashMap::from([(var_name, var_value)]);
329
330        let mut scopes = ExecutionScopes { data: vec![scope] };
331
332        let var_value_new: Box<dyn Any> = Box::new(Felt252::from(3));
333
334        scopes.assign_or_update_variable("a", var_value_new);
335
336        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
337        assert_eq!(
338            scopes
339                .get_local_variables()
340                .unwrap()
341                .get("a")
342                .unwrap()
343                .downcast_ref::<Felt252>(),
344            Some(&Felt252::from(3))
345        );
346    }
347
348    #[test]
349    fn delete_local_variable_test() {
350        let var_name = String::from("a");
351        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
352
353        let scope = HashMap::from([(var_name, var_value)]);
354
355        let mut scopes = ExecutionScopes { data: vec![scope] };
356
357        assert!(scopes
358            .get_local_variables()
359            .unwrap()
360            .contains_key(&String::from("a")));
361
362        scopes.delete_variable("a");
363
364        assert!(!scopes
365            .get_local_variables()
366            .unwrap()
367            .contains_key(&String::from("a")));
368    }
369
370    #[test]
371    fn exit_main_scope_gives_error_test() {
372        let mut scopes = ExecutionScopes::new();
373
374        assert!(scopes.exit_scope().is_err());
375    }
376
377    #[test]
378    fn get_listu64_test() {
379        let list_u64: Box<dyn Any> = Box::new(vec![20_u64, 18_u64]);
380
381        let mut scopes = ExecutionScopes::default();
382
383        scopes.insert_box("list_u64", list_u64);
384
385        assert_matches!(
386            scopes.get_list::<u64>("list_u64"),
387            Ok(x) if x == vec![20_u64, 18_u64]
388        );
389
390        assert_matches!(
391            scopes.get_list::<u64>("no_variable"),
392            Err(HintError::VariableNotInScopeError(
393                x
394            )) if *x == *"no_variable".to_string()
395        );
396    }
397
398    #[test]
399    fn get_u64_test() {
400        let u64: Box<dyn Any> = Box::new(9_u64);
401
402        let mut scopes = ExecutionScopes::new();
403
404        scopes.assign_or_update_variable("u64", u64);
405
406        assert_matches!(scopes.get_ref::<u64>("u64"), Ok(&9_u64));
407        assert_matches!(scopes.get_mut_ref::<u64>("u64"), Ok(&mut 9_u64));
408
409        assert_matches!(
410            scopes.get_mut_ref::<u64>("no_variable"),
411            Err(HintError::VariableNotInScopeError(
412                x
413            )) if *x == *"no_variable".to_string()
414        );
415        assert_matches!(
416            scopes.get_ref::<u64>("no_variable"),
417            Err(HintError::VariableNotInScopeError(
418                x
419            )) if *x == *"no_variable".to_string()
420        );
421    }
422
423    #[test]
424    fn get_mut_int_ref_test() {
425        let bigint: Box<dyn Any> = Box::new(Felt252::from(12));
426
427        let mut scopes = ExecutionScopes::new();
428        scopes.assign_or_update_variable("bigint", bigint);
429
430        assert_matches!(
431            scopes.get_mut_ref::<Felt252>("bigint"),
432            Ok(x) if x == &mut Felt252::from(12)
433        );
434    }
435
436    #[test]
437    fn get_any_boxed_test() {
438        let list_u64: Box<dyn Any> = Box::new(vec![20_u64, 18_u64]);
439
440        let mut scopes = ExecutionScopes::default();
441
442        scopes.assign_or_update_variable("list_u64", list_u64);
443
444        assert_eq!(
445            scopes
446                .get_any_boxed_ref("list_u64")
447                .unwrap()
448                .downcast_ref::<Vec<u64>>(),
449            Some(&vec![20_u64, 18_u64])
450        );
451
452        assert_eq!(
453            scopes
454                .get_any_boxed_mut("list_u64")
455                .unwrap()
456                .downcast_mut::<Vec<u64>>(),
457            Some(&mut vec![20_u64, 18_u64])
458        );
459
460        assert!(scopes.get_any_boxed_mut("no_variable").is_err());
461        assert!(scopes.get_any_boxed_ref("no_variable").is_err());
462    }
463}