cairo_vm/types/
exec_scope.rs

1use crate::stdlib::{any::Any, cell::RefCell, collections::HashMap, prelude::*, rc::Rc};
2use crate::{
3    any_box,
4    hint_processor::builtin_hint_processor::dict_manager::DictManager,
5    vm::errors::{exec_scope_errors::ExecScopeError, hint_errors::HintError},
6};
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    #[cfg(target_arch = "wasm32")]
200    use wasm_bindgen_test::*;
201
202    #[test]
203    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
204    fn initialize_execution_scopes() {
205        let scopes = ExecutionScopes::new();
206        assert_eq!(scopes.data.len(), 1);
207    }
208
209    #[test]
210    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
211    fn get_local_variables_test() {
212        let var_name = String::from("a");
213        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
214
215        let scope = HashMap::from([(var_name, var_value)]);
216
217        let scopes = ExecutionScopes { data: vec![scope] };
218        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
219        assert_eq!(
220            scopes
221                .get_local_variables()
222                .unwrap()
223                .get("a")
224                .unwrap()
225                .downcast_ref::<Felt252>(),
226            Some(&Felt252::from(2))
227        );
228    }
229
230    #[test]
231    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
232    fn enter_new_scope_test() {
233        let var_name = String::from("a");
234        let var_value: Box<dyn Any> = Box::new(Felt252::from(2_i32));
235
236        let new_scope = HashMap::from([(var_name, var_value)]);
237
238        let mut scopes = ExecutionScopes {
239            data: vec![HashMap::from([(
240                String::from("b"),
241                (Box::new(Felt252::ONE) as Box<dyn Any>),
242            )])],
243        };
244
245        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
246        assert_eq!(
247            scopes
248                .get_local_variables()
249                .unwrap()
250                .get("b")
251                .unwrap()
252                .downcast_ref::<Felt252>(),
253            Some(&Felt252::ONE)
254        );
255
256        scopes.enter_scope(new_scope);
257
258        // check that variable `b` can't be accessed now
259        assert!(scopes.get_local_variables().unwrap().get("b").is_none());
260
261        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
262        assert_eq!(
263            scopes
264                .get_local_variables()
265                .unwrap()
266                .get("a")
267                .unwrap()
268                .downcast_ref::<Felt252>(),
269            Some(&Felt252::from(2))
270        );
271    }
272
273    #[test]
274    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
275    fn exit_scope_test() {
276        let var_name = String::from("a");
277        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
278
279        let new_scope = HashMap::from([(var_name, var_value)]);
280
281        // this initializes an empty main scope
282        let mut scopes = ExecutionScopes::new();
283
284        // enter one extra scope
285        scopes.enter_scope(new_scope);
286
287        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
288        assert_eq!(
289            scopes
290                .get_local_variables()
291                .unwrap()
292                .get("a")
293                .unwrap()
294                .downcast_ref::<Felt252>(),
295            Some(&Felt252::from(2))
296        );
297
298        // exit the current scope
299        let exit_scope_result = scopes.exit_scope();
300
301        assert!(exit_scope_result.is_ok());
302
303        // assert that variable `a` is no longer available
304        assert!(scopes.get_local_variables().unwrap().get("a").is_none());
305
306        // assert that we recovered the older scope
307        assert!(scopes.get_local_variables().unwrap().is_empty());
308    }
309
310    #[test]
311    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
312    fn assign_local_variable_test() {
313        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
314
315        let mut scopes = ExecutionScopes::new();
316
317        scopes.assign_or_update_variable("a", var_value);
318
319        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
320        assert_eq!(
321            scopes
322                .get_local_variables()
323                .unwrap()
324                .get("a")
325                .unwrap()
326                .downcast_ref::<Felt252>(),
327            Some(&Felt252::from(2))
328        );
329    }
330
331    #[test]
332    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
333    fn re_assign_local_variable_test() {
334        let var_name = String::from("a");
335        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
336
337        let scope = HashMap::from([(var_name, var_value)]);
338
339        let mut scopes = ExecutionScopes { data: vec![scope] };
340
341        let var_value_new: Box<dyn Any> = Box::new(Felt252::from(3));
342
343        scopes.assign_or_update_variable("a", var_value_new);
344
345        assert_eq!(scopes.get_local_variables().unwrap().len(), 1);
346        assert_eq!(
347            scopes
348                .get_local_variables()
349                .unwrap()
350                .get("a")
351                .unwrap()
352                .downcast_ref::<Felt252>(),
353            Some(&Felt252::from(3))
354        );
355    }
356
357    #[test]
358    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
359    fn delete_local_variable_test() {
360        let var_name = String::from("a");
361        let var_value: Box<dyn Any> = Box::new(Felt252::from(2));
362
363        let scope = HashMap::from([(var_name, var_value)]);
364
365        let mut scopes = ExecutionScopes { data: vec![scope] };
366
367        assert!(scopes
368            .get_local_variables()
369            .unwrap()
370            .contains_key(&String::from("a")));
371
372        scopes.delete_variable("a");
373
374        assert!(!scopes
375            .get_local_variables()
376            .unwrap()
377            .contains_key(&String::from("a")));
378    }
379
380    #[test]
381    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
382    fn exit_main_scope_gives_error_test() {
383        let mut scopes = ExecutionScopes::new();
384
385        assert!(scopes.exit_scope().is_err());
386    }
387
388    #[test]
389    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
390    fn get_listu64_test() {
391        let list_u64: Box<dyn Any> = Box::new(vec![20_u64, 18_u64]);
392
393        let mut scopes = ExecutionScopes::default();
394
395        scopes.insert_box("list_u64", list_u64);
396
397        assert_matches!(
398            scopes.get_list::<u64>("list_u64"),
399            Ok(x) if x == vec![20_u64, 18_u64]
400        );
401
402        assert_matches!(
403            scopes.get_list::<u64>("no_variable"),
404            Err(HintError::VariableNotInScopeError(
405                x
406            )) if *x == *"no_variable".to_string()
407        );
408    }
409
410    #[test]
411    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
412    fn get_u64_test() {
413        let u64: Box<dyn Any> = Box::new(9_u64);
414
415        let mut scopes = ExecutionScopes::new();
416
417        scopes.assign_or_update_variable("u64", u64);
418
419        assert_matches!(scopes.get_ref::<u64>("u64"), Ok(&9_u64));
420        assert_matches!(scopes.get_mut_ref::<u64>("u64"), Ok(&mut 9_u64));
421
422        assert_matches!(
423            scopes.get_mut_ref::<u64>("no_variable"),
424            Err(HintError::VariableNotInScopeError(
425                x
426            )) if *x == *"no_variable".to_string()
427        );
428        assert_matches!(
429            scopes.get_ref::<u64>("no_variable"),
430            Err(HintError::VariableNotInScopeError(
431                x
432            )) if *x == *"no_variable".to_string()
433        );
434    }
435
436    #[test]
437    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
438    fn get_mut_int_ref_test() {
439        let bigint: Box<dyn Any> = Box::new(Felt252::from(12));
440
441        let mut scopes = ExecutionScopes::new();
442        scopes.assign_or_update_variable("bigint", bigint);
443
444        assert_matches!(
445            scopes.get_mut_ref::<Felt252>("bigint"),
446            Ok(x) if x == &mut Felt252::from(12)
447        );
448    }
449
450    #[test]
451    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
452    fn get_any_boxed_test() {
453        let list_u64: Box<dyn Any> = Box::new(vec![20_u64, 18_u64]);
454
455        let mut scopes = ExecutionScopes::default();
456
457        scopes.assign_or_update_variable("list_u64", list_u64);
458
459        assert_eq!(
460            scopes
461                .get_any_boxed_ref("list_u64")
462                .unwrap()
463                .downcast_ref::<Vec<u64>>(),
464            Some(&vec![20_u64, 18_u64])
465        );
466
467        assert_eq!(
468            scopes
469                .get_any_boxed_mut("list_u64")
470                .unwrap()
471                .downcast_mut::<Vec<u64>>(),
472            Some(&mut vec![20_u64, 18_u64])
473        );
474
475        assert!(scopes.get_any_boxed_mut("no_variable").is_err());
476        assert!(scopes.get_any_boxed_ref("no_variable").is_err());
477    }
478}