Browse Source

Implement closure functions (#1442)

pull/1448/head
Halid Odat 3 years ago committed by GitHub
parent
commit
91f0fe62bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      boa/examples/closures.rs
  2. 8
      boa/src/builtins/array/mod.rs
  3. 83
      boa/src/builtins/function/mod.rs
  4. 6
      boa/src/builtins/map/mod.rs
  5. 2
      boa/src/builtins/object/mod.rs
  6. 27
      boa/src/builtins/regexp/mod.rs
  7. 9
      boa/src/builtins/set/mod.rs
  8. 3
      boa/src/builtins/symbol/mod.rs
  9. 83
      boa/src/context.rs
  10. 19
      boa/src/object/gcobject.rs
  11. 2
      boa/src/object/internal_methods.rs
  12. 109
      boa/src/object/mod.rs
  13. 5
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  14. 6
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  15. 7
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  16. 3
      boa/src/value/operations.rs
  17. 10
      boa/src/vm/mod.rs
  18. 4
      boa_tester/src/exec/mod.rs

20
boa/examples/closures.rs

@ -0,0 +1,20 @@
use boa::{Context, JsString, Value};
fn main() -> Result<(), Value> {
let mut context = Context::new();
let variable = JsString::new("I am a captured variable");
// We register a global closure function that has the name 'closure' with length 0.
context.register_global_closure("closure", 0, move |_, _, _| {
// This value is captured from main function.
Ok(variable.clone().into())
})?;
assert_eq!(
context.eval("closure()")?,
"I am a captured variable".into()
);
Ok(())
}

8
boa/src/builtins/array/mod.rs

@ -45,16 +45,14 @@ impl BuiltIn for Array {
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = WellKnownSymbols::iterator();
let get_species = FunctionBuilder::new(context, Self::get_species) let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let values_function = FunctionBuilder::new(context, Self::values) let values_function = FunctionBuilder::native(context, Self::values)
.name("values") .name("values")
.length(0) .length(0)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();
@ -166,7 +164,7 @@ impl Array {
// i. Let intLen be ! ToUint32(len). // i. Let intLen be ! ToUint32(len).
let int_len = len.to_u32(context).unwrap(); let int_len = len.to_u32(context).unwrap();
// ii. If SameValueZero(intLen, len) is false, throw a RangeError exception. // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
if !Value::same_value_zero(&int_len.into(), &len) { if !Value::same_value_zero(&int_len.into(), len) {
return Err(context.construct_range_error("invalid array length")); return Err(context.construct_range_error("invalid array length"));
} }
int_len int_len

83
boa/src/builtins/function/mod.rs

@ -15,7 +15,7 @@ use crate::object::PROTOTYPE;
use crate::{ use crate::{
builtins::{Array, BuiltIn}, builtins::{Array, BuiltIn},
environment::lexical_environment::Environment, environment::lexical_environment::Environment,
gc::{empty_trace, Finalize, Trace}, gc::{custom_trace, empty_trace, Finalize, Trace},
object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData}, object::{ConstructorBuilder, FunctionBuilder, GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
syntax::ast::node::{FormalParameter, RcStatementList}, syntax::ast::node::{FormalParameter, RcStatementList},
@ -23,13 +23,17 @@ use crate::{
}; };
use bitflags::bitflags; use bitflags::bitflags;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::rc::Rc;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/// _fn(this, arguments, context) -> ResultValue_ - The signature of a built-in function /// _fn(this, arguments, context) -> ResultValue_ - The signature of a native built-in function
pub type NativeFunction = fn(&Value, &[Value], &mut Context) -> Result<Value>; pub type NativeFunction = fn(&Value, &[Value], &mut Context) -> Result<Value>;
/// _fn(this, arguments, context) -> ResultValue_ - The signature of a closure built-in function
pub type ClosureFunction = dyn Fn(&Value, &[Value], &mut Context) -> Result<Value>;
#[derive(Clone, Copy, Finalize)] #[derive(Clone, Copy, Finalize)]
pub struct BuiltInFunction(pub(crate) NativeFunction); pub struct BuiltInFunction(pub(crate) NativeFunction);
@ -52,31 +56,12 @@ impl Debug for BuiltInFunction {
bitflags! { bitflags! {
#[derive(Finalize, Default)] #[derive(Finalize, Default)]
pub struct FunctionFlags: u8 { pub struct FunctionFlags: u8 {
const CALLABLE = 0b0000_0001;
const CONSTRUCTABLE = 0b0000_0010; const CONSTRUCTABLE = 0b0000_0010;
const LEXICAL_THIS_MODE = 0b0000_0100; const LEXICAL_THIS_MODE = 0b0000_0100;
} }
} }
impl FunctionFlags { impl FunctionFlags {
pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self {
let mut flags = Self::default();
if callable {
flags |= Self::CALLABLE;
}
if constructable {
flags |= Self::CONSTRUCTABLE;
}
flags
}
#[inline]
pub(crate) fn is_callable(&self) -> bool {
self.contains(Self::CALLABLE)
}
#[inline] #[inline]
pub(crate) fn is_constructable(&self) -> bool { pub(crate) fn is_constructable(&self) -> bool {
self.contains(Self::CONSTRUCTABLE) self.contains(Self::CONSTRUCTABLE)
@ -97,9 +82,16 @@ unsafe impl Trace for FunctionFlags {
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) /// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
/// ///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Debug, Clone, Finalize, Trace)] #[derive(Finalize)]
pub enum Function { pub enum Function {
BuiltIn(BuiltInFunction, FunctionFlags), Native {
function: BuiltInFunction,
constructable: bool,
},
Closure {
function: Rc<ClosureFunction>,
constructable: bool,
},
Ordinary { Ordinary {
flags: FunctionFlags, flags: FunctionFlags,
body: RcStatementList, body: RcStatementList,
@ -108,6 +100,24 @@ pub enum Function {
}, },
} }
impl Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Function {{ ... }}")
}
}
unsafe impl Trace for Function {
custom_trace!(this, {
match this {
Function::Native { .. } => {}
Function::Closure { .. } => {}
Function::Ordinary { environment, .. } => {
mark(environment);
}
}
});
}
impl Function { impl Function {
// Adds the final rest parameters to the Environment as an array // Adds the final rest parameters to the Environment as an array
pub(crate) fn add_rest_param( pub(crate) fn add_rest_param(
@ -154,18 +164,11 @@ impl Function {
.expect("Failed to intialize binding"); .expect("Failed to intialize binding");
} }
/// Returns true if the function object is callable.
pub fn is_callable(&self) -> bool {
match self {
Self::BuiltIn(_, flags) => flags.is_callable(),
Self::Ordinary { flags, .. } => flags.is_callable(),
}
}
/// Returns true if the function object is constructable. /// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool { pub fn is_constructable(&self) -> bool {
match self { match self {
Self::BuiltIn(_, flags) => flags.is_constructable(), Self::Native { constructable, .. } => *constructable,
Self::Closure { constructable, .. } => *constructable,
Self::Ordinary { flags, .. } => flags.is_constructable(), Self::Ordinary { flags, .. } => flags.is_constructable(),
} }
} }
@ -230,7 +233,10 @@ pub fn make_builtin_fn<N>(
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let mut function = Object::function( let mut function = Object::function(
Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), Function::Native {
function: function.into(),
constructable: false,
},
interpreter interpreter
.standard_objects() .standard_objects()
.function_object() .function_object()
@ -270,10 +276,10 @@ impl BuiltInFunctionObject {
.expect("this should be an object") .expect("this should be an object")
.set_prototype_instance(prototype.into()); .set_prototype_instance(prototype.into());
this.set_data(ObjectData::Function(Function::BuiltIn( this.set_data(ObjectData::Function(Function::Native {
BuiltInFunction(|_, _, _| Ok(Value::undefined())), function: BuiltInFunction(|_, _, _| Ok(Value::undefined())),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, constructable: true,
))); }));
Ok(this) Ok(this)
} }
@ -342,10 +348,9 @@ impl BuiltIn for BuiltInFunctionObject {
let _timer = BoaProfiler::global().start_event("function", "init"); let _timer = BoaProfiler::global().start_event("function", "init");
let function_prototype = context.standard_objects().function_object().prototype(); let function_prototype = context.standard_objects().function_object().prototype();
FunctionBuilder::new(context, Self::prototype) FunctionBuilder::native(context, Self::prototype)
.name("") .name("")
.length(0) .length(0)
.callable(true)
.constructable(false) .constructable(false)
.build_function_prototype(&function_prototype); .build_function_prototype(&function_prototype);

6
boa/src/builtins/map/mod.rs

@ -46,16 +46,14 @@ impl BuiltIn for Map {
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let iterator_symbol = WellKnownSymbols::iterator(); let iterator_symbol = WellKnownSymbols::iterator();
let get_species = FunctionBuilder::new(context, Self::get_species) let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let entries_function = FunctionBuilder::new(context, Self::entries) let entries_function = FunctionBuilder::native(context, Self::entries)
.name("entries") .name("entries")
.length(0) .length(0)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();

2
boa/src/builtins/object/mod.rs

@ -360,7 +360,7 @@ impl Object {
/// Define a property in an object /// Define a property in an object
pub fn define_property(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> { pub fn define_property(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let object = args.get(0).cloned().unwrap_or_else(Value::undefined); let object = args.get(0).cloned().unwrap_or_else(Value::undefined);
if let Some(mut object) = object.as_object() { if let Some(object) = object.as_object() {
let key = args let key = args
.get(1) .get(1)
.unwrap_or(&Value::undefined()) .unwrap_or(&Value::undefined())

27
boa/src/builtins/regexp/mod.rs

@ -75,53 +75,44 @@ impl BuiltIn for RegExp {
fn init(context: &mut Context) -> (&'static str, Value, Attribute) { fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::new(context, Self::get_species) let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_global = FunctionBuilder::new(context, Self::get_global) let get_global = FunctionBuilder::native(context, Self::get_global)
.name("get global") .name("get global")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_ignore_case = FunctionBuilder::new(context, Self::get_ignore_case) let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case)
.name("get ignoreCase") .name("get ignoreCase")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_multiline = FunctionBuilder::new(context, Self::get_multiline) let get_multiline = FunctionBuilder::native(context, Self::get_multiline)
.name("get multiline") .name("get multiline")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_dot_all = FunctionBuilder::new(context, Self::get_dot_all) let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all)
.name("get dotAll") .name("get dotAll")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_unicode = FunctionBuilder::new(context, Self::get_unicode) let get_unicode = FunctionBuilder::native(context, Self::get_unicode)
.name("get unicode") .name("get unicode")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_sticky = FunctionBuilder::new(context, Self::get_sticky) let get_sticky = FunctionBuilder::native(context, Self::get_sticky)
.name("get sticky") .name("get sticky")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_flags = FunctionBuilder::new(context, Self::get_flags) let get_flags = FunctionBuilder::native(context, Self::get_flags)
.name("get flags") .name("get flags")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let get_source = FunctionBuilder::new(context, Self::get_source) let get_source = FunctionBuilder::native(context, Self::get_source)
.name("get source") .name("get source")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let regexp_object = ConstructorBuilder::with_standard_object( let regexp_object = ConstructorBuilder::with_standard_object(
context, context,

9
boa/src/builtins/set/mod.rs

@ -39,14 +39,12 @@ impl BuiltIn for Set {
fn init(context: &mut Context) -> (&'static str, Value, Attribute) { fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::new(context, Self::get_species) let get_species = FunctionBuilder::native(context, Self::get_species)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let size_getter = FunctionBuilder::new(context, Self::size_getter) let size_getter = FunctionBuilder::native(context, Self::size_getter)
.callable(true)
.constructable(false) .constructable(false)
.name("get size") .name("get size")
.build(); .build();
@ -55,10 +53,9 @@ impl BuiltIn for Set {
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let values_function = FunctionBuilder::new(context, Self::values) let values_function = FunctionBuilder::native(context, Self::values)
.name("values") .name("values")
.length(0) .length(0)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();

3
boa/src/builtins/symbol/mod.rs

@ -97,10 +97,9 @@ impl BuiltIn for Symbol {
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let get_description = FunctionBuilder::new(context, Self::get_description) let get_description = FunctionBuilder::native(context, Self::get_description)
.name("get description") .name("get description")
.constructable(false) .constructable(false)
.callable(true)
.build(); .build();
let symbol_object = ConstructorBuilder::with_standard_object( let symbol_object = ConstructorBuilder::with_standard_object(

83
boa/src/context.rs

@ -21,7 +21,7 @@ use crate::{
}, },
Parser, Parser,
}, },
BoaProfiler, Executable, Result, Value, BoaProfiler, Executable, JsString, Result, Value,
}; };
#[cfg(feature = "console")] #[cfg(feature = "console")]
@ -486,21 +486,24 @@ impl Context {
} }
/// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function<P, B>( pub(crate) fn create_function<N, P, B>(
&mut self, &mut self,
name: N,
params: P, params: P,
body: B, body: B,
flags: FunctionFlags, flags: FunctionFlags,
) -> Result<Value> ) -> Result<Value>
where where
N: Into<JsString>,
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>, B: Into<StatementList>,
{ {
let name = name.into();
let function_prototype: Value = let function_prototype: Value =
self.standard_objects().function_object().prototype().into(); self.standard_objects().function_object().prototype().into();
// Every new function has a prototype property pre-made // Every new function has a prototype property pre-made
let proto = Value::new_object(self); let prototype = self.construct_object();
let params = params.into(); let params = params.into();
let params_len = params.len(); let params_len = params.len();
@ -511,30 +514,48 @@ impl Context {
environment: self.get_current_environment().clone(), environment: self.get_current_environment().clone(),
}; };
let new_func = Object::function(func, function_prototype); let function = GcObject::new(Object::function(func, function_prototype));
let val = Value::from(new_func);
// Set constructor field to the newly created Value (function object) // Set constructor field to the newly created Value (function object)
proto.set_field("constructor", val.clone(), false, self)?; let constructor = DataDescriptor::new(
function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
prototype.define_property_or_throw("constructor", constructor, self)?;
val.set_field(PROTOTYPE, proto, false, self)?; let prototype = DataDescriptor::new(
val.set_field("length", Value::from(params_len), false, self)?; prototype,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
function.define_property_or_throw(PROTOTYPE, prototype, self)?;
let length = DataDescriptor::new(
params_len,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
function.define_property_or_throw("length", length, self)?;
let name = DataDescriptor::new(
name,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
function.define_property_or_throw("name", name, self)?;
Ok(val) Ok(function.into())
} }
/// Register a global function. /// Register a global native function.
///
/// This is more efficient that creating a closure function, since this does not allocate,
/// it is just a function pointer.
/// ///
/// The function will be both `callable` and `constructable` (call with `new`). /// The function will be both `constructable` (call with `new`).
/// ///
/// The function will be bound to the global object with `writable`, `non-enumerable` /// The function will be bound to the global object with `writable`, `non-enumerable`
/// and `configurable` attributes. The same as when you create a function in JavaScript. /// and `configurable` attributes. The same as when you create a function in JavaScript.
/// ///
/// # Note /// # Note
/// ///
/// If you want to make a function only `callable` or `constructable`, or wish to bind it differently /// If you want to make a function only `constructable`, or wish to bind it differently
/// to the global object, you can create the function object with [`FunctionBuilder`](crate::object::FunctionBuilder). /// to the global object, you can create the function object with [`FunctionBuilder`](crate::object::FunctionBuilder::native).
/// And bind it to the global object with [`Context::register_global_property`](Context::register_global_property) method. /// And bind it to the global object with [`Context::register_global_property`](Context::register_global_property) method.
#[inline] #[inline]
pub fn register_global_function( pub fn register_global_function(
@ -543,10 +564,40 @@ impl Context {
length: usize, length: usize,
body: NativeFunction, body: NativeFunction,
) -> Result<()> { ) -> Result<()> {
let function = FunctionBuilder::new(self, body) let function = FunctionBuilder::native(self, body)
.name(name)
.length(length)
.constructable(true)
.build();
self.global_object().insert_property(
name,
function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
Ok(())
}
/// Register a global closure function.
///
/// The function will be both `constructable` (call with `new`).
///
/// The function will be bound to the global object with `writable`, `non-enumerable`
/// and `configurable` attributes. The same as when you create a function in JavaScript.
///
/// # Note
///
/// If you want to make a function only `constructable`, or wish to bind it differently
/// to the global object, you can create the function object with [`FunctionBuilder`](crate::object::FunctionBuilder::closure).
/// And bind it to the global object with [`Context::register_global_property`](Context::register_global_property) method.
#[inline]
pub fn register_global_closure<F>(&mut self, name: &str, length: usize, body: F) -> Result<()>
where
F: Fn(&Value, &[Value], &mut Context) -> Result<Value> + 'static,
{
let function = FunctionBuilder::closure(self, body)
.name(name) .name(name)
.length(length) .length(length)
.callable(true)
.constructable(true) .constructable(true)
.build(); .build();

19
boa/src/object/gcobject.rs

@ -5,7 +5,7 @@
use super::{NativeObject, Object, PROTOTYPE}; use super::{NativeObject, Object, PROTOTYPE};
use crate::{ use crate::{
builtins::function::{ builtins::function::{
create_unmapped_arguments_object, BuiltInFunction, Function, NativeFunction, create_unmapped_arguments_object, ClosureFunction, Function, NativeFunction,
}, },
context::StandardConstructor, context::StandardConstructor,
environment::{ environment::{
@ -26,6 +26,7 @@ use std::{
collections::HashMap, collections::HashMap,
error::Error, error::Error,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
rc::Rc,
result::Result as StdResult, result::Result as StdResult,
}; };
@ -46,6 +47,7 @@ pub struct GcObject(Gc<GcCell<Object>>);
enum FunctionBody { enum FunctionBody {
BuiltInFunction(NativeFunction), BuiltInFunction(NativeFunction),
BuiltInConstructor(NativeFunction), BuiltInConstructor(NativeFunction),
Closure(Rc<ClosureFunction>),
Ordinary(RcStatementList), Ordinary(RcStatementList),
} }
@ -139,17 +141,19 @@ impl GcObject {
.display() .display()
.to_string(); .to_string();
return context.throw_type_error(format!("{} is not a constructor", name)); return context.throw_type_error(format!("{} is not a constructor", name));
} else if !construct && !function.is_callable() {
return context.throw_type_error("function object is not callable");
} else { } else {
match function { match function {
Function::BuiltIn(BuiltInFunction(function), flags) => { Function::Native {
if flags.is_constructable() || construct { function,
FunctionBody::BuiltInConstructor(*function) constructable,
} => {
if *constructable || construct {
FunctionBody::BuiltInConstructor(function.0)
} else { } else {
FunctionBody::BuiltInFunction(*function) FunctionBody::BuiltInFunction(function.0)
} }
} }
Function::Closure { function, .. } => FunctionBody::Closure(function.clone()),
Function::Ordinary { Function::Ordinary {
body, body,
params, params,
@ -298,6 +302,7 @@ impl GcObject {
function(&Value::undefined(), args, context) function(&Value::undefined(), args, context)
} }
FunctionBody::BuiltInFunction(function) => function(this_target, args, context), FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Closure(function) => (function)(this_target, args, context),
FunctionBody::Ordinary(body) => { FunctionBody::Ordinary(body) => {
let result = body.run(context); let result = body.run(context);
let this = context.get_this_binding(); let this = context.get_this_binding();

2
boa/src/object/internal_methods.rs

@ -152,7 +152,7 @@ impl GcObject {
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
#[inline] #[inline]
pub fn define_property_or_throw<K, P>( pub fn define_property_or_throw<K, P>(
&mut self, &self,
key: K, key: K,
desc: P, desc: P,
context: &mut Context, context: &mut Context,

109
boa/src/object/mod.rs

@ -3,7 +3,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
array::array_iterator::ArrayIterator, array::array_iterator::ArrayIterator,
function::{BuiltInFunction, Function, FunctionFlags, NativeFunction}, function::{Function, NativeFunction},
map::map_iterator::MapIterator, map::map_iterator::MapIterator,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
regexp::regexp_string_iterator::RegExpStringIterator, regexp::regexp_string_iterator::RegExpStringIterator,
@ -22,6 +22,7 @@ use std::{
any::Any, any::Any,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
rc::Rc,
}; };
#[cfg(test)] #[cfg(test)]
@ -264,7 +265,7 @@ impl Object {
/// [spec]: https://tc39.es/ecma262/#sec-iscallable /// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline] #[inline]
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_callable()) matches!(self.data, ObjectData::Function(_))
} }
/// It determines if Object is a function object with a `[[Construct]]` internal method. /// It determines if Object is a function object with a `[[Construct]]` internal method.
@ -682,24 +683,40 @@ where
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionBuilder<'context> { pub struct FunctionBuilder<'context> {
context: &'context mut Context, context: &'context mut Context,
function: BuiltInFunction, function: Option<Function>,
name: Option<String>, name: JsString,
length: usize, length: usize,
callable: bool,
constructable: bool,
} }
impl<'context> FunctionBuilder<'context> { impl<'context> FunctionBuilder<'context> {
/// Create a new `FunctionBuilder` /// Create a new `FunctionBuilder` for creating a native function.
#[inline] #[inline]
pub fn new(context: &'context mut Context, function: NativeFunction) -> Self { pub fn native(context: &'context mut Context, function: NativeFunction) -> Self {
Self { Self {
context, context,
function: function.into(), function: Some(Function::Native {
name: None, function: function.into(),
constructable: false,
}),
name: JsString::default(),
length: 0,
}
}
/// Create a new `FunctionBuilder` for creating a closure function.
#[inline]
pub fn closure<F>(context: &'context mut Context, function: F) -> Self
where
F: Fn(&Value, &[Value], &mut Context) -> Result<Value, Value> + 'static,
{
Self {
context,
function: Some(Function::Closure {
function: Rc::new(function),
constructable: false,
}),
name: JsString::default(),
length: 0, length: 0,
callable: true,
constructable: false,
} }
} }
@ -711,7 +728,7 @@ impl<'context> FunctionBuilder<'context> {
where where
N: AsRef<str>, N: AsRef<str>,
{ {
self.name = Some(name.as_ref().into()); self.name = name.as_ref().into();
self self
} }
@ -726,21 +743,16 @@ impl<'context> FunctionBuilder<'context> {
self self
} }
/// Specify the whether the object function object can be called.
///
/// The default is `true`.
#[inline]
pub fn callable(&mut self, yes: bool) -> &mut Self {
self.callable = yes;
self
}
/// Specify the whether the object function object can be called with `new` keyword. /// Specify the whether the object function object can be called with `new` keyword.
/// ///
/// The default is `false`. /// The default is `false`.
#[inline] #[inline]
pub fn constructable(&mut self, yes: bool) -> &mut Self { pub fn constructable(&mut self, yes: bool) -> &mut Self {
self.constructable = yes; match self.function.as_mut() {
Some(Function::Native { constructable, .. }) => *constructable = yes,
Some(Function::Closure { constructable, .. }) => *constructable = yes,
_ => unreachable!(),
}
self self
} }
@ -748,10 +760,7 @@ impl<'context> FunctionBuilder<'context> {
#[inline] #[inline]
pub fn build(&mut self) -> GcObject { pub fn build(&mut self) -> GcObject {
let mut function = Object::function( let mut function = Object::function(
Function::BuiltIn( self.function.take().unwrap(),
self.function,
FunctionFlags::from_parameters(self.callable, self.constructable),
),
self.context self.context
.standard_objects() .standard_objects()
.function_object() .function_object()
@ -759,11 +768,7 @@ impl<'context> FunctionBuilder<'context> {
.into(), .into(),
); );
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
if let Some(name) = self.name.take() { function.insert_property("name", self.name.clone(), attribute);
function.insert_property("name", name, attribute);
} else {
function.insert_property("name", "", attribute);
}
function.insert_property("length", self.length, attribute); function.insert_property("length", self.length, attribute);
GcObject::new(function) GcObject::new(function)
@ -772,10 +777,7 @@ impl<'context> FunctionBuilder<'context> {
/// Initializes the `Function.prototype` function object. /// Initializes the `Function.prototype` function object.
pub(crate) fn build_function_prototype(&mut self, object: &GcObject) { pub(crate) fn build_function_prototype(&mut self, object: &GcObject) {
let mut object = object.borrow_mut(); let mut object = object.borrow_mut();
object.data = ObjectData::Function(Function::BuiltIn( object.data = ObjectData::Function(self.function.take().unwrap());
self.function,
FunctionFlags::from_parameters(self.callable, self.constructable),
));
object.set_prototype_instance( object.set_prototype_instance(
self.context self.context
.standard_objects() .standard_objects()
@ -783,12 +785,8 @@ impl<'context> FunctionBuilder<'context> {
.prototype() .prototype()
.into(), .into(),
); );
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
if let Some(name) = self.name.take() { object.insert_property("name", self.name.clone(), attribute);
object.insert_property("name", name, attribute);
} else {
object.insert_property("name", "", attribute);
}
object.insert_property("length", self.length, attribute); object.insert_property("length", self.length, attribute);
} }
} }
@ -836,10 +834,9 @@ impl<'context> ObjectInitializer<'context> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::new(self.context, function) let function = FunctionBuilder::native(self.context, function)
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();
@ -876,7 +873,7 @@ pub struct ConstructorBuilder<'context> {
constructor_function: NativeFunction, constructor_function: NativeFunction,
constructor_object: GcObject, constructor_object: GcObject,
prototype: GcObject, prototype: GcObject,
name: Option<String>, name: JsString,
length: usize, length: usize,
callable: bool, callable: bool,
constructable: bool, constructable: bool,
@ -907,7 +904,7 @@ impl<'context> ConstructorBuilder<'context> {
constructor_object: GcObject::new(Object::default()), constructor_object: GcObject::new(Object::default()),
prototype: GcObject::new(Object::default()), prototype: GcObject::new(Object::default()),
length: 0, length: 0,
name: None, name: JsString::default(),
callable: true, callable: true,
constructable: true, constructable: true,
inherit: None, inherit: None,
@ -926,7 +923,7 @@ impl<'context> ConstructorBuilder<'context> {
constructor_object: object.constructor, constructor_object: object.constructor,
prototype: object.prototype, prototype: object.prototype,
length: 0, length: 0,
name: None, name: JsString::default(),
callable: true, callable: true,
constructable: true, constructable: true,
inherit: None, inherit: None,
@ -940,10 +937,9 @@ impl<'context> ConstructorBuilder<'context> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::new(self.context, function) let function = FunctionBuilder::native(self.context, function)
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();
@ -967,10 +963,9 @@ impl<'context> ConstructorBuilder<'context> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::new(self.context, function) let function = FunctionBuilder::native(self.context, function)
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.callable(true)
.constructable(false) .constructable(false)
.build(); .build();
@ -1081,7 +1076,7 @@ impl<'context> ConstructorBuilder<'context> {
where where
N: AsRef<str>, N: AsRef<str>,
{ {
self.name = Some(name.as_ref().into()); self.name = name.as_ref().into();
self self
} }
@ -1122,17 +1117,17 @@ impl<'context> ConstructorBuilder<'context> {
/// Build the constructor function object. /// Build the constructor function object.
pub fn build(&mut self) -> GcObject { pub fn build(&mut self) -> GcObject {
// Create the native function // Create the native function
let function = Function::BuiltIn( let function = Function::Native {
self.constructor_function.into(), function: self.constructor_function.into(),
FunctionFlags::from_parameters(self.callable, self.constructable), constructable: self.constructable,
); };
let length = DataDescriptor::new( let length = DataDescriptor::new(
self.length, self.length,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );
let name = DataDescriptor::new( let name = DataDescriptor::new(
self.name.take().unwrap_or_else(|| String::from("[object]")), self.name.clone(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );

5
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -74,11 +74,10 @@ impl ArrowFunctionDecl {
impl Executable for ArrowFunctionDecl { impl Executable for ArrowFunctionDecl {
fn run(&self, context: &mut Context) -> Result<Value> { fn run(&self, context: &mut Context) -> Result<Value> {
context.create_function( context.create_function(
"",
self.params().to_vec(), self.params().to_vec(),
self.body().to_vec(), self.body().to_vec(),
FunctionFlags::CALLABLE FunctionFlags::CONSTRUCTABLE | FunctionFlags::LEXICAL_THIS_MODE,
| FunctionFlags::CONSTRUCTABLE
| FunctionFlags::LEXICAL_THIS_MODE,
) )
} }
} }

6
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -89,14 +89,12 @@ impl Executable for FunctionDecl {
fn run(&self, context: &mut Context) -> Result<Value> { fn run(&self, context: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec"); let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec");
let val = context.create_function( let val = context.create_function(
self.name(),
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, FunctionFlags::CONSTRUCTABLE,
)?; )?;
// Set the name and assign it in the current environment
val.set_field("name", self.name(), false, context)?;
if context.has_binding(self.name()) { if context.has_binding(self.name()) {
context.set_mutable_binding(self.name(), val, true)?; context.set_mutable_binding(self.name(), val, true)?;
} else { } else {

7
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -100,15 +100,12 @@ impl FunctionExpr {
impl Executable for FunctionExpr { impl Executable for FunctionExpr {
fn run(&self, context: &mut Context) -> Result<Value> { fn run(&self, context: &mut Context) -> Result<Value> {
let val = context.create_function( let val = context.create_function(
self.name().unwrap_or(""),
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, FunctionFlags::CONSTRUCTABLE,
)?; )?;
if let Some(name) = self.name() {
val.set_field("name", Value::from(name), false, context)?;
}
Ok(val) Ok(val)
} }
} }

3
boa/src/value/operations.rs

@ -11,9 +11,6 @@ impl Value {
(Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y), (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)), (Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)),
(Self::String(ref x), Self::String(ref y)) => Self::string(format!("{}{}", x, y)),
(Self::String(ref x), y) => Self::string(format!("{}{}", x, y.to_string(context)?)),
(x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(context)?, y)),
(Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), (Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)),
(Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)), (Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)),
(x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)), (x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)),

10
boa/src/vm/mod.rs

@ -289,13 +289,13 @@ impl<'a> Vm<'a> {
let value = self.pop(); let value = self.pop();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
self.context.initialize_binding(&name, value)?; self.context.initialize_binding(name, value)?;
} }
Opcode::GetName => { Opcode::GetName => {
let index = self.read::<u32>(); let index = self.read::<u32>();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
let value = self.context.get_binding_value(&name)?; let value = self.context.get_binding_value(name)?;
self.push(value); self.push(value);
} }
Opcode::SetName => { Opcode::SetName => {
@ -303,16 +303,16 @@ impl<'a> Vm<'a> {
let value = self.pop(); let value = self.pop();
let name = &self.code.names[index as usize]; let name = &self.code.names[index as usize];
if self.context.has_binding(&name) { if self.context.has_binding(name) {
// Binding already exists // Binding already exists
self.context.set_mutable_binding(&name, value, true)?; self.context.set_mutable_binding(name, value, true)?;
} else { } else {
self.context.create_mutable_binding( self.context.create_mutable_binding(
name.to_string(), name.to_string(),
true, true,
VariableScope::Function, VariableScope::Function,
)?; )?;
self.context.initialize_binding(&name, value)?; self.context.initialize_binding(name, value)?;
} }
} }
Opcode::Jump => { Opcode::Jump => {

4
boa_tester/src/exec/mod.rs

@ -137,7 +137,7 @@ impl Test {
Outcome::Positive => { Outcome::Positive => {
// TODO: implement async and add `harness/doneprintHandle.js` to the includes. // TODO: implement async and add `harness/doneprintHandle.js` to the includes.
match self.set_up_env(&harness, strict) { match self.set_up_env(harness, strict) {
Ok(mut context) => { Ok(mut context) => {
let res = context.eval(&self.content.as_ref()); let res = context.eval(&self.content.as_ref());
@ -183,7 +183,7 @@ impl Test {
if let Err(e) = parse(&self.content.as_ref(), strict) { if let Err(e) = parse(&self.content.as_ref(), strict) {
(false, format!("Uncaught {}", e)) (false, format!("Uncaught {}", e))
} else { } else {
match self.set_up_env(&harness, strict) { match self.set_up_env(harness, strict) {
Ok(mut context) => match context.eval(&self.content.as_ref()) { Ok(mut context) => match context.eval(&self.content.as_ref()) {
Ok(res) => (false, format!("{}", res.display())), Ok(res) => (false, format!("{}", res.display())),
Err(e) => { Err(e) => {

Loading…
Cancel
Save