Browse Source

replacing ExecutorBuilder with Realm (#126)

* replacing ExecutorBuilder with Realm

* fixing global_env

* adding benchmark for realm

* instrinsics was being called twice

* update changelog
pull/125/head
Jason Williams 5 years ago committed by GitHub
parent
commit
1c3fea4cf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      CHANGELOG.md
  2. 4
      Cargo.toml
  3. 12
      benches/exec.rs
  4. 5
      src/bin/shell.rs
  5. 142
      src/lib/exec.rs
  6. 7
      src/lib/js/array.rs
  7. 10
      src/lib/js/boolean.rs
  8. 4
      src/lib/js/function.rs
  9. 13
      src/lib/js/regexp.rs
  10. 16
      src/lib/js/string.rs
  11. 11
      src/lib/lib.rs
  12. 95
      src/lib/realm.rs

7
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.

4
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"

12
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);

5
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()),

142
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<F: FnOnce(&Value)>(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<Value>) -> 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);

7
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"];

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

4
src/lib/js/function.rs

@ -133,11 +133,13 @@ pub fn create_unmapped_arguments_object(arguments_list: Vec<Value>) -> 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];

13
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()"),

16
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');

11
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 {

95
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<GcCell<Box<GlobalEnvironmentRecord>>>,
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<GcCell<Box<GlobalEnvironmentRecord>>> {
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(),
})))
}
Loading…
Cancel
Save