diff --git a/Cargo.lock b/Cargo.lock index a0c4def9be..28c6c8f1c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,18 @@ # It is not intended for manual editing. [[package]] name = "Boa" -version = "0.1.6" +version = "0.1.7" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "gc 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gc 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "gc_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -23,7 +23,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -31,26 +31,17 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "fuchsia-cprng" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "gc" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -65,12 +56,12 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.43" +version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -93,52 +84,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rand" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ryu" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.80" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_json" -version = "1.0.32" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -170,12 +161,12 @@ dependencies = [ [[package]] name = "time" -version = "0.1.40" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -185,7 +176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -203,30 +194,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bitflags 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1fa8ad26490b0a5cfec99089952250301b6716cdeaa7c9ab229598fb82ab66" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum gc 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "630b15fafc2270fc89de904da9ebb8b950642a5ed7bd99ec3f95558b0831ea9a" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum gc 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75656800ec248b3d0c33b685e442a67e7308009ae59b1f8eb60c4f09ebebb512" "checksum gc_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2501c15cbaf28a0c2214617aa85351982a933161d7937fe6cd71c855364e0ea6" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" -"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" -"checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef" -"checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" +"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index fb2c017b88..40def0bea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "Boa" -version = "0.1.6" +version = "0.1.7" authors = ["Jason Williams "] description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language." homepage = "https://github.com/jasonwilliams/boa" diff --git a/src/bin/bin.rs b/src/bin/bin.rs index 2fd2289089..d628694f1e 100644 --- a/src/bin/bin.rs +++ b/src/bin/bin.rs @@ -12,7 +12,6 @@ pub fn main() { // Setup executor let expr = Parser::new(tokens).parse_all().unwrap(); - // print!("{:#?}", expr); let mut engine: Interpreter = Executor::new(); let result = engine.run(&expr); diff --git a/src/lib/environment/declerative_environment_record.rs b/src/lib/environment/declerative_environment_record.rs new file mode 100644 index 0000000000..c5dfd2899c --- /dev/null +++ b/src/lib/environment/declerative_environment_record.rs @@ -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, + 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, + pub outer_env: Option, +} + +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 { + 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 { + match &self.outer_env { + Some(outer) => outer.borrow().get_global_object(), + None => None, + } + } +} diff --git a/src/lib/environment/environment_record_trait.rs b/src/lib/environment/environment_record_trait.rs new file mode 100644 index 0000000000..0eb1cb4349 --- /dev/null +++ b/src/lib/environment/environment_record_trait.rs @@ -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; + + /// 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; +} diff --git a/src/lib/environment/function_environment_record.rs b/src/lib/environment/function_environment_record.rs new file mode 100644 index 0000000000..504cf954e9 --- /dev/null +++ b/src/lib/environment/function_environment_record.rs @@ -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, + /// 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, +} + +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 { + 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 { + match &self.outer_env { + Some(ref outer) => outer.borrow().get_global_object(), + None => None, + } + } +} diff --git a/src/lib/environment/global_environment_record.rs b/src/lib/environment/global_environment_record.rs new file mode 100644 index 0000000000..d3c8d89ac3 --- /dev/null +++ b/src/lib/environment/global_environment_record.rs @@ -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, + pub global_this_binding: Value, + pub declerative_record: Box, + pub var_names: HashSet, +} + +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 { + 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 { + Some(self.global_this_binding.clone()) + } +} diff --git a/src/lib/environment/lexical_environment.rs b/src/lib/environment/lexical_environment.rs new file mode 100644 index 0000000000..41aa425de2 --- /dev/null +++ b/src/lib/environment/lexical_environment.rs @@ -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>>; + +/// 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, +} + +/// 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 { + 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 = 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 { + 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 { + 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 { + 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(), + }))) +} diff --git a/src/lib/environment/mod.rs b/src/lib/environment/mod.rs new file mode 100644 index 0000000000..c96c6af488 --- /dev/null +++ b/src/lib/environment/mod.rs @@ -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; diff --git a/src/lib/environment/object_environment_record.rs b/src/lib/environment/object_environment_record.rs new file mode 100644 index 0000000000..3922f0a6dc --- /dev/null +++ b/src/lib/environment/object_environment_record.rs @@ -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, +} + +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 { + 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 { + match &self.outer_env { + Some(outer) => outer.borrow().get_global_object(), + None => None, + } + } +} diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 13b7965219..720e4ada2f 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -1,3 +1,4 @@ +use crate::environment::lexical_environment::{new_function_environment, LexicalEnvironment}; use crate::js::function::{Function, RegularFunction}; use crate::js::object::{INSTANCE_PROTOTYPE, PROTOTYPE}; use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData}; @@ -8,27 +9,11 @@ use crate::syntax::ast::op::{BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}; use gc::{Gc, GcCell}; use std::borrow::Borrow; use std::collections::HashMap; -/// A variable scope -#[derive(Trace, Finalize, Clone, Debug)] -pub struct Scope { - /// The value of `this` in the scope - pub this: Value, - /// The variables declared in the scope - pub vars: Value, -} /// An execution engine pub trait Executor { /// Make a new execution engine fn new() -> Self; - /// Set a global variable called `name` with the value `val` - fn set_global(&mut self, name: String, val: Value) -> Value; - /// Resolve the global variable `name` - fn get_global(&self, name: String) -> Value; - /// Create a new scope and return it - fn make_scope(&mut self, this: Value) -> Scope; - /// Destroy the current scope - fn destroy_scope(&mut self) -> Scope; /// Run an expression fn run(&mut self, expr: &Expr) -> ResultValue; } @@ -36,59 +21,25 @@ pub trait Executor { /// A Javascript intepreter pub struct Interpreter { /// An object representing the global object - global: Value, - /// The scopes - pub scopes: Vec, -} - -impl Interpreter { - #[inline(always)] - /// Get the current scope - pub fn scope(&self) -> &Scope { - self.scopes.get(self.scopes.len() - 1).unwrap() - } + environment: LexicalEnvironment, } impl Executor for Interpreter { fn new() -> Interpreter { let global = ValueData::new_obj(None); - object::init(global.clone()); - console::init(global.clone()); - math::init(global.clone()); - array::init(global.clone()); - function::init(global.clone()); - json::init(global.clone()); - string::init(global.clone()); + let test = ValueData::new_obj(None); + object::init(&global); + console::init(&global); + math::init(&global); + array::init(&global); + function::init(&global); + json::init(&global); + string::init(&global); Interpreter { - global: global.clone(), - scopes: vec![Scope { - this: global.clone(), - vars: global.clone(), - }], + environment: LexicalEnvironment::new(global.clone()), } } - fn set_global(&mut self, name: String, val: Value) -> Value { - self.global.borrow().set_field(name, val) - } - - fn get_global(&self, name: String) -> Value { - self.global.borrow().get_field(name) - } - - fn make_scope(&mut self, this: Value) -> Scope { - let scope = Scope { - this: this, - vars: ValueData::new_obj(None), - }; - self.scopes.push(scope.clone()); - scope - } - - fn destroy_scope(&mut self) -> Scope { - self.scopes.pop().unwrap() - } - fn run(&mut self, expr: &Expr) -> ResultValue { match expr.def { ExprDef::ConstExpr(Const::Null) => Ok(to_value(None::<()>)), @@ -112,21 +63,7 @@ impl Executor for Interpreter { Ok(obj) } ExprDef::LocalExpr(ref name) => { - let mut val = Gc::new(ValueData::Undefined); - for scope in self.scopes.iter().rev() { - let vars = scope.vars.clone(); - let vars_ptr = vars.borrow(); - match *vars_ptr.clone() { - ValueData::Object(ref obj, _) => match obj.borrow().get(name) { - Some(v) => { - val = v.value.clone(); - break; - } - None => (), - }, - _ => unreachable!(), - } - } + let val = self.environment.get_binding_value(name.to_string()); Ok(val) } ExprDef::GetConstFieldExpr(ref obj, ref field) => { @@ -152,28 +89,39 @@ impl Executor for Interpreter { obj.borrow().get_field(field.borrow().to_string()), ) } - _ => (self.global.clone(), self.run(&callee.clone())?), + _ => ( + self.environment.get_global_object().unwrap(), + self.run(&callee.clone())?, + ), // 'this' binding should come from the function's self-contained environment }; let mut v_args = Vec::with_capacity(args.len()); for arg in args.iter() { v_args.push(self.run(arg)?); } match *func { - ValueData::Function(ref func) => match *func.borrow() { + ValueData::Function(ref inner_func) => match *inner_func.borrow() { Function::NativeFunc(ref ntv) => { let func = ntv.data; func(this, self.run(callee)?, v_args) } Function::RegularFunc(ref data) => { - let scope = self.make_scope(this); - let scope_vars_ptr = scope.vars.borrow(); + let env = &mut self.environment; + // New target (second argument) is only needed for constructors, just pass undefined + let undefined = Gc::new(ValueData::Undefined); + env.push(new_function_environment( + func.clone(), + undefined, + Some(env.get_current_environment_ref().clone()), + )); for i in 0..data.args.len() { let name = data.args.get(i).unwrap(); let expr = v_args.get(i).unwrap(); - scope_vars_ptr.set_field(name.clone(), expr.clone()); + self.environment.create_mutable_binding(name.clone(), false); + self.environment + .initialize_binding(name.clone(), expr.to_owned()); } let result = self.run(&data.expr); - self.destroy_scope(); + self.environment.pop(); result } }, @@ -226,23 +174,26 @@ impl Executor for Interpreter { Ok(result) } ExprDef::ObjectDeclExpr(ref map) => { - let obj = ValueData::new_obj(Some(self.global.clone())); + let global_val = &self.environment.get_global_object().unwrap(); + let obj = ValueData::new_obj(Some(global_val)); for (key, val) in map.iter() { - obj.borrow().set_field(key.clone(), r#try!(self.run(val))); + obj.borrow().set_field(key.clone(), self.run(val)?); } Ok(obj) } ExprDef::ArrayDeclExpr(ref arr) => { - let arr_map = ValueData::new_obj(Some(self.global.clone())); + let global_val = &self.environment.get_global_object().unwrap(); + let arr_map = ValueData::new_obj(Some(global_val)); let mut index: i32 = 0; for val in arr.iter() { - let val = r#try!(self.run(val)); + let val = self.run(val)?; arr_map.borrow().set_field(index.to_string(), val); index += 1; } arr_map.borrow().set_field_slice( INSTANCE_PROTOTYPE, - self.get_global("Array".to_string()) + self.environment + .get_binding_value("Array".to_string()) .borrow() .get_field_slice(PROTOTYPE), ); @@ -254,9 +205,10 @@ impl Executor for Interpreter { Function::RegularFunc(RegularFunction::new(*expr.clone(), args.clone())); let val = Gc::new(ValueData::Function(GcCell::new(function))); if name.is_some() { - self.global - .borrow() - .set_field(name.clone().unwrap(), val.clone()); + self.environment + .create_mutable_binding(name.clone().unwrap(), false); + self.environment + .initialize_binding(name.clone().unwrap(), val.clone()) } Ok(val) } @@ -343,21 +295,28 @@ impl Executor for Interpreter { this.borrow() .set_field_slice(INSTANCE_PROTOTYPE, func.borrow().get_field_slice(PROTOTYPE)); match *func { - ValueData::Function(ref func) => match func.clone().into_inner() { + ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() { Function::NativeFunc(ref ntv) => { let func = ntv.data; func(this, self.run(callee)?, v_args) } Function::RegularFunc(ref data) => { - let scope = self.make_scope(this); - let scope_vars_ptr = scope.vars.borrow(); + // Create new scope + let env = &mut self.environment; + env.push(new_function_environment( + func.clone(), + this.clone(), + Some(env.get_current_environment_ref().clone()), + )); + for i in 0..data.args.len() { let name = data.args.get(i).unwrap(); let expr = v_args.get(i).unwrap(); - scope_vars_ptr.set_field(name.clone(), (*expr).clone()); + env.create_mutable_binding(name.clone(), false); + env.initialize_binding(name.clone(), expr.to_owned()); } let result = self.run(&data.expr); - self.destroy_scope(); + self.environment.pop(); result } }, @@ -370,13 +329,12 @@ impl Executor for Interpreter { }, ExprDef::ThrowExpr(ref ex) => Err(r#try!(self.run(ex))), ExprDef::AssignExpr(ref ref_e, ref val_e) => { - let val = r#try!(self.run(val_e)); + let val = self.run(val_e)?; match ref_e.def { ExprDef::LocalExpr(ref name) => { - self.scope() - .vars - .borrow() - .set_field(name.clone(), val.clone()); + self.environment.create_mutable_binding(name.clone(), false); + self.environment + .initialize_binding(name.clone(), val.clone()); } ExprDef::GetConstFieldExpr(ref obj, ref field) => { let val_obj = r#try!(self.run(obj)); @@ -387,15 +345,14 @@ impl Executor for Interpreter { Ok(val) } ExprDef::VarDeclExpr(ref vars) => { - let scope_vars = self.scope().vars.clone(); - let scope_vars_ptr = scope_vars.borrow(); for var in vars.iter() { let (name, value) = var.clone(); let val = match value { Some(v) => r#try!(self.run(&v)), None => Gc::new(ValueData::Null), }; - scope_vars_ptr.set_field(name.clone(), val); + self.environment.create_mutable_binding(name.clone(), false); + self.environment.initialize_binding(name, val); } Ok(Gc::new(ValueData::Undefined)) } diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index 54cf913b7f..e12015066b 100644 --- a/src/lib/js/array.rs +++ b/src/lib/js/array.rs @@ -1,6 +1,6 @@ -use gc::Gc; use crate::js::function::NativeFunctionData; use crate::js::value::{to_value, ResultValue, Value, ValueData}; +use gc::Gc; /// Create a new array pub fn make_array(this: Value, _: Value, _: Vec) -> ResultValue { @@ -14,6 +14,6 @@ pub fn _create() -> Value { array } /// Initialise the global object with the `Array` object -pub fn init(global: Value) { +pub fn init(global: &Value) { global.set_field_slice("Array", _create()); } diff --git a/src/lib/js/boolean.rs b/src/lib/js/boolean.rs index dc0ff01c51..da01dbf827 100644 --- a/src/lib/js/boolean.rs +++ b/src/lib/js/boolean.rs @@ -11,7 +11,7 @@ pub fn _create(global: Value) -> Value { boolean } /// Initialise the global object with the `Error` object -pub fn init(global: Value) { +pub fn init(global: &Value) { let global_ptr = global.borrow(); global_ptr.set_field_slice("Boolean", _create(global)); } diff --git a/src/lib/js/console.rs b/src/lib/js/console.rs index 50436be7b2..8e5c3445d9 100644 --- a/src/lib/js/console.rs +++ b/src/lib/js/console.rs @@ -65,7 +65,7 @@ pub fn error(_: Value, _: Value, args: Vec) -> ResultValue { Ok(Gc::new(ValueData::Undefined)) } /// Create a new `console` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let console = ValueData::new_obj(Some(global)); console.set_field_slice("log", to_value(log as NativeFunctionData)); console.set_field_slice("error", to_value(error as NativeFunctionData)); @@ -73,6 +73,6 @@ pub fn _create(global: Value) -> Value { console } /// Initialise the global object with the `console` object -pub fn init(global: Value) { - global.set_field_slice("console", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("console", _create(global)); } diff --git a/src/lib/js/error.rs b/src/lib/js/error.rs index b4efb94c9a..71cd304208 100644 --- a/src/lib/js/error.rs +++ b/src/lib/js/error.rs @@ -1,7 +1,7 @@ -use gc::Gc; use crate::js::function::NativeFunctionData; use crate::js::object::PROTOTYPE; use crate::js::value::{to_value, ResultValue, Value, ValueData}; +use gc::Gc; /// Create a new error pub fn make_error(this: Value, _: Value, args: Vec) -> ResultValue { @@ -17,7 +17,7 @@ pub fn to_string(this: Value, _: Value, _: Vec) -> ResultValue { Ok(to_value(format!("{}: {}", name, message).to_string())) } /// Create a new `Error` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let prototype = ValueData::new_obj(Some(global)); prototype.set_field_slice("message", to_value("")); prototype.set_field_slice("name", to_value("Error")); @@ -27,6 +27,6 @@ pub fn _create(global: Value) -> Value { error } /// Initialise the global object with the `Error` object -pub fn init(global: Value) { - global.set_field_slice("Error", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("Error", _create(global)); } diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index 8b367f6f2e..df5748f62f 100644 --- a/src/lib/js/function.rs +++ b/src/lib/js/function.rs @@ -1,8 +1,8 @@ -use gc::Gc; use crate::js::object::{ObjectData, Property}; use crate::js::value::{to_value, ResultValue, Value, ValueData}; -use std::collections::HashMap; use crate::syntax::ast::expr::Expr; +use gc::Gc; +use std::collections::HashMap; /// fn(this, callee, arguments) pub type NativeFunctionData = fn(Value, Value, Vec) -> ResultValue; @@ -73,7 +73,7 @@ pub fn _create() -> Value { to_value(function) } /// Initialise the global object with the `Function` object -pub fn init(global: Value) { +pub fn init(global: &Value) { let global_ptr = global; global_ptr.set_field_slice("Function", _create()); } diff --git a/src/lib/js/json.rs b/src/lib/js/json.rs index b15c16947b..01251d3ce6 100644 --- a/src/lib/js/json.rs +++ b/src/lib/js/json.rs @@ -20,7 +20,7 @@ pub fn stringify(_: Value, _: Value, args: Vec) -> ResultValue { } /// Create a new `JSON` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let object = ValueData::new_obj(Some(global)); object.set_field_slice("stringify", to_value(stringify as NativeFunctionData)); object.set_field_slice("parse", to_value(parse as NativeFunctionData)); @@ -28,6 +28,6 @@ pub fn _create(global: Value) -> Value { } /// Initialise the global object with the `JSON` object -pub fn init(global: Value) { - global.set_field_slice("JSON", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("JSON", _create(global)); } diff --git a/src/lib/js/math.rs b/src/lib/js/math.rs index 6d5574b426..59fadb33c1 100644 --- a/src/lib/js/math.rs +++ b/src/lib/js/math.rs @@ -186,7 +186,7 @@ pub fn tan(_: Value, _: Value, args: Vec) -> ResultValue { })) } /// Create a new `Math` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let math = ValueData::new_obj(Some(global)); math.set_field_slice("E", to_value(f64::consts::E)); math.set_field_slice("LN2", to_value(f64::consts::LN_2)); @@ -218,6 +218,6 @@ pub fn _create(global: Value) -> Value { math } /// Initialise the `Math` object on the global object -pub fn init(global: Value) { - global.set_field_slice("Math", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("Math", _create(global)); } diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index 6aecd10b56..bd3e5415c8 100644 --- a/src/lib/js/object.rs +++ b/src/lib/js/object.rs @@ -118,7 +118,7 @@ pub fn has_own_prop(this: Value, _: Value, args: Vec) -> ResultValue { } /// Create a new `Object` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let object = to_value(make_object as NativeFunctionData); let prototype = ValueData::new_obj(Some(global)); prototype.set_field_slice( @@ -144,6 +144,6 @@ pub fn _create(global: Value) -> Value { } /// Initialise the `Object` object on the global object -pub fn init(global: Value) { - global.set_field_slice("Object", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("Object", _create(global)); } diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index dee8f674b2..4939173c81 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -78,7 +78,7 @@ pub fn char_code_at(this: Value, _: Value, args: Vec) -> ResultValue { } /// Create a new `String` object -pub fn _create(global: Value) -> Value { +pub fn _create(global: &Value) -> Value { let string = to_value(make_string as NativeFunctionData); let proto = ValueData::new_obj(Some(global)); let prop = Property { @@ -97,8 +97,8 @@ pub fn _create(global: Value) -> Value { string } /// Initialise the `String` object on the global object -pub fn init(global: Value) { - global.set_field_slice("String", _create(global.clone())); +pub fn init(global: &Value) { + global.set_field_slice("String", _create(global)); } #[cfg(test)] @@ -107,7 +107,7 @@ mod tests { #[test] fn check_string_constructor_is_function() { let global = ValueData::new_obj(None); - let string_constructor = _create(global); + let string_constructor = _create(&global); assert_eq!(string_constructor.is_function(), true); } } diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index 44e722a5d5..f8c8d2ae46 100644 --- a/src/lib/js/value.rs +++ b/src/lib/js/value.rs @@ -45,7 +45,7 @@ pub enum ValueData { impl ValueData { /// Returns a new empty object - pub fn new_obj(global: Option) -> Value { + pub fn new_obj(global: Option<&Value>) -> Value { let mut obj: ObjectData = HashMap::new(); let private_obj: ObjectData = HashMap::new(); if global.is_some() { @@ -72,6 +72,14 @@ impl ValueData { )) } + /// This will tell us if we can exten an object or not, not properly implemented yet, for now always returns true + /// For scalar types it should be false, for objects check the private field for extensibilaty. By default true + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal would turn extensible to false + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze would also turn extensible to false + pub fn is_extensible(&self) -> bool { + true + } + /// Returns true if the value is an object pub fn is_object(&self) -> bool { match *self { @@ -174,6 +182,20 @@ impl ValueData { } } + /// remove_prop removes a property from a Value object. + /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned + pub fn remove_prop(&self, field: &String) { + match *self { + ValueData::Object(ref obj, _) => obj.borrow_mut().deref_mut().remove(field), + // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * + ValueData::Function(ref func) => match func.borrow_mut().deref_mut() { + Function::NativeFunc(ref mut func) => func.object.remove(field), + Function::RegularFunc(ref mut func) => func.object.remove(field), + }, + _ => None, + }; + } + /// Resolve the property in the object /// Returns a copy of the Property pub fn get_prop(&self, field: String) -> Option { @@ -188,6 +210,8 @@ impl ValueData { let obj: ObjectData = match *self { ValueData::Object(ref obj, _) => { let hash = obj.clone(); + // TODO: This will break, we should return a GcCellRefMut instead + // into_inner will consume the wrapped value and remove it from the hashmap hash.into_inner() } // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * @@ -207,6 +231,45 @@ impl ValueData { } } + /// update_prop will overwrite individual [Property] fields, unlike + /// Set_prop, which will overwrite prop with a new Property + /// Mostly used internally for now + pub fn update_prop( + &self, + field: String, + value: Option, + enumerable: Option, + writable: Option, + configurable: Option, + ) { + let obj: Option = match self { + ValueData::Object(ref obj, _) => Some(obj.borrow_mut().deref_mut().clone()), + // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * + ValueData::Function(ref func) => match func.borrow_mut().deref_mut() { + Function::NativeFunc(ref mut func) => Some(func.object.clone()), + Function::RegularFunc(ref mut func) => Some(func.object.clone()), + }, + _ => None, + }; + + if obj.is_none() { + return (); + } + + let mut hashmap = obj.unwrap(); + // Use value, or walk up the prototype chain + match hashmap.get_mut(&field) { + Some(ref mut prop) => { + prop.value = value.unwrap_or(prop.value.clone()); + prop.enumerable = enumerable.unwrap_or(prop.enumerable); + prop.writable = writable.unwrap_or(prop.writable); + prop.configurable = configurable.unwrap_or(prop.configurable); + } + // Try again with next prop up the chain + None => (), + } + } + /// Resolve the property in the object /// Returns a copy of the Property pub fn get_private_prop(&self, field: String) -> Option { @@ -270,6 +333,14 @@ impl ValueData { } } + /// Check to see if the Value has the field, mainly used by environment records + pub fn has_field(&self, field: String) -> bool { + match self.get_prop(field) { + Some(_) => true, + None => false, + } + } + /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist pub fn get_field_slice<'a>(&self, field: &'a str) -> Value { self.get_field(field.to_string()) diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 1c4a555f15..b3e85ace6d 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -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; diff --git a/tests/js/test.js b/tests/js/test.js index 8fcf8d126e..0855559585 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1,7 @@ -var a = new String("ABC"); -a.charCodeAt(-1); +var a = "World"; +function jason() { + console.log(a); + return true; +} + +jason();