diff --git a/CHANGELOG.md b/CHANGELOG.md index 355ca5b509..7056115e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ Features: Enables Boa to run within the Test 262 framework. This will help us see what is implemented or not within the spec +# Next + +Feature enhancements: + +- [FEATURE #119](https://github.com/jasonwilliams/boa/issues/119): + Introduce realm struct to hold realm context and global object + # 0.4.0 (2019-09-25) v0.4.0 brings quite a big release. The biggest feature to land is the support of regular expressions. diff --git a/Cargo.toml b/Cargo.toml index 4a5afd318c..eab8fbc570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,10 @@ harness = false name = "fib" harness = false +[[bench]] +name = "exec" +harness = false + [[bin]] name = "boa" path = "src/bin/bin.rs" diff --git a/benches/exec.rs b/benches/exec.rs new file mode 100644 index 0000000000..41477be6d5 --- /dev/null +++ b/benches/exec.rs @@ -0,0 +1,12 @@ +#[macro_use] +extern crate criterion; + +use boa::realm::Realm; +use criterion::Criterion; + +fn create_realm(c: &mut Criterion) { + c.bench_function("Create Realm", move |b| b.iter(|| Realm::create())); +} + +criterion_group!(benches, create_realm); +criterion_main!(benches); diff --git a/src/bin/shell.rs b/src/bin/shell.rs index f58a2d3d5e..c5610f5df8 100644 --- a/src/bin/shell.rs +++ b/src/bin/shell.rs @@ -21,6 +21,7 @@ clippy::module_name_repetitions )] +use boa::realm::Realm; use boa::{exec::Executor, forward_val}; use std::{fs::read_to_string, path::PathBuf}; use structopt::StructOpt; @@ -35,8 +36,8 @@ pub fn main() -> Result<(), std::io::Error> { let args = Opt::from_args(); let buffer = read_to_string(args.file)?; - - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); match forward_val(&mut engine, &buffer) { Ok(v) => print!("{}", v.to_string()), diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 7d45d1019f..e391bd4897 100644 --- a/src/lib/exec.rs +++ b/src/lib/exec.rs @@ -1,13 +1,11 @@ use crate::{ - environment::lexical_environment::{new_function_environment, LexicalEnvironment}, + environment::lexical_environment::new_function_environment, js::{ - array, boolean, console, function, function::{create_unmapped_arguments_object, Function, RegularFunction}, - json, math, object, object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, - regexp, string, value::{from_value, to_value, ResultValue, Value, ValueData}, }, + realm::Realm, syntax::ast::{ constant::Const, expr::{Expr, ExprDef}, @@ -23,7 +21,7 @@ use std::{ /// An execution engine pub trait Executor { /// Make a new execution engine - fn new() -> Self; + fn new(realm: Realm) -> Self; /// Run an expression fn run(&mut self, expr: &Expr) -> ResultValue; } @@ -31,18 +29,9 @@ pub trait Executor { /// A Javascript intepreter #[derive(Debug)] pub struct Interpreter { - /// An object representing the global object - environment: LexicalEnvironment, is_return: bool, -} - -/// Builder for the [`Interpreter`] -/// -/// [`Interpreter`]: struct.Interpreter.html -#[derive(Debug)] -pub struct InterpreterBuilder { - /// The global object - global: Value, + /// realm holds both the global object and the environment + realm: Realm, } fn exec_assign_op(op: &AssignOp, v_a: ValueData, v_b: ValueData) -> Value { @@ -61,8 +50,11 @@ fn exec_assign_op(op: &AssignOp, v_a: ValueData, v_b: ValueData) -> Value { } impl Executor for Interpreter { - fn new() -> Self { - InterpreterBuilder::new().build() + fn new(realm: Realm) -> Self { + Interpreter { + realm, + is_return: false, + } } #[allow(clippy::match_same_arms)] @@ -94,7 +86,7 @@ impl Executor for Interpreter { Ok(obj) } ExprDef::Local(ref name) => { - let val = self.environment.get_binding_value(name); + let val = self.realm.environment.get_binding_value(name); Ok(val) } ExprDef::GetConstField(ref obj, ref field) => { @@ -123,10 +115,7 @@ impl Executor for Interpreter { obj.borrow().get_field(&field.borrow().to_string()), ) } - _ => ( - self.environment.get_global_object().unwrap(), - self.run(&callee.clone())?, - ), // 'this' binding should come from the function's self-contained environment + _ => (self.realm.global_obj.clone(), 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() { @@ -179,7 +168,7 @@ impl Executor for Interpreter { Ok(result) } ExprDef::ObjectDecl(ref map) => { - let global_val = &self.environment.get_global_object().unwrap(); + let global_val = &self.realm.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(), self.run(val)?); @@ -187,7 +176,7 @@ impl Executor for Interpreter { Ok(obj) } ExprDef::ArrayDecl(ref arr) => { - let global_val = &self.environment.get_global_object().unwrap(); + let global_val = &self.realm.environment.get_global_object().unwrap(); let arr_map = ValueData::new_obj(Some(global_val)); // Note that this object is an Array arr_map.set_kind(ObjectKind::Array); @@ -199,7 +188,8 @@ impl Executor for Interpreter { } arr_map.borrow().set_internal_slot( INSTANCE_PROTOTYPE, - self.environment + self.realm + .environment .get_binding_value("Array") .borrow() .get_field_slice(PROTOTYPE), @@ -212,9 +202,11 @@ impl Executor for Interpreter { Function::RegularFunc(RegularFunction::new(*expr.clone(), args.clone())); let val = Gc::new(ValueData::Function(Box::new(GcCell::new(function)))); if name.is_some() { - self.environment + self.realm + .environment .create_mutable_binding(name.clone().unwrap(), false); - self.environment + self.realm + .environment .initialize_binding(name.as_ref().unwrap(), val.clone()) } Ok(val) @@ -292,10 +284,11 @@ impl Executor for Interpreter { } ExprDef::BinOp(BinOp::Assign(ref op), ref a, ref b) => match a.def { ExprDef::Local(ref name) => { - let v_a = (*self.environment.get_binding_value(&name)).clone(); + let v_a = (*self.realm.environment.get_binding_value(&name)).clone(); let v_b = (*self.run(b)?).clone(); let value = exec_assign_op(op, v_a, v_b); - self.environment + self.realm + .environment .set_mutable_binding(&name, value.clone(), true); Ok(value) } @@ -335,7 +328,7 @@ impl Executor for Interpreter { } Function::RegularFunc(ref data) => { // Create new scope - let env = &mut self.environment; + let env = &mut self.realm.environment; env.push(new_function_environment( construct.clone(), this.clone(), @@ -349,7 +342,7 @@ impl Executor for Interpreter { env.initialize_binding(name, expr.to_owned()); } let result = self.run(&data.expr); - self.environment.pop(); + self.realm.environment.pop(); result } }, @@ -370,13 +363,17 @@ impl Executor for Interpreter { let val = self.run(val_e)?; match ref_e.def { ExprDef::Local(ref name) => { - if *self.environment.get_binding_value(&name) != ValueData::Undefined { + if *self.realm.environment.get_binding_value(&name) != ValueData::Undefined + { // Binding already exists - self.environment + self.realm + .environment .set_mutable_binding(&name, val.clone(), true); } else { - self.environment.create_mutable_binding(name.clone(), true); - self.environment.initialize_binding(name, val.clone()); + self.realm + .environment + .create_mutable_binding(name.clone(), true); + self.realm.environment.initialize_binding(name, val.clone()); } } ExprDef::GetConstField(ref obj, ref field) => { @@ -394,8 +391,10 @@ impl Executor for Interpreter { Some(v) => self.run(&v)?, None => Gc::new(ValueData::Null), }; - self.environment.create_mutable_binding(name.clone(), false); - self.environment.initialize_binding(&name, val); + self.realm + .environment + .create_mutable_binding(name.clone(), false); + self.realm.environment.initialize_binding(&name, val); } Ok(Gc::new(ValueData::Undefined)) } @@ -406,17 +405,20 @@ impl Executor for Interpreter { Some(v) => self.run(&v)?, None => Gc::new(ValueData::Null), }; - self.environment.create_mutable_binding(name.clone(), false); - self.environment.initialize_binding(&name, val); + self.realm + .environment + .create_mutable_binding(name.clone(), false); + self.realm.environment.initialize_binding(&name, val); } Ok(Gc::new(ValueData::Undefined)) } ExprDef::ConstDecl(ref vars) => { for (name, value) in vars.iter() { - self.environment + self.realm + .environment .create_immutable_binding(name.clone(), false); let val = self.run(&value)?; - self.environment.initialize_binding(&name, val); + self.realm.environment.initialize_binding(&name, val); } Ok(Gc::new(ValueData::Undefined)) } @@ -435,41 +437,6 @@ impl Executor for Interpreter { } } -impl InterpreterBuilder { - pub fn new() -> Self { - let global = ValueData::new_obj(None); - object::init(&global); - console::init(&global); - math::init(&global); - function::init(&global); - json::init(&global); - global.set_field_slice("String", string::create_constructor(&global)); - global.set_field_slice("RegExp", regexp::create_constructor(&global)); - global.set_field_slice("Array", array::create_constructor(&global)); - global.set_field_slice("Boolean", boolean::create_constructor(&global)); - - Self { global } - } - - pub fn init_globals(self, init_fn: F) -> Self { - init_fn(&self.global); - self - } - - pub fn build(self) -> Interpreter { - Interpreter { - environment: LexicalEnvironment::new(self.global.clone()), - is_return: false, - } - } -} - -impl Default for InterpreterBuilder { - fn default() -> Self { - Self::new() - } -} - impl Interpreter { /// https://tc39.es/ecma262/#sec-call fn call(&mut self, f: &Value, v: &Value, arguments_list: Vec) -> ResultValue { @@ -490,7 +457,7 @@ impl Interpreter { func(v, &arguments_list, self) } Function::RegularFunc(ref data) => { - let env = &mut self.environment; + let env = &mut self.realm.environment; // New target (second argument) is only needed for constructors, just pass undefined let undefined = Gc::new(ValueData::Undefined); env.push(new_function_environment( @@ -501,19 +468,25 @@ impl Interpreter { for i in 0..data.args.len() { let name = data.args.get(i).unwrap(); let expr: &Value = arguments_list.get(i).unwrap(); - self.environment.create_mutable_binding(name.clone(), false); - self.environment.initialize_binding(name, expr.clone()); + self.realm + .environment + .create_mutable_binding(name.clone(), false); + self.realm + .environment + .initialize_binding(name, expr.clone()); } // Add arguments object let arguments_obj = create_unmapped_arguments_object(arguments_list); - self.environment + self.realm + .environment .create_mutable_binding("arguments".to_string(), false); - self.environment + self.realm + .environment .initialize_binding("arguments", arguments_obj); let result = self.run(&data.expr); - self.environment.pop(); + self.realm.environment.pop(); result } }, @@ -608,6 +581,7 @@ impl Interpreter { | ValueData::Null => Err(Gc::new(ValueData::Undefined)), ValueData::Boolean(_) => { let proto = self + .realm .environment .get_binding_value("Boolean") .get_field_slice(PROTOTYPE); @@ -618,6 +592,7 @@ impl Interpreter { } ValueData::Number(_) => { let proto = self + .realm .environment .get_binding_value("Number") .get_field_slice(PROTOTYPE); @@ -627,6 +602,7 @@ impl Interpreter { } ValueData::String(_) => { let proto = self + .realm .environment .get_binding_value("String") .get_field_slice(PROTOTYPE); diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index 86664930d0..b4db761580 100644 --- a/src/lib/js/array.rs +++ b/src/lib/js/array.rs @@ -290,11 +290,13 @@ pub fn create_constructor(global: &Value) -> Value { mod tests { use crate::exec::Executor; use crate::forward; + use crate::realm::Realm; #[test] fn concat() { //TODO: array display formatter - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" let empty = new Array(); let one = new Array(1); @@ -316,7 +318,8 @@ mod tests { #[test] fn join() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" let empty = [ ]; let one = ["a"]; diff --git a/src/lib/js/boolean.rs b/src/lib/js/boolean.rs index 196284c2a8..dee87e616c 100644 --- a/src/lib/js/boolean.rs +++ b/src/lib/js/boolean.rs @@ -91,6 +91,7 @@ pub fn this_boolean_value(value: &Value) -> Value { mod tests { use super::*; use crate::exec::Executor; + use crate::realm::Realm; use crate::{forward, forward_val, js::value::same_value}; #[test] @@ -103,7 +104,8 @@ mod tests { #[test] /// Test the correct type is returned from call and construct fn construct_and_call() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const one = new Boolean(1); const zero = Boolean(0); @@ -118,7 +120,8 @@ mod tests { #[test] fn constructor_gives_true_instance() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const trueVal = new Boolean(true); const trueNum = new Boolean(1); @@ -147,7 +150,8 @@ mod tests { #[test] fn instances_have_correct_proto_set() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const boolInstance = new Boolean(true); const boolProto = Boolean.prototype; diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index 06d219d9ad..937880d0c9 100644 --- a/src/lib/js/function.rs +++ b/src/lib/js/function.rs @@ -133,11 +133,13 @@ pub fn create_unmapped_arguments_object(arguments_list: Vec) -> Value { #[cfg(test)] mod tests { use crate::exec::Executor; + use crate::realm::Realm; use crate::{forward, forward_val, js::value::from_value}; #[test] fn check_arguments_object() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" function jason(a, b) { return arguments[0]; diff --git a/src/lib/js/regexp.rs b/src/lib/js/regexp.rs index 6b0a26d5ac..f95475cbb3 100644 --- a/src/lib/js/regexp.rs +++ b/src/lib/js/regexp.rs @@ -292,10 +292,12 @@ mod tests { use super::*; use crate::exec::Executor; use crate::forward; + use crate::realm::Realm; #[test] fn test_constructors() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" let constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); let literal = /[0-9]+(\.[0-9]+)?/; @@ -345,7 +347,8 @@ mod tests { #[test] fn test_last_index() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" let regex = /[0-9]+(\.[0-9]+)?/g; "#; @@ -360,7 +363,8 @@ mod tests { #[test] fn test_exec() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" var re = /quick\s(brown).+?(jumps)/ig; var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); @@ -379,7 +383,8 @@ mod tests { #[test] fn test_to_string() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); assert_eq!( forward(&mut engine, "(new RegExp('a+b+c')).toString()"), diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index 2ac665d6ec..010a69098f 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -713,6 +713,7 @@ pub fn init(global: &Value) { mod tests { use super::*; use crate::exec::Executor; + use crate::realm::Realm; use crate::{forward, forward_val}; #[test] @@ -749,7 +750,8 @@ mod tests { // } #[test] fn concat() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const hello = new String('Hello, '); const world = new String('world! '); @@ -766,7 +768,8 @@ mod tests { #[test] /// Test the correct type is returned from call and construct fn construct_and_call() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const hello = new String('Hello'); const world = String('world'); @@ -781,7 +784,8 @@ mod tests { #[test] fn repeat() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const empty = new String(''); const en = new String('english'); @@ -808,7 +812,8 @@ mod tests { #[test] fn starts_with() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const empty = new String(''); const en = new String('english'); @@ -831,7 +836,8 @@ mod tests { #[test] fn ends_with() { - let mut engine = Executor::new(); + let realm = Realm::create(); + let mut engine = Executor::new(realm); let init = r#" const empty = new String(''); const en = new String('english'); diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 7e29005794..02f5c96bdf 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -36,11 +36,13 @@ pub mod environment; pub mod exec; pub mod js; +pub mod realm; pub mod syntax; use crate::{ exec::{Executor, Interpreter}, js::value::ResultValue, + realm::Realm, syntax::{ast::expr::Expr, lexer::Lexer, parser::Parser}, }; use wasm_bindgen::prelude::*; @@ -76,7 +78,9 @@ pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { /// Create a clean Interpreter and execute the code pub fn exec(src: &str) -> String { - let mut engine: Interpreter = Executor::new(); + // Create new Realm + let realm = Realm::create(); + let mut engine: Interpreter = Executor::new(realm); forward(&mut engine, src) } @@ -111,8 +115,9 @@ pub fn evaluate(src: &str) -> String { return String::from("parsing failed"); } } - - let mut engine: Interpreter = Executor::new(); + // Create new Realm + let realm = Realm::create(); + let mut engine: Interpreter = Executor::new(realm); let result = engine.run(&expr); log("test2"); match result { diff --git a/src/lib/realm.rs b/src/lib/realm.rs new file mode 100644 index 0000000000..a6291b4e75 --- /dev/null +++ b/src/lib/realm.rs @@ -0,0 +1,95 @@ +//! Conceptually, a realm consists of a set of intrinsic objects, an ECMAScript global environment, +//! all of the ECMAScript code that is loaded within the scope of that global environment, +//! and other associated state and resources. +//! +//!A realm is represented in this implementation as a Realm struct with the fields specified from the spec +use crate::{ + environment::{ + declarative_environment_record::DeclarativeEnvironmentRecord, + global_environment_record::GlobalEnvironmentRecord, + lexical_environment::LexicalEnvironment, + object_environment_record::ObjectEnvironmentRecord, + }, + js::{ + array, boolean, console, function, json, math, object, regexp, string, + value::{Value, ValueData}, + }, +}; +use gc::{Gc, GcCell}; +use std::collections::{hash_map::HashMap, hash_set::HashSet}; + +/// Representation of a Realm. +/// In the specification these are called Realm Records. +#[derive(Debug)] +pub struct Realm { + pub global_obj: Value, + pub global_env: Gc>>, + pub environment: LexicalEnvironment, +} + +impl Realm { + pub fn create() -> Realm { + // Create brand new global object + // Global has no prototype to pass None to new_obj + let global = ValueData::new_obj(None); + // We need to clone the global here because its referenced from separate places (only pointer is cloned) + let global_env = new_global_environment(global.clone(), global.clone()); + + let new_realm = Realm { + global_obj: global.clone(), + global_env, + environment: LexicalEnvironment::new(global), + }; + + // Add new builtIns to Realm + // At a later date this can be removed from here and called explicity, but for now we almost always want these default builtins + new_realm.create_instrinsics(); + + new_realm + } + + // Sets up the default global objects within Global + fn create_instrinsics(&self) { + let global = &self.global_obj; + // Create intrinsics, add global objects here + object::init(global); + console::init(global); + math::init(global); + function::init(global); + json::init(global); + + global.set_field_slice("String", string::create_constructor(global)); + global.set_field_slice("RegExp", regexp::create_constructor(global)); + global.set_field_slice("Array", array::create_constructor(global)); + global.set_field_slice("Boolean", boolean::create_constructor(global)); + } +} + +// Similar to new_global_environment in lexical_environment, except we need to return a GlobalEnvirionment +fn new_global_environment( + global: Value, + this_value: Value, +) -> Gc>> { + 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(DeclarativeEnvironmentRecord { + env_rec: HashMap::new(), + outer_env: None, + }); + + Gc::new(GcCell::new(Box::new(GlobalEnvironmentRecord { + object_record: obj_rec, + global_this_binding: this_value, + declarative_record: dcl_rec, + var_names: HashSet::new(), + }))) +}