Browse Source

merging master

pull/18/head
Jason Williams 5 years ago
parent
commit
d7d7133b3f
  1. 102
      Cargo.lock
  2. 2
      Cargo.toml
  3. 4
      README.md
  4. 173
      src/lib/environment/declerative_environment_record.rs
  5. 79
      src/lib/environment/environment_record_trait.rs
  6. 246
      src/lib/environment/function_environment_record.rs
  7. 197
      src/lib/environment/global_environment_record.rs
  8. 214
      src/lib/environment/lexical_environment.rs
  9. 6
      src/lib/environment/mod.rs
  10. 124
      src/lib/environment/object_environment_record.rs
  11. 161
      src/lib/exec.rs
  12. 4
      src/lib/js/array.rs
  13. 2
      src/lib/js/boolean.rs
  14. 6
      src/lib/js/console.rs
  15. 8
      src/lib/js/error.rs
  16. 6
      src/lib/js/function.rs
  17. 6
      src/lib/js/json.rs
  18. 6
      src/lib/js/math.rs
  19. 6
      src/lib/js/object.rs
  20. 8
      src/lib/js/string.rs
  21. 73
      src/lib/js/value.rs
  22. 1
      src/lib/lib.rs

102
Cargo.lock generated

@ -2,19 +2,19 @@
# 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)",
"wasm-bindgen 0.2.43 (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]]
@ -34,7 +34,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]]
@ -42,26 +42,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]]
@ -76,7 +67,7 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -86,7 +77,7 @@ 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]]
@ -133,52 +124,52 @@ dependencies = [
[[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]]
@ -220,12 +211,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]]
@ -288,7 +279,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)",
@ -306,36 +297,35 @@ 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 bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4639720be048090544634e0402490838995ccdc9d2fe648f528f30d3c33ae71f"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"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 itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"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 proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
"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 syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe"
"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 unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "546e4ab1bf7f9a3532d21472efd72d01a23f55abd885c60b165f393394dbad95"
@ -343,6 +333,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum wasm-bindgen-macro 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "f2a033fc6bfd5e486a488b0e19d7d1bb29e667ebb91db85f698381a8aa831786"
"checksum wasm-bindgen-macro-support 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "fba68375ef8f095c4a169c093c95ed2e1b5e44f7872f3bcbcafe2c51b4a80480"
"checksum wasm-bindgen-shared 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "321949f4d7f7bf7a49dccd464bdc46581b180f761d9505e4943926d50b2a4a64"
"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"

2
Cargo.toml

@ -1,6 +1,6 @@
[package]
name = "Boa"
version = "0.1.6"
version = "0.1.7"
authors = ["Jason Williams <jase.williams@gmail.com>"]
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"

4
README.md

@ -22,7 +22,9 @@ In the console you can use `window.evaluate` to pass JavaScript in
#### Roadmap
- ~string.length~ - works in 0.1.5
- Adding support for constructors
- Adding support for constructors - half working, in progress
- ~better environment and scope support~ - Finished (05/05/2019)
- `let` and `const` support - Next
- Better error output
- Passing [test262](https://github.com/tc39/test262)

173
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<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,
}
}
}

79
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<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>;
}

246
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<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,
}
}
}

197
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<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())
}
}

214
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<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(),
})))
}

6
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;

124
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<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,
}
}
}

161
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<Scope>,
}
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))
}

4
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<Value>) -> 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());
}

2
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));
}

6
src/lib/js/console.rs

@ -65,7 +65,7 @@ pub fn error(_: Value, _: Value, args: Vec<Value>) -> 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));
}

8
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<Value>) -> ResultValue {
@ -17,7 +17,7 @@ pub fn to_string(this: Value, _: Value, _: Vec<Value>) -> 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));
}

6
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<Value>) -> 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());
}

6
src/lib/js/json.rs

@ -20,7 +20,7 @@ pub fn stringify(_: Value, _: Value, args: Vec<Value>) -> 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));
}

6
src/lib/js/math.rs

@ -186,7 +186,7 @@ pub fn tan(_: Value, _: Value, args: Vec<Value>) -> 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));
}

6
src/lib/js/object.rs

@ -118,7 +118,7 @@ pub fn has_own_prop(this: Value, _: Value, args: Vec<Value>) -> 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));
}

8
src/lib/js/string.rs

@ -78,7 +78,7 @@ pub fn char_code_at(this: Value, _: Value, args: Vec<Value>) -> 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);
}
}

73
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>) -> 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<Property> {
@ -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<Value>,
enumerable: Option<bool>,
writable: Option<bool>,
configurable: Option<bool>,
) {
let obj: Option<ObjectData> = 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<Property> {
@ -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())

1
src/lib/lib.rs

@ -6,6 +6,7 @@ extern crate serde_json;
#[macro_use]
extern crate gc_derive;
pub mod environment;
pub mod exec;
pub mod js;
pub mod syntax;

Loading…
Cancel
Save