lib_ruby_parser/
static_environment.rs

1use std::cell::RefCell;
2use std::collections::BTreeSet;
3use std::rc::Rc;
4
5/// Stack of local variables in nested scopes
6///
7/// Each scope represents a Ruby scope:
8///
9/// ```test
10/// # 1
11/// class A
12///   # 1, 2
13///   def m
14///     # 1, 2, 3
15///   end
16///   # 1, 2
17/// end
18/// # 1
19/// ```
20///
21/// In the example above comments show what's in the stack.
22/// Basically, it's pushed when you enter a new scope
23/// and it's popped when exit it.
24#[derive(Debug, Clone, Default)]
25pub struct StaticEnvironment {
26    variables: Rc<RefCell<BTreeSet<String>>>,
27    stack: Rc<RefCell<Vec<BTreeSet<String>>>>,
28}
29
30const FORWARD_ARGS: &str = "FORWARD_ARGS";
31const ANONYMOUS_BLOCKARG: &str = "ANONYMOUS_BLOCKARG";
32
33impl StaticEnvironment {
34    /// Constructor
35    pub fn new() -> Self {
36        Self {
37            variables: Rc::new(RefCell::new(BTreeSet::new())),
38            stack: Rc::new(RefCell::new(vec![])),
39        }
40    }
41
42    pub(crate) fn is_empty(&self) -> bool {
43        self.stack.borrow().is_empty()
44    }
45
46    /// Performs a push, doesn't inherit previously declared variables in the new scope
47    ///
48    /// Handles class/module scopes
49    pub fn extend_static(&self) {
50        let variables = std::mem::take(&mut *self.variables.borrow_mut());
51        self.stack.borrow_mut().push(variables);
52    }
53
54    /// Performs a push, inherits previously declared variables in the new scope
55    ///
56    /// Handles block/lambda scopes
57    pub fn extend_dynamic(&self) {
58        self.stack
59            .borrow_mut()
60            .push(self.variables.borrow().clone());
61    }
62
63    /// Performs pop
64    pub fn unextend(&self) {
65        *self.variables.borrow_mut() = self
66            .stack
67            .borrow_mut()
68            .pop()
69            .expect("expected static_env to have at least one frame");
70    }
71
72    /// Declares a new variable in the current scope
73    pub fn declare(&self, name: &str) {
74        self.variables.borrow_mut().insert(name.to_string());
75    }
76
77    /// Returns `true` if variable with a given `name` is declared in the current scope
78    pub fn is_declared(&self, name: &str) -> bool {
79        self.variables.borrow().get(name).is_some()
80    }
81
82    pub(crate) fn declare_forward_args(&self) {
83        self.declare(FORWARD_ARGS);
84    }
85
86    pub(crate) fn is_forward_args_declared(&self) -> bool {
87        self.is_declared(FORWARD_ARGS)
88    }
89
90    pub(crate) fn declare_anonymous_blockarg(&self) {
91        self.declare(ANONYMOUS_BLOCKARG)
92    }
93
94    pub(crate) fn is_anonymous_blockarg_declared(&self) -> bool {
95        self.is_declared(ANONYMOUS_BLOCKARG)
96    }
97}
98
99#[test]
100fn test_declare() {
101    let env = StaticEnvironment::new();
102    assert!(!env.is_declared("foo"));
103
104    env.declare("foo");
105    assert!(env.is_declared("foo"));
106}
107
108#[test]
109fn test_extend_static() {
110    let env = StaticEnvironment::new();
111
112    env.declare("foo");
113    env.extend_static();
114    env.declare("bar");
115
116    assert!(!env.is_declared("foo"));
117    assert!(env.is_declared("bar"));
118}
119
120#[test]
121fn test_extend_dynamic() {
122    let env = StaticEnvironment::new();
123
124    env.declare("foo");
125    env.extend_dynamic();
126    env.declare("bar");
127
128    assert!(env.is_declared("foo"));
129    assert!(env.is_declared("bar"));
130}
131
132#[test]
133fn test_unextend() {
134    let env = StaticEnvironment::new();
135
136    env.declare("foo");
137    env.extend_dynamic();
138    env.declare("bar");
139    env.unextend();
140
141    assert!(env.is_declared("foo"));
142    assert!(!env.is_declared("bar"));
143}