boa/environment/
environment_record_trait.rs

1//! # Environment Records
2//!
3//! <https://tc39.es/ecma262/#sec-environment-records>
4//! <https://tc39.es/ecma262/#sec-lexical-environments>
5//!
6//! Some environments are stored as `JSObjects`. This is for GC, i.e we want to keep an environment if a variable is closed-over (a closure is returned).
7//! All of the logic to handle scope/environment records are stored in here.
8//!
9//! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait`
10//!
11
12use crate::{environment::lexical_environment::VariableScope, object::JsObject};
13use crate::{
14    environment::lexical_environment::{Environment, EnvironmentType},
15    gc::{Finalize, Trace},
16    Context, JsResult, JsValue,
17};
18use std::fmt::Debug;
19
20/// <https://tc39.es/ecma262/#sec-environment-records>
21///
22/// In the ECMAScript specification Environment Records are hierachical and have a base class with abstract methods.
23/// In this implementation we have a trait which represents the behaviour of all `EnvironmentRecord` types.
24pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
25    /// Determine if an Environment Record has a binding for the String value N.
26    /// Return true if it does and false if it does not.
27    fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool>;
28
29    /// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name.
30    /// If the Boolean argument deletion is true the binding may be subsequently deleted.
31    ///
32    /// * `allow_name_reuse` - specifies whether or not reusing binding names is allowed.
33    ///
34    /// Most variable names cannot be reused, but functions in JavaScript are allowed to have multiple
35    /// paraments with the same name.
36    fn create_mutable_binding(
37        &self,
38        name: &str,
39        deletion: bool,
40        allow_name_reuse: bool,
41        context: &mut Context,
42    ) -> JsResult<()>;
43
44    /// Create a new but uninitialized immutable binding in an Environment Record.
45    /// The String value N is the text of the bound name.
46    /// If strict is true then attempts to set it after it has been initialized will always throw an exception,
47    /// regardless of the strict mode setting of operations that reference that binding.
48    fn create_immutable_binding(
49        &self,
50        name: &str,
51        strict: bool,
52        context: &mut Context,
53    ) -> JsResult<()>;
54
55    /// Set the value of an already existing but uninitialized binding in an Environment Record.
56    /// The String value N is the text of the bound name.
57    /// V is the value for the binding and is a value of any ECMAScript language type.
58    fn initialize_binding(&self, name: &str, value: JsValue, context: &mut Context)
59        -> JsResult<()>;
60
61    /// Set the value of an already existing mutable binding in an Environment Record.
62    /// The String value `name` is the text of the bound name.
63    /// value is the `value` for the binding and may be a value of any ECMAScript language type. S is a Boolean flag.
64    /// If `strict` is true and the binding cannot be set throw a TypeError exception.
65    fn set_mutable_binding(
66        &self,
67        name: &str,
68        value: JsValue,
69        strict: bool,
70        context: &mut Context,
71    ) -> JsResult<()>;
72
73    /// Returns the value of an already existing binding from an Environment Record.
74    /// The String value N is the text of the bound name.
75    /// S is used to identify references originating in strict mode code or that
76    /// otherwise require strict mode reference semantics.
77    fn get_binding_value(
78        &self,
79        name: &str,
80        strict: bool,
81        context: &mut Context,
82    ) -> JsResult<JsValue>;
83
84    /// Delete a binding from an Environment Record.
85    /// The String value name is the text of the bound name.
86    /// If a binding for name exists, remove the binding and return true.
87    /// If the binding exists but cannot be removed return false. If the binding does not exist return true.
88    fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult<bool>;
89
90    /// Determine if an Environment Record establishes a this binding.
91    /// Return true if it does and false if it does not.
92    fn has_this_binding(&self) -> bool;
93
94    /// Return the `this` binding from the environment
95    fn get_this_binding(&self, context: &mut Context) -> JsResult<JsValue>;
96
97    /// Determine if an Environment Record establishes a super method binding.
98    /// Return true if it does and false if it does not.
99    fn has_super_binding(&self) -> bool;
100
101    /// If this Environment Record is associated with a with statement, return the with object.
102    /// Otherwise, return None.
103    fn with_base_object(&self) -> Option<JsObject>;
104
105    /// Get the next environment up
106    fn get_outer_environment_ref(&self) -> Option<&Environment>;
107    fn get_outer_environment(&self) -> Option<Environment> {
108        self.get_outer_environment_ref().cloned()
109    }
110
111    /// Set the next environment up
112    fn set_outer_environment(&mut self, env: Environment);
113
114    /// Get the type of environment this is
115    fn get_environment_type(&self) -> EnvironmentType;
116
117    /// Return the `this` binding from the environment or try to get it from outer environments
118    fn recursive_get_this_binding(&self, context: &mut Context) -> JsResult<JsValue> {
119        if self.has_this_binding() {
120            self.get_this_binding(context)
121        } else {
122            match self.get_outer_environment_ref() {
123                Some(outer) => outer.recursive_get_this_binding(context),
124                None => Ok(JsValue::undefined()),
125            }
126        }
127    }
128
129    /// Create mutable binding while handling outer environments
130    fn recursive_create_mutable_binding(
131        &self,
132        name: &str,
133        deletion: bool,
134        scope: VariableScope,
135        context: &mut Context,
136    ) -> JsResult<()> {
137        match scope {
138            VariableScope::Block => self.create_mutable_binding(name, deletion, false, context),
139            VariableScope::Function => self
140                .get_outer_environment_ref()
141                .expect("No function or global environment")
142                .recursive_create_mutable_binding(name, deletion, scope, context),
143        }
144    }
145
146    /// Create immutable binding while handling outer environments
147    fn recursive_create_immutable_binding(
148        &self,
149        name: &str,
150        deletion: bool,
151        scope: VariableScope,
152        context: &mut Context,
153    ) -> JsResult<()> {
154        match scope {
155            VariableScope::Block => self.create_immutable_binding(name, deletion, context),
156            VariableScope::Function => self
157                .get_outer_environment_ref()
158                .expect("No function or global environment")
159                .recursive_create_immutable_binding(name, deletion, scope, context),
160        }
161    }
162
163    /// Set mutable binding while handling outer environments
164    fn recursive_set_mutable_binding(
165        &self,
166        name: &str,
167        value: JsValue,
168        strict: bool,
169        context: &mut Context,
170    ) -> JsResult<()> {
171        if self.has_binding(name, context)? {
172            self.set_mutable_binding(name, value, strict, context)
173        } else {
174            self.get_outer_environment_ref()
175                .expect("Environment stack underflow")
176                .recursive_set_mutable_binding(name, value, strict, context)
177        }
178    }
179
180    /// Initialize binding while handling outer environments
181    fn recursive_initialize_binding(
182        &self,
183        name: &str,
184        value: JsValue,
185        context: &mut Context,
186    ) -> JsResult<()> {
187        if self.has_binding(name, context)? {
188            self.initialize_binding(name, value, context)
189        } else {
190            self.get_outer_environment_ref()
191                .expect("Environment stack underflow")
192                .recursive_initialize_binding(name, value, context)
193        }
194    }
195
196    /// Check if a binding exists in current or any outer environment
197    fn recursive_has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
198        Ok(self.has_binding(name, context)?
199            || match self.get_outer_environment_ref() {
200                Some(outer) => outer.recursive_has_binding(name, context)?,
201                None => false,
202            })
203    }
204
205    /// Retrieve binding from current or any outer environment
206    fn recursive_get_binding_value(&self, name: &str, context: &mut Context) -> JsResult<JsValue> {
207        if self.has_binding(name, context)? {
208            self.get_binding_value(name, false, context)
209        } else {
210            match self.get_outer_environment_ref() {
211                Some(outer) => outer.recursive_get_binding_value(name, context),
212                None => context.throw_reference_error(format!("{} is not defined", name)),
213            }
214        }
215    }
216}