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}