boa/environment/
lexical_environment.rs

1//! # Lexical Environment
2//!
3//! <https://tc39.es/ecma262/#sec-lexical-environment-operations>
4//!
5//! The following operations are used to operate upon lexical environments
6//! This is the entrypoint to lexical environments.
7
8use super::global_environment_record::GlobalEnvironmentRecord;
9use crate::{
10    environment::environment_record_trait::EnvironmentRecordTrait, object::JsObject, BoaProfiler,
11    Context, JsResult, JsValue,
12};
13use gc::Gc;
14use std::{collections::VecDeque, error, fmt};
15
16/// Environments are wrapped in a Box and then in a GC wrapper
17pub type Environment = Gc<Box<dyn EnvironmentRecordTrait>>;
18
19/// Give each environment an easy way to declare its own type
20/// This helps with comparisons
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum EnvironmentType {
23    Declarative,
24    Function,
25    Global,
26    Object,
27}
28
29/// The scope of a given variable
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum VariableScope {
32    /// The variable declaration is scoped to the current block (`let` and `const`)
33    Block,
34    /// The variable declaration is scoped to the current function (`var`)
35    Function,
36}
37
38#[derive(Debug, Clone)]
39pub struct LexicalEnvironment {
40    environment_stack: VecDeque<Environment>,
41}
42
43/// An error that occurred during lexing or compiling of the source input.
44#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct EnvironmentError {
46    details: String,
47}
48
49impl EnvironmentError {
50    pub fn new(msg: &str) -> Self {
51        Self {
52            details: msg.to_string(),
53        }
54    }
55}
56
57impl fmt::Display for EnvironmentError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}", self.details)
60    }
61}
62
63impl error::Error for EnvironmentError {}
64
65impl LexicalEnvironment {
66    pub fn new(global: JsObject) -> Self {
67        let _timer = BoaProfiler::global().start_event("LexicalEnvironment::new", "env");
68        let global_env = GlobalEnvironmentRecord::new(global.clone(), global);
69        let mut lexical_env = Self {
70            environment_stack: VecDeque::new(),
71        };
72
73        // lexical_env.push(global_env);
74        lexical_env.environment_stack.push_back(global_env.into());
75        lexical_env
76    }
77}
78
79impl Context {
80    pub(crate) fn push_environment<T: Into<Environment>>(&mut self, env: T) {
81        self.realm
82            .environment
83            .environment_stack
84            .push_back(env.into());
85    }
86
87    pub(crate) fn pop_environment(&mut self) -> Option<Environment> {
88        self.realm.environment.environment_stack.pop_back()
89    }
90
91    pub(crate) fn get_this_binding(&mut self) -> JsResult<JsValue> {
92        self.get_current_environment()
93            .recursive_get_this_binding(self)
94    }
95
96    pub(crate) fn create_mutable_binding(
97        &mut self,
98        name: &str,
99        deletion: bool,
100        scope: VariableScope,
101    ) -> JsResult<()> {
102        self.get_current_environment()
103            .recursive_create_mutable_binding(name, deletion, scope, self)
104    }
105
106    pub(crate) fn create_immutable_binding(
107        &mut self,
108        name: &str,
109        deletion: bool,
110        scope: VariableScope,
111    ) -> JsResult<()> {
112        self.get_current_environment()
113            .recursive_create_immutable_binding(name, deletion, scope, self)
114    }
115
116    pub(crate) fn set_mutable_binding(
117        &mut self,
118        name: &str,
119        value: JsValue,
120        strict: bool,
121    ) -> JsResult<()> {
122        self.get_current_environment()
123            .recursive_set_mutable_binding(name, value, strict, self)
124    }
125
126    pub(crate) fn initialize_binding(&mut self, name: &str, value: JsValue) -> JsResult<()> {
127        self.get_current_environment()
128            .recursive_initialize_binding(name, value, self)
129    }
130
131    /// When neededing to clone an environment (linking it with another environnment)
132    /// cloning is more suited. The GC will remove the env once nothing is linking to it anymore
133    pub(crate) fn get_current_environment(&mut self) -> Environment {
134        self.realm
135            .environment
136            .environment_stack
137            .back_mut()
138            .expect("Could not get mutable reference to back object")
139            .clone()
140    }
141
142    pub(crate) fn has_binding(&mut self, name: &str) -> JsResult<bool> {
143        self.get_current_environment()
144            .recursive_has_binding(name, self)
145    }
146
147    pub(crate) fn get_binding_value(&mut self, name: &str) -> JsResult<JsValue> {
148        self.get_current_environment()
149            .recursive_get_binding_value(name, self)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use crate::exec;
156
157    #[test]
158    fn let_is_blockscoped() {
159        let scenario = r#"
160          {
161            let bar = "bar";
162          }
163
164          try{
165            bar;
166          } catch (err) {
167            err.message
168          }
169        "#;
170
171        assert_eq!(&exec(scenario), "\"bar is not defined\"");
172    }
173
174    #[test]
175    fn const_is_blockscoped() {
176        let scenario = r#"
177          {
178            const bar = "bar";
179          }
180
181          try{
182            bar;
183          } catch (err) {
184            err.message
185          }
186        "#;
187
188        assert_eq!(&exec(scenario), "\"bar is not defined\"");
189    }
190
191    #[test]
192    fn var_not_blockscoped() {
193        let scenario = r#"
194          {
195            var bar = "bar";
196          }
197          bar == "bar";
198        "#;
199
200        assert_eq!(&exec(scenario), "true");
201    }
202
203    #[test]
204    fn functions_use_declaration_scope() {
205        let scenario = r#"
206          function foo() {
207            try {
208                bar;
209            } catch (err) {
210                return err.message;
211            }
212          }
213          {
214            let bar = "bar";
215            foo();
216          }
217        "#;
218
219        assert_eq!(&exec(scenario), "\"bar is not defined\"");
220    }
221
222    #[test]
223    fn set_outer_var_in_blockscope() {
224        let scenario = r#"
225          var bar;
226          {
227            bar = "foo";
228          }
229          bar == "foo";
230        "#;
231
232        assert_eq!(&exec(scenario), "true");
233    }
234
235    #[test]
236    fn set_outer_let_in_blockscope() {
237        let scenario = r#"
238          let bar;
239          {
240            bar = "foo";
241          }
242          bar == "foo";
243        "#;
244
245        assert_eq!(&exec(scenario), "true");
246    }
247}