mirror of https://github.com/boa-dev/boa.git
Jason Williams
6 years ago
committed by
GitHub
23 changed files with 1252 additions and 190 deletions
@ -0,0 +1,173 @@
|
||||
//! # Declerative Records
|
||||
//!
|
||||
//! Each declarative Environment Record is associated with an ECMAScript program scope containing variable,
|
||||
//! `constant`, `let`, `class`, `module`, `import`, and/or function declarations.
|
||||
//! A declarative Environment Record binds the set of identifiers defined by the declarations contained within its scope.
|
||||
//! More info: [ECMA-262 sec-declarative-environment-records](https://tc39.github.io/ecma262/#sec-declarative-environment-records)
|
||||
|
||||
use crate::environment::environment_record_trait::EnvironmentRecordTrait; |
||||
use crate::environment::lexical_environment::{Environment, EnvironmentType}; |
||||
use crate::js::value::{Value, ValueData}; |
||||
use gc::Gc; |
||||
use std::collections::hash_map::HashMap; |
||||
|
||||
/// Declerative Bindings have a few properties for book keeping purposes, such as mutability (const vs let).
|
||||
/// Can it be deleted? and strict mode.
|
||||
///
|
||||
/// So we need to create a struct to hold these values.
|
||||
/// From this point onwards, a binding is referring to one of these structures.
|
||||
#[derive(Trace, Finalize, Debug, Clone)] |
||||
pub struct DeclerativeEnvironmentRecordBinding { |
||||
pub value: Option<Value>, |
||||
pub can_delete: bool, |
||||
pub mutable: bool, |
||||
pub strict: bool, |
||||
} |
||||
|
||||
/// A declarative Environment Record binds the set of identifiers defined by the
|
||||
/// declarations contained within its scope.
|
||||
#[derive(Trace, Finalize, Clone)] |
||||
pub struct DeclerativeEnvironmentRecord { |
||||
pub env_rec: HashMap<String, DeclerativeEnvironmentRecordBinding>, |
||||
pub outer_env: Option<Environment>, |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord { |
||||
fn has_binding(&self, name: &String) -> bool { |
||||
self.env_rec.contains_key(name) |
||||
} |
||||
|
||||
fn create_mutable_binding(&mut self, name: String, deletion: bool) { |
||||
if self.env_rec.contains_key(&name) { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been declared", name); |
||||
} |
||||
|
||||
self.env_rec.insert( |
||||
name, |
||||
DeclerativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: deletion, |
||||
mutable: true, |
||||
strict: false, |
||||
}, |
||||
); |
||||
} |
||||
|
||||
fn create_immutable_binding(&mut self, name: String, strict: bool) { |
||||
if !self.env_rec.contains_key(&name) { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been declared", name); |
||||
} |
||||
|
||||
self.env_rec.insert( |
||||
name, |
||||
DeclerativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: true, |
||||
mutable: false, |
||||
strict: strict, |
||||
}, |
||||
); |
||||
} |
||||
|
||||
fn initialize_binding(&mut self, name: String, value: Value) { |
||||
match self.env_rec.get_mut(&name) { |
||||
Some(ref mut record) => { |
||||
match record.value { |
||||
Some(_) => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been defined", name); |
||||
} |
||||
None => record.value = Some(value), |
||||
} |
||||
} |
||||
None => {} |
||||
} |
||||
} |
||||
|
||||
fn set_mutable_binding(&mut self, name: String, value: Value, mut strict: bool) { |
||||
if self.env_rec.get(&name).is_none() { |
||||
if strict == true { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Cannot set mutable binding for {}", name); |
||||
} |
||||
|
||||
self.create_mutable_binding(name.clone(), true); |
||||
self.initialize_binding(name.clone(), value); |
||||
return; |
||||
} |
||||
|
||||
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(&name).unwrap(); |
||||
if record.strict { |
||||
strict = true |
||||
} |
||||
if record.value.is_none() { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Cannot set mutable binding for {}", name); |
||||
} |
||||
|
||||
if record.mutable { |
||||
record.value = Some(value); |
||||
} else { |
||||
if strict { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("TypeError: Cannot mutate an immutable binding {}", name); |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn get_binding_value(&self, name: String, _strict: bool) -> Value { |
||||
if self.env_rec.get(&name).is_some() && self.env_rec.get(&name).unwrap().value.is_some() { |
||||
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(&name).unwrap(); |
||||
record.value.as_ref().unwrap().clone() |
||||
} else { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("ReferenceError: Cannot get binding value for {}", name); |
||||
} |
||||
} |
||||
|
||||
fn delete_binding(&mut self, name: String) -> bool { |
||||
if self.env_rec.get(&name).is_some() { |
||||
if self.env_rec.get(&name).unwrap().can_delete { |
||||
self.env_rec.remove(&name); |
||||
true |
||||
} else { |
||||
false |
||||
} |
||||
} else { |
||||
false |
||||
} |
||||
} |
||||
|
||||
fn has_this_binding(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
fn has_super_binding(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
fn with_base_object(&self) -> Value { |
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
|
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
None |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.outer_env = Some(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
return EnvironmentType::Declerative; |
||||
} |
||||
|
||||
fn get_global_object(&self) -> Option<Value> { |
||||
match &self.outer_env { |
||||
Some(outer) => outer.borrow().get_global_object(), |
||||
None => None, |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,79 @@
|
||||
//! # Environment Records
|
||||
//!
|
||||
//! https://tc39.github.io/ecma262/#sec-environment-records
|
||||
//! https://tc39.github.io/ecma262/#sec-lexical-environments
|
||||
//!
|
||||
//! 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).
|
||||
//! All of the logic to handle scope/environment records are stored in here.
|
||||
//!
|
||||
//! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait`
|
||||
//!
|
||||
use crate::environment::lexical_environment::{Environment, EnvironmentType}; |
||||
use crate::js::value::Value; |
||||
use gc::{Finalize, Trace}; |
||||
|
||||
/// https://tc39.github.io/ecma262/#sec-environment-records
|
||||
///
|
||||
/// In the ECMAScript specification Environment Records are hierachical and have a base class with abstract methods.
|
||||
/// In this implementation we have a trait which represents the behaviour of all EnvironmentRecord types.
|
||||
pub trait EnvironmentRecordTrait: Trace + Finalize { |
||||
/// Determine if an Environment Record has a binding for the String value N. Return true if it does and false if it does not.
|
||||
fn has_binding(&self, name: &String) -> bool; |
||||
|
||||
/// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name.
|
||||
/// If the Boolean argument deletion is true the binding may be subsequently deleted.
|
||||
fn create_mutable_binding(&mut self, name: String, deletion: bool); |
||||
|
||||
/// Create a new but uninitialized immutable binding in an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// If strict is true then attempts to set it after it has been initialized will always throw an exception,
|
||||
/// regardless of the strict mode setting of operations that reference that binding.
|
||||
fn create_immutable_binding(&mut self, name: String, strict: bool); |
||||
|
||||
/// Set the value of an already existing but uninitialized binding in an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// V is the value for the binding and is a value of any ECMAScript language type.
|
||||
fn initialize_binding(&mut self, name: String, value: Value); |
||||
|
||||
/// Set the value of an already existing mutable binding in an Environment Record.
|
||||
/// The String value `name` is the text of the bound name.
|
||||
/// value is the `value` for the binding and may be a value of any ECMAScript language type. S is a Boolean flag.
|
||||
/// If `strict` is true and the binding cannot be set throw a TypeError exception.
|
||||
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool); |
||||
|
||||
/// Returns the value of an already existing binding from an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// S is used to identify references originating in strict mode code or that
|
||||
/// otherwise require strict mode reference semantics.
|
||||
fn get_binding_value(&self, name: String, strict: bool) -> Value; |
||||
|
||||
/// Delete a binding from an Environment Record.
|
||||
/// The String value name is the text of the bound name.
|
||||
/// If a binding for name exists, remove the binding and return true.
|
||||
/// If the binding exists but cannot be removed return false. If the binding does not exist return true.
|
||||
fn delete_binding(&mut self, name: String) -> bool; |
||||
|
||||
/// Determine if an Environment Record establishes a this binding.
|
||||
/// Return true if it does and false if it does not.
|
||||
fn has_this_binding(&self) -> bool; |
||||
|
||||
/// Determine if an Environment Record establishes a super method binding.
|
||||
/// Return true if it does and false if it does not.
|
||||
fn has_super_binding(&self) -> bool; |
||||
|
||||
/// If this Environment Record is associated with a with statement, return the with object.
|
||||
/// Otherwise, return undefined.
|
||||
fn with_base_object(&self) -> Value; |
||||
|
||||
/// Get the next environment up
|
||||
fn get_outer_environment(&self) -> Option<Environment>; |
||||
|
||||
/// Set the next environment up
|
||||
fn set_outer_environment(&mut self, env: Environment); |
||||
|
||||
/// Get the type of environment this is
|
||||
fn get_environment_type(&self) -> EnvironmentType; |
||||
|
||||
/// Fetch global variable
|
||||
fn get_global_object(&self) -> Option<Value>; |
||||
} |
@ -0,0 +1,246 @@
|
||||
//! # Function Environment Records
|
||||
//!
|
||||
//! A function Environment Record is a declarative Environment Record that is used to represent
|
||||
//! the top-level scope of a function and, if the function is not an ArrowFunction,
|
||||
//! provides a `this` binding.
|
||||
//! If a function is not an ArrowFunction function and references super,
|
||||
//! its function Environment Record also contains the state that is used to perform super method invocations
|
||||
//! from within the function.
|
||||
//! More info: https://tc39.github.io/ecma262/#sec-function-environment-records
|
||||
|
||||
use crate::environment::declerative_environment_record::DeclerativeEnvironmentRecordBinding; |
||||
use crate::environment::environment_record_trait::EnvironmentRecordTrait; |
||||
use crate::environment::lexical_environment::{Environment, EnvironmentType}; |
||||
use crate::js::value::{Value, ValueData}; |
||||
use gc::Gc; |
||||
use std::collections::hash_map::HashMap; |
||||
|
||||
/// Different binding status for `this`.
|
||||
/// Usually set on a function environment record
|
||||
#[derive(Trace, Finalize, Debug, Clone)] |
||||
pub enum BindingStatus { |
||||
/// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
|
||||
Lexical, |
||||
/// If initialized the function environment record has already been bound with a `this` value
|
||||
Initialized, |
||||
/// If uninitialized the function environment record has not been bouned with a `this` value
|
||||
Uninitialized, |
||||
} |
||||
|
||||
/// https://tc39.github.io/ecma262/#table-16
|
||||
#[derive(Trace, Finalize, Clone)] |
||||
pub struct FunctionEnvironmentRecord { |
||||
pub env_rec: HashMap<String, DeclerativeEnvironmentRecordBinding>, |
||||
/// This is the this value used for this invocation of the function.
|
||||
pub this_value: Value, |
||||
/// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
|
||||
pub this_binding_status: BindingStatus, |
||||
/// The function object whose invocation caused this Environment Record to be created.
|
||||
pub function_object: Value, |
||||
/// If the associated function has super property accesses and is not an ArrowFunction,
|
||||
/// [[HomeObject]] is the object that the function is bound to as a method.
|
||||
/// The default value for [[HomeObject]] is undefined.
|
||||
pub home_object: Value, |
||||
/// If this Environment Record was created by the [[Construct]] internal method,
|
||||
/// [[NewTarget]] is the value of the [[Construct]] newTarget parameter.
|
||||
/// Otherwise, its value is undefined.
|
||||
pub new_target: Value, |
||||
/// Reference to the outer environment to help with the scope chain
|
||||
/// Option type is needed as some environments can be created before we know what the outer env is
|
||||
pub outer_env: Option<Environment>, |
||||
} |
||||
|
||||
impl FunctionEnvironmentRecord { |
||||
pub fn bind_this_value(&mut self, value: Value) { |
||||
match self.this_binding_status { |
||||
// You can not bind an arrow function, their `this` value comes from the lexical scope above
|
||||
BindingStatus::Lexical => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Cannot bind to an arrow function!"); |
||||
} |
||||
// You can not bind a function twice
|
||||
BindingStatus::Initialized => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Cannot bind to an initialised function!"); |
||||
} |
||||
|
||||
BindingStatus::Uninitialized => { |
||||
self.this_value = value; |
||||
self.this_binding_status = BindingStatus::Initialized; |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn get_this_binding(&self) -> Value { |
||||
match self.this_binding_status { |
||||
BindingStatus::Lexical => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("There is no this for a lexical function record"); |
||||
} |
||||
BindingStatus::Uninitialized => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Unitialised binding for this function"); |
||||
} |
||||
|
||||
BindingStatus::Initialized => self.this_value.clone(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for FunctionEnvironmentRecord { |
||||
// TODO: get_super_base can't implement until GetPrototypeof is implemented on object
|
||||
|
||||
fn has_binding(&self, name: &String) -> bool { |
||||
self.env_rec.contains_key(name) |
||||
} |
||||
|
||||
fn create_mutable_binding(&mut self, name: String, deletion: bool) { |
||||
if !self.env_rec.contains_key(&name) { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been declared", name); |
||||
} |
||||
|
||||
self.env_rec.insert( |
||||
name, |
||||
DeclerativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: deletion, |
||||
mutable: true, |
||||
strict: false, |
||||
}, |
||||
); |
||||
} |
||||
|
||||
fn create_immutable_binding(&mut self, name: String, strict: bool) { |
||||
if !self.env_rec.contains_key(&name) { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been declared", name); |
||||
} |
||||
|
||||
self.env_rec.insert( |
||||
name, |
||||
DeclerativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: true, |
||||
mutable: false, |
||||
strict: strict, |
||||
}, |
||||
); |
||||
} |
||||
|
||||
fn initialize_binding(&mut self, name: String, value: Value) { |
||||
match self.env_rec.get_mut(&name) { |
||||
Some(ref mut record) => { |
||||
match record.value { |
||||
Some(_) => { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Identifier {} has already been defined", name); |
||||
} |
||||
None => record.value = Some(value), |
||||
} |
||||
} |
||||
None => {} |
||||
} |
||||
} |
||||
|
||||
fn set_mutable_binding(&mut self, name: String, value: Value, mut strict: bool) { |
||||
if self.env_rec.get(&name).is_none() { |
||||
if strict == true { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Cannot set mutable binding for {}", name); |
||||
} |
||||
|
||||
self.create_mutable_binding(name.clone(), true); |
||||
self.initialize_binding(name.clone(), value); |
||||
return; |
||||
} |
||||
|
||||
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(&name).unwrap(); |
||||
if record.strict { |
||||
strict = true |
||||
} |
||||
|
||||
if record.value.is_none() { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("Reference Error: Cannot set mutable binding for {}", name); |
||||
} |
||||
|
||||
if record.mutable { |
||||
record.value = Some(value); |
||||
} else { |
||||
if strict { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("TypeError: Cannot mutate an immutable binding {}", name); |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn get_binding_value(&self, name: String, _strict: bool) -> Value { |
||||
if self.env_rec.get(&name).is_some() && self.env_rec.get(&name).unwrap().value.is_some() { |
||||
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(&name).unwrap(); |
||||
record.value.as_ref().unwrap().clone() |
||||
} else { |
||||
// TODO: change this when error handling comes into play
|
||||
panic!("ReferenceError: Cannot get binding value for {}", name); |
||||
} |
||||
} |
||||
|
||||
fn delete_binding(&mut self, name: String) -> bool { |
||||
if self.env_rec.get(&name).is_some() { |
||||
if self.env_rec.get(&name).unwrap().can_delete { |
||||
self.env_rec.remove(&name); |
||||
true |
||||
} else { |
||||
false |
||||
} |
||||
} else { |
||||
false |
||||
} |
||||
} |
||||
|
||||
fn has_super_binding(&self) -> bool { |
||||
match self.this_binding_status { |
||||
BindingStatus::Lexical => false, |
||||
_ => { |
||||
if self.home_object.is_undefined() { |
||||
false |
||||
} else { |
||||
true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn has_this_binding(&self) -> bool { |
||||
match self.this_binding_status { |
||||
BindingStatus::Lexical => false, |
||||
_ => true, |
||||
} |
||||
} |
||||
|
||||
fn with_base_object(&self) -> Value { |
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
|
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
match &self.outer_env { |
||||
Some(outer) => Some(outer.clone()), |
||||
None => None, |
||||
} |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.outer_env = Some(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
return EnvironmentType::Function; |
||||
} |
||||
|
||||
fn get_global_object(&self) -> Option<Value> { |
||||
match &self.outer_env { |
||||
Some(ref outer) => outer.borrow().get_global_object(), |
||||
None => None, |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,197 @@
|
||||
//! # Global Environment Records
|
||||
//!
|
||||
//! A global Environment Record is used to represent the outer most scope that is shared by all
|
||||
//! of the ECMAScript Script elements that are processed in a common realm.
|
||||
//! A global Environment Record provides the bindings for built-in globals (clause 18),
|
||||
//! properties of the global object, and for all top-level declarations (13.2.8, 13.2.10)
|
||||
//! that occur within a Script.
|
||||
//! More info: https://tc39.github.io/ecma262/#sec-global-environment-records
|
||||
|
||||
use crate::environment::declerative_environment_record::DeclerativeEnvironmentRecord; |
||||
use crate::environment::environment_record_trait::EnvironmentRecordTrait; |
||||
use crate::environment::lexical_environment::{Environment, EnvironmentType}; |
||||
use crate::environment::object_environment_record::ObjectEnvironmentRecord; |
||||
use crate::js::value::{Value, ValueData}; |
||||
use gc::Gc; |
||||
use std::collections::HashSet; |
||||
|
||||
#[derive(Trace, Finalize, Clone)] |
||||
pub struct GlobalEnvironmentRecord { |
||||
pub object_record: Box<ObjectEnvironmentRecord>, |
||||
pub global_this_binding: Value, |
||||
pub declerative_record: Box<DeclerativeEnvironmentRecord>, |
||||
pub var_names: HashSet<String>, |
||||
} |
||||
|
||||
impl GlobalEnvironmentRecord { |
||||
pub fn get_this_binding(&self) -> Value { |
||||
return self.global_this_binding.clone(); |
||||
} |
||||
|
||||
pub fn has_var_decleration(&self, name: &String) -> bool { |
||||
return self.var_names.contains(name); |
||||
} |
||||
|
||||
pub fn has_lexical_decleration(&self, name: &String) -> bool { |
||||
self.declerative_record.has_binding(name) |
||||
} |
||||
|
||||
pub fn has_restricted_global_property(&self, name: &String) -> bool { |
||||
let global_object = &self.object_record.bindings; |
||||
let existing_prop = global_object.get_prop(name.clone()); |
||||
match existing_prop { |
||||
Some(prop) => { |
||||
if prop.value.is_undefined() || prop.configurable == true { |
||||
return false; |
||||
} |
||||
true |
||||
} |
||||
None => false, |
||||
} |
||||
} |
||||
|
||||
pub fn create_global_var_binding(&mut self, name: String, deletion: bool) { |
||||
let obj_rec = &mut self.object_record; |
||||
let global_object = &obj_rec.bindings; |
||||
let has_property = global_object.has_field(name.clone()); |
||||
let extensible = global_object.is_extensible(); |
||||
if !has_property && extensible { |
||||
obj_rec.create_mutable_binding(name.clone(), deletion); |
||||
obj_rec.initialize_binding(name.clone(), Gc::new(ValueData::Undefined)); |
||||
} |
||||
|
||||
let var_declared_names = &mut self.var_names; |
||||
if !var_declared_names.contains(&name) { |
||||
var_declared_names.insert(name.clone()); |
||||
} |
||||
} |
||||
|
||||
pub fn create_global_function_binding(&mut self, name: String, value: Value, deletion: bool) { |
||||
let global_object = &mut self.object_record.bindings; |
||||
let existing_prop = global_object.get_prop(name.clone()); |
||||
match existing_prop { |
||||
Some(prop) => { |
||||
if prop.value.is_undefined() || prop.configurable { |
||||
global_object.update_prop( |
||||
name, |
||||
Some(value), |
||||
Some(true), |
||||
Some(true), |
||||
Some(deletion), |
||||
); |
||||
} |
||||
} |
||||
None => { |
||||
global_object.update_prop( |
||||
name, |
||||
Some(value), |
||||
Some(true), |
||||
Some(true), |
||||
Some(deletion), |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for GlobalEnvironmentRecord { |
||||
fn has_binding(&self, name: &String) -> bool { |
||||
if self.declerative_record.has_binding(name) { |
||||
return true; |
||||
} |
||||
self.object_record.has_binding(name) |
||||
} |
||||
|
||||
fn create_mutable_binding(&mut self, name: String, deletion: bool) { |
||||
if self.declerative_record.has_binding(&name) { |
||||
// TODO: change to exception
|
||||
panic!("Binding already exists!"); |
||||
} |
||||
|
||||
self.declerative_record |
||||
.create_mutable_binding(name.clone(), deletion) |
||||
} |
||||
|
||||
fn create_immutable_binding(&mut self, name: String, strict: bool) { |
||||
if self.declerative_record.has_binding(&name) { |
||||
// TODO: change to exception
|
||||
panic!("Binding already exists!"); |
||||
} |
||||
self.declerative_record |
||||
.create_immutable_binding(name.clone(), strict) |
||||
} |
||||
|
||||
fn initialize_binding(&mut self, name: String, value: Value) { |
||||
if self.declerative_record.has_binding(&name) { |
||||
// TODO: assert binding is in the object environment record
|
||||
return self |
||||
.declerative_record |
||||
.initialize_binding(name.clone(), value); |
||||
} |
||||
|
||||
panic!("Should not initialized binding without creating first."); |
||||
} |
||||
|
||||
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) { |
||||
if self.declerative_record.has_binding(&name) { |
||||
return self |
||||
.declerative_record |
||||
.set_mutable_binding(name, value, strict); |
||||
} |
||||
self.object_record.set_mutable_binding(name, value, strict) |
||||
} |
||||
|
||||
fn get_binding_value(&self, name: String, strict: bool) -> Value { |
||||
if self.declerative_record.has_binding(&name) { |
||||
return self.declerative_record.get_binding_value(name, strict); |
||||
} |
||||
return self.object_record.get_binding_value(name, strict); |
||||
} |
||||
|
||||
fn delete_binding(&mut self, name: String) -> bool { |
||||
if self.declerative_record.has_binding(&name) { |
||||
return self.declerative_record.delete_binding(name.clone()); |
||||
} |
||||
|
||||
let global: &Value = &self.object_record.bindings; |
||||
if global.has_field(name.clone()) { |
||||
let status = self.object_record.delete_binding(name.clone()); |
||||
if status { |
||||
let var_names = &mut self.var_names; |
||||
if var_names.contains(&name) { |
||||
var_names.remove(&name); |
||||
return status; |
||||
} |
||||
} |
||||
} |
||||
true |
||||
} |
||||
|
||||
fn has_this_binding(&self) -> bool { |
||||
true |
||||
} |
||||
|
||||
fn has_super_binding(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
fn with_base_object(&self) -> Value { |
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
|
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
None |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, _env: Environment) { |
||||
unimplemented!() |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
return EnvironmentType::Global; |
||||
} |
||||
|
||||
fn get_global_object(&self) -> Option<Value> { |
||||
Some(self.global_this_binding.clone()) |
||||
} |
||||
} |
@ -0,0 +1,214 @@
|
||||
//! # Lexical Environment
|
||||
//!
|
||||
//! https://tc39.github.io/ecma262/#sec-lexical-environment-operations
|
||||
//!
|
||||
//! The following operations are used to operate upon lexical environments
|
||||
//! This is the entrypoint to lexical environments.
|
||||
//!
|
||||
|
||||
use crate::environment::declerative_environment_record::DeclerativeEnvironmentRecord; |
||||
use crate::environment::environment_record_trait::EnvironmentRecordTrait; |
||||
use crate::environment::function_environment_record::{BindingStatus, FunctionEnvironmentRecord}; |
||||
use crate::environment::global_environment_record::GlobalEnvironmentRecord; |
||||
use crate::environment::object_environment_record::ObjectEnvironmentRecord; |
||||
use crate::js::value::{Value, ValueData}; |
||||
use gc::{Gc, GcCell}; |
||||
use std::collections::hash_map::HashMap; |
||||
use std::collections::{HashSet, VecDeque}; |
||||
use std::debug_assert; |
||||
use std::error; |
||||
use std::fmt; |
||||
|
||||
/// Environments are wrapped in a Box and then in a GC wrapper
|
||||
pub type Environment = Gc<GcCell<Box<EnvironmentRecordTrait>>>; |
||||
|
||||
/// Give each environment an easy way to declare its own type
|
||||
/// This helps with comparisons
|
||||
#[derive(Debug)] |
||||
pub enum EnvironmentType { |
||||
Declerative, |
||||
Function, |
||||
Global, |
||||
Object, |
||||
} |
||||
|
||||
pub struct LexicalEnvironment { |
||||
environment_stack: VecDeque<Environment>, |
||||
} |
||||
|
||||
/// An error that occurred during lexing or compiling of the source input.
|
||||
#[derive(Debug, Clone)] |
||||
pub struct EnvironmentError { |
||||
details: String, |
||||
} |
||||
|
||||
impl EnvironmentError { |
||||
pub fn new(msg: &str) -> EnvironmentError { |
||||
EnvironmentError { |
||||
details: msg.to_string(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for EnvironmentError { |
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||
write!(f, "{}", self.details) |
||||
} |
||||
} |
||||
|
||||
impl error::Error for EnvironmentError { |
||||
fn description(&self) -> &str { |
||||
&self.details |
||||
} |
||||
|
||||
fn cause(&self) -> Option<&error::Error> { |
||||
// Generic error, underlying cause isn't tracked.
|
||||
None |
||||
} |
||||
} |
||||
|
||||
impl LexicalEnvironment { |
||||
pub fn new(global: Value) -> LexicalEnvironment { |
||||
let global_env = new_global_environment(global.clone(), global.clone()); |
||||
let mut lexical_env = LexicalEnvironment { |
||||
environment_stack: VecDeque::new(), |
||||
}; |
||||
|
||||
// lexical_env.push(global_env);
|
||||
lexical_env.environment_stack.push_back(global_env); |
||||
lexical_env |
||||
} |
||||
|
||||
pub fn push(&mut self, env: Environment) { |
||||
let current_env: Environment = self.get_current_environment().clone(); |
||||
env.borrow_mut().set_outer_environment(current_env); |
||||
self.environment_stack.push_back(env); |
||||
} |
||||
|
||||
pub fn pop(&mut self) { |
||||
self.environment_stack.pop_back(); |
||||
} |
||||
|
||||
pub fn get_global_object(&self) -> Option<Value> { |
||||
let global = &self.environment_stack[0]; |
||||
global.borrow().get_global_object() |
||||
} |
||||
|
||||
pub fn create_mutable_binding(&mut self, name: String, deletion: bool) { |
||||
self.get_current_environment() |
||||
.borrow_mut() |
||||
.create_mutable_binding(name, deletion) |
||||
} |
||||
|
||||
pub fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) { |
||||
let env = self.get_current_environment(); |
||||
env.borrow_mut().set_mutable_binding(name, value, strict); |
||||
} |
||||
|
||||
pub fn initialize_binding(&mut self, name: String, value: Value) { |
||||
let env = self.get_current_environment(); |
||||
env.borrow_mut().initialize_binding(name, value); |
||||
} |
||||
|
||||
/// get_current_environment_ref is used when you only need to borrow the environment
|
||||
/// (you only need to add a new variable binding, or you want to fetch a value)
|
||||
pub fn get_current_environment_ref(&self) -> &Environment { |
||||
&self |
||||
.environment_stack |
||||
.get(self.environment_stack.len() - 1) |
||||
.unwrap() |
||||
} |
||||
|
||||
/// When neededing to clone an environment (linking it with another environnment)
|
||||
/// cloning is more suited. The GC will remove the env once nothing is linking to it anymore
|
||||
pub fn get_current_environment(&mut self) -> &mut Environment { |
||||
self.environment_stack.back_mut().unwrap() |
||||
} |
||||
|
||||
pub fn get_binding_value(&mut self, name: String) -> Value { |
||||
let env: Environment = self.get_current_environment().clone(); |
||||
let borrowed_env = env.borrow(); |
||||
let result = borrowed_env.has_binding(&name); |
||||
if result { |
||||
return borrowed_env.get_binding_value(name, false); |
||||
} |
||||
|
||||
// Check outer scope
|
||||
if borrowed_env.get_outer_environment().is_some() { |
||||
let mut outer: Option<Environment> = borrowed_env.get_outer_environment(); |
||||
while outer.is_some() { |
||||
if outer.as_ref().unwrap().borrow().has_binding(&name) { |
||||
return outer.unwrap().borrow().get_binding_value(name, false); |
||||
} |
||||
outer = outer.unwrap().borrow().get_outer_environment(); |
||||
} |
||||
} |
||||
|
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
} |
||||
|
||||
pub fn new_declerative_environment(env: Option<Environment>) -> Environment { |
||||
let boxed_env = Box::new(DeclerativeEnvironmentRecord { |
||||
env_rec: HashMap::new(), |
||||
outer_env: env, |
||||
}); |
||||
|
||||
Gc::new(GcCell::new(boxed_env)) |
||||
} |
||||
|
||||
pub fn new_function_environment( |
||||
f: Value, |
||||
new_target: Value, |
||||
outer: Option<Environment>, |
||||
) -> Environment { |
||||
debug_assert!(f.is_function()); |
||||
debug_assert!(new_target.is_object() || new_target.is_undefined()); |
||||
Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord { |
||||
env_rec: HashMap::new(), |
||||
function_object: f.clone(), |
||||
this_binding_status: BindingStatus::Uninitialized, // hardcoding to unitialized for now until short functions are properly supported
|
||||
home_object: Gc::new(ValueData::Undefined), |
||||
new_target: new_target, |
||||
outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.github.io/ecma262/#sec-ecmascript-function-objects
|
||||
this_value: Gc::new(ValueData::Undefined), // TODO: this_value should start as an Option as its not always there to begin with
|
||||
}))) |
||||
} |
||||
|
||||
pub fn new_object_environment(object: Value, environment: Option<Environment>) -> Environment { |
||||
Gc::new(GcCell::new(Box::new(ObjectEnvironmentRecord { |
||||
bindings: object, |
||||
outer_env: environment, |
||||
/// Object Environment Records created for with statements (13.11)
|
||||
/// can provide their binding object as an implicit this value for use in function calls.
|
||||
/// The capability is controlled by a withEnvironment Boolean value that is associated
|
||||
/// with each object Environment Record. By default, the value of withEnvironment is false
|
||||
/// for any object Environment Record.
|
||||
with_environment: false, |
||||
}))) |
||||
} |
||||
|
||||
pub fn new_global_environment(global: Value, this_value: Value) -> Environment { |
||||
let obj_rec = Box::new(ObjectEnvironmentRecord { |
||||
bindings: global, |
||||
outer_env: None, |
||||
/// Object Environment Records created for with statements (13.11)
|
||||
/// can provide their binding object as an implicit this value for use in function calls.
|
||||
/// The capability is controlled by a withEnvironment Boolean value that is associated
|
||||
/// with each object Environment Record. By default, the value of withEnvironment is false
|
||||
/// for any object Environment Record.
|
||||
with_environment: false, |
||||
}); |
||||
|
||||
let dcl_rec = Box::new(DeclerativeEnvironmentRecord { |
||||
env_rec: HashMap::new(), |
||||
outer_env: None, |
||||
}); |
||||
|
||||
Gc::new(GcCell::new(Box::new(GlobalEnvironmentRecord { |
||||
object_record: obj_rec, |
||||
global_this_binding: this_value, |
||||
declerative_record: dcl_rec, |
||||
var_names: HashSet::new(), |
||||
}))) |
||||
} |
@ -0,0 +1,6 @@
|
||||
pub mod declerative_environment_record; |
||||
pub mod environment_record_trait; |
||||
pub mod function_environment_record; |
||||
pub mod global_environment_record; |
||||
pub mod lexical_environment; |
||||
pub mod object_environment_record; |
@ -0,0 +1,124 @@
|
||||
//! # Object Records
|
||||
//!
|
||||
//! Each object Environment Record is associated with an object called its binding object.
|
||||
//! An object Environment Record binds the set of string identifier names that directly
|
||||
//! correspond to the property names of its binding object.
|
||||
//! Property keys that are not strings in the form of an IdentifierName are not included in the set of bound identifiers.
|
||||
//! More info: [Object Records](https://tc39.github.io/ecma262/#sec-object-environment-records)
|
||||
|
||||
use crate::environment::environment_record_trait::EnvironmentRecordTrait; |
||||
use crate::environment::lexical_environment::{Environment, EnvironmentType}; |
||||
use crate::js::object::Property; |
||||
use crate::js::value::{Value, ValueData}; |
||||
use gc::Gc; |
||||
|
||||
#[derive(Trace, Finalize, Clone)] |
||||
pub struct ObjectEnvironmentRecord { |
||||
pub bindings: Value, |
||||
pub with_environment: bool, |
||||
pub outer_env: Option<Environment>, |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for ObjectEnvironmentRecord { |
||||
fn has_binding(&self, name: &String) -> bool { |
||||
if !self.bindings.has_field(name.to_string()) { |
||||
return false; |
||||
} |
||||
if !self.with_environment { |
||||
return true; |
||||
} |
||||
|
||||
// TODO: implement unscopables
|
||||
true |
||||
} |
||||
|
||||
fn create_mutable_binding(&mut self, name: String, deletion: bool) { |
||||
// TODO: could save time here and not bother generating a new undefined object,
|
||||
// only for it to be replace with the real value later. We could just add the name to a Vector instead
|
||||
let bindings = &mut self.bindings; |
||||
let uninitialized = Gc::new(ValueData::Undefined); |
||||
let mut prop = Property::new(uninitialized); |
||||
prop.enumerable = true; |
||||
prop.writable = true; |
||||
prop.configurable = deletion; |
||||
bindings.set_prop(name, prop); |
||||
} |
||||
|
||||
fn create_immutable_binding(&mut self, _name: String, _strict: bool) { |
||||
unimplemented!() |
||||
} |
||||
|
||||
fn initialize_binding(&mut self, name: String, value: Value) { |
||||
// We should never need to check if a binding has been created,
|
||||
// As all calls to create_mutable_binding are followed by initialized binding
|
||||
// The below is just a check.
|
||||
debug_assert!(self.has_binding(&name)); |
||||
return self.set_mutable_binding(name, value, false); |
||||
} |
||||
|
||||
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) { |
||||
debug_assert!(value.is_object() || value.is_function()); |
||||
|
||||
let bindings = &mut self.bindings; |
||||
bindings.update_prop(name, Some(value.clone()), None, None, Some(strict)); |
||||
} |
||||
|
||||
fn get_binding_value(&self, name: String, strict: bool) -> Value { |
||||
if self.bindings.has_field(name.clone()) { |
||||
return self.bindings.get_field(name); |
||||
} |
||||
|
||||
if !strict { |
||||
return Gc::new(ValueData::Undefined); |
||||
} |
||||
|
||||
// TODO: throw error here
|
||||
// Error handling not implemented yet
|
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
|
||||
fn delete_binding(&mut self, name: String) -> bool { |
||||
self.bindings.remove_prop(&name); |
||||
true |
||||
} |
||||
|
||||
fn has_this_binding(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
fn has_super_binding(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
fn with_base_object(&self) -> Value { |
||||
// Object Environment Records return undefined as their
|
||||
// WithBaseObject unless their withEnvironment flag is true.
|
||||
if self.with_environment { |
||||
return self.bindings.clone(); |
||||
} |
||||
|
||||
Gc::new(ValueData::Undefined) |
||||
} |
||||
|
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
match &self.outer_env { |
||||
Some(outer) => Some(outer.clone()), |
||||
None => None, |
||||
} |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.outer_env = Some(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
return EnvironmentType::Function; |
||||
} |
||||
|
||||
fn get_global_object(&self) -> Option<Value> { |
||||
match &self.outer_env { |
||||
Some(outer) => outer.borrow().get_global_object(), |
||||
None => None, |
||||
} |
||||
} |
||||
} |
@ -1,11 +1,12 @@
|
||||
extern crate chrono; |
||||
extern crate gc; |
||||
extern crate rand; |
||||
extern crate serde_json; |
||||
extern crate chrono; |
||||
|
||||
#[macro_use] |
||||
extern crate gc_derive; |
||||
|
||||
pub mod environment; |
||||
pub mod exec; |
||||
pub mod js; |
||||
pub mod syntax; |
||||
|
Loading…
Reference in new issue