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 gc; |
||||||
extern crate rand; |
extern crate rand; |
||||||
extern crate serde_json; |
extern crate serde_json; |
||||||
extern crate chrono; |
|
||||||
|
|
||||||
#[macro_use] |
#[macro_use] |
||||||
extern crate gc_derive; |
extern crate gc_derive; |
||||||
|
|
||||||
|
pub mod environment; |
||||||
pub mod exec; |
pub mod exec; |
||||||
pub mod js; |
pub mod js; |
||||||
pub mod syntax; |
pub mod syntax; |
||||||
|
Loading…
Reference in new issue