From e111f7b92024d982e1ad906d1605d00f2e68a8e6 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 13 Aug 2020 15:32:13 +0200 Subject: [PATCH 1/7] Added NativeObject and native Classes - Removed internal state - Added native class example --- boa/examples/classes.rs | 84 +++++++ boa/src/builtins/function/mod.rs | 2 +- boa/src/builtins/json/mod.rs | 15 +- boa/src/builtins/object/internal_methods.rs | 78 +++---- boa/src/builtins/object/mod.rs | 243 ++++++++++++++++++-- boa/src/exec/mod.rs | 16 +- boa/src/lib.rs | 2 + 7 files changed, 367 insertions(+), 73 deletions(-) create mode 100644 boa/examples/classes.rs diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs new file mode 100644 index 00000000000..e4a533ea5ea --- /dev/null +++ b/boa/examples/classes.rs @@ -0,0 +1,84 @@ +use boa::builtins::object::{Class, ClassBuilder}; +use boa::builtins::value::*; +use boa::exec::*; +use boa::realm::Realm; +use boa::*; + +use gc::{Finalize, Trace}; + +#[derive(Debug, Trace, Finalize)] +struct Person { + name: String, + age: u32, +} + +impl Person { + fn say_hello(this: &Value, _: &[Value], ctx: &mut Interpreter) -> Result { + if let Some(object) = this.as_object() { + if let Some(person) = object.downcast_ref::() { + println!( + "Hello my name is {}, I'm {} years old", + person.name, person.age + ); + return Ok(Value::undefined()); + } + } + ctx.throw_type_error("'this' is not a Person object") + } +} + +impl Class for Person { + const NAME: &'static str = "Person"; + const LENGTH: usize = 2; + + fn constructor(_this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { + let name = args.get(0).cloned().unwrap_or_default().to_string(ctx)?; + let age = args.get(1).cloned().unwrap_or_default().to_u32(ctx)?; + + let person = Person { + name: name.to_string(), + age, + }; + + Ok(person) + } + + fn methods(class: &mut ClassBuilder) -> Result<()> { + class.method("sayHello", 0, Self::say_hello); + class.static_method("is", 1, |_this, args, _ctx| { + if let Some(arg) = args.get(0) { + if let Some(object) = arg.as_object() { + if object.is::() { + return Ok(true.into()); + } + } + } + Ok(false.into()) + }); + + Ok(()) + } +} + +fn main() { + let realm = Realm::create(); + let mut context = Interpreter::new(realm); + + context.register_global_class::().unwrap(); + + forward_val( + &mut context, + r" + let person = new Person('John', 19); + person.sayHello(); + + if (Person.is(person)) { + console.log('person is a Person class instance.'); + } + if (!Person.is('Hello')) { + console.log('\'Hello\' string is not a Person class instance.'); + } + ", + ) + .unwrap(); +} diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index f6e6eb56652..25730235245 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -59,7 +59,7 @@ bitflags! { } impl FunctionFlags { - fn from_parameters(callable: bool, constructable: bool) -> Self { + pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self { let mut flags = Self::default(); if callable { diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 71f86e50107..d674d18d5b5 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -81,18 +81,19 @@ impl Json { holder: &mut Value, key: &PropertyKey, ) -> Result { - let mut value = holder.get_field(key.clone()); + let value = holder.get_field(key.clone()); - let obj = value.as_object().as_deref().cloned(); - if let Some(obj) = obj { - for key in obj.keys() { - let v = Self::walk(reviver, ctx, &mut value, &key); + if let Value::Object(ref object) = value { + let keys: Vec<_> = object.borrow().keys().collect(); + + for key in keys { + let v = Self::walk(reviver, ctx, &mut value.clone(), &key); match v { Ok(v) if !v.is_undefined() => { - value.set_field(key.clone(), v); + value.set_field(key, v); } Ok(_) => { - value.remove_property(key.clone()); + value.remove_property(key); } Err(_v) => {} } diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index 0595a294abf..8bda40f1ea5 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -282,45 +282,45 @@ impl Object { }) } - /// `Object.setPropertyOf(obj, prototype)` - /// - /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) - /// of a specified object to another object or `null`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf - pub fn set_prototype_of(&mut self, val: Value) -> bool { - debug_assert!(val.is_object() || val.is_null()); - let current = self.prototype.clone(); - if same_value(¤t, &val) { - return true; - } - if !self.is_extensible() { - return false; - } - let mut p = val.clone(); - let mut done = false; - while !done { - if p.is_null() { - done = true - } else if same_value(&Value::from(self.clone()), &p) { - return false; - } else { - let prototype = p - .as_object() - .expect("prototype should be null or object") - .prototype - .clone(); - p = prototype; - } - } - self.prototype = val; - true - } + // /// `Object.setPropertyOf(obj, prototype)` + // /// + // /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) + // /// of a specified object to another object or `null`. + // /// + // /// More information: + // /// - [ECMAScript reference][spec] + // /// - [MDN documentation][mdn] + // /// + // /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf + // pub fn set_prototype_of(&mut self, val: Value) -> bool { + // debug_assert!(val.is_object() || val.is_null()); + // let current = self.prototype.clone(); + // if same_value(¤t, &val) { + // return true; + // } + // if !self.is_extensible() { + // return false; + // } + // let mut p = val.clone(); + // let mut done = false; + // while !done { + // if p.is_null() { + // done = true + // } else if same_value(&Value::from(self.clone()), &p) { + // return false; + // } else { + // let prototype = p + // .as_object() + // .expect("prototype should be null or object") + // .prototype + // .clone(); + // p = prototype; + // } + // } + // self.prototype = val; + // true + // } /// Returns either the prototype or null /// diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index ff45ff4c9e8..d081ebbbacc 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::{ function::Function, map::ordered_map::OrderedMap, - property::{Property, PropertyKey}, + property::{Attribute, Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BigInt, Date, RegExp, }, @@ -26,10 +26,13 @@ use crate::{ }; use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; +use std::any::Any; use std::fmt::{Debug, Display, Error, Formatter}; use std::result::Result as StdResult; -use super::function::{make_builtin_fn, make_constructor_fn}; +use super::function::{ + make_builtin_fn, make_constructor_fn, BuiltInFunction, FunctionFlags, NativeFunction, +}; use crate::builtins::value::same_value; mod gcobject; @@ -45,8 +48,166 @@ mod tests; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. pub static PROTOTYPE: &str = "prototype"; +pub trait NativeObject: Debug + Any + Trace { + fn as_any(&self) -> &dyn Any; + fn as_mut_any(&mut self) -> &mut dyn Any; +} + +impl NativeObject for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } +} + +pub trait Class: NativeObject { + /// The binding name of the object. + const NAME: &'static str; + /// The amount of arguments the class `constructor` takes. + const LENGTH: usize = 0; + + /// This is a wrapper around `Self::constructor` that sets the internal data of the class. + fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result + where + Self: Sized, + { + let object_instance = Self::constructor(this, args, ctx)?; + this.set_data(ObjectData::NativeObject(Box::new(object_instance))); + Ok(this.clone()) + } + + /// The constructor of the class. + fn constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result + where + Self: Sized; + + /// Initializes the internals and the methods of the class. + fn methods(class: &mut ClassBuilder<'_>) -> Result<()>; +} + +/// Class builder which allows adding methods and static methods to the class. +#[derive(Debug)] +pub struct ClassBuilder<'context> { + context: &'context mut Interpreter, + object: GcObject, + prototype: GcObject, +} + +impl<'context> ClassBuilder<'context> { + pub(crate) fn new(context: &'context mut Interpreter) -> Self + where + T: Class, + { + let global = context.global(); + + let prototype = { + let object_prototype = global.get_field("Object").get_field(PROTOTYPE); + + let object = Object::create(object_prototype); + GcObject::new(object) + }; + // Create the native function + let function = Function::BuiltIn( + BuiltInFunction(T::raw_constructor), + FunctionFlags::CONSTRUCTABLE, + ); + + // Get reference to Function.prototype + // Create the function object and point its instance prototype to Function.prototype + let mut constructor = + Object::function(function, global.get_field("Function").get_field(PROTOTYPE)); + + let length = Property::data_descriptor( + T::LENGTH.into(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + constructor.insert_property("length", length); + + let name = Property::data_descriptor( + T::NAME.into(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + constructor.insert_property("name", name); + + let constructor = GcObject::new(constructor); + + prototype + .borrow_mut() + .insert_field("constructor", constructor.clone().into()); + + constructor + .borrow_mut() + .insert_field(PROTOTYPE, prototype.clone().into()); + + Self { + context, + object: constructor, + prototype, + } + } + + pub(crate) fn build(self) -> GcObject { + self.object + } + + /// Add a method to the class. + /// + /// It is added to `prototype`. + pub fn method(&mut self, name: N, length: usize, function: NativeFunction) + where + N: Into, + { + let name = name.into(); + let mut function = Object::function( + Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), + self.context + .global() + .get_field("Function") + .get_field("prototype"), + ); + + function.insert_field("length", Value::from(length)); + function.insert_field("name", Value::from(name.as_str())); + + self.prototype + .borrow_mut() + .insert_field(name, Value::from(function)); + } + + /// Add a static method to the class. + /// + /// It is added to class object itself. + pub fn static_method(&mut self, name: N, length: usize, function: NativeFunction) + where + N: Into, + { + let name = name.into(); + let mut function = Object::function( + Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), + self.context + .global() + .get_field("Function") + .get_field("prototype"), + ); + + function.insert_field("length", Value::from(length)); + function.insert_field("name", Value::from(name.as_str())); + + self.object + .borrow_mut() + .insert_field(name, Value::from(function)); + } + + pub fn context(&mut self) -> &'_ mut Interpreter { + self.context + } +} + /// The internal representation of an JavaScript object. -#[derive(Debug, Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize)] pub struct Object { /// The type of the object. pub data: ObjectData, @@ -62,7 +223,7 @@ pub struct Object { } /// Defines the different types of objects. -#[derive(Debug, Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, Map(OrderedMap), @@ -77,6 +238,7 @@ pub enum ObjectData { Ordinary, Date(Date), Global, + NativeObject(Box), } impl Display for ObjectData { @@ -98,6 +260,7 @@ impl Display for ObjectData { Self::BigInt(_) => "BigInt", Self::Date(_) => "Date", Self::Global => "Global", + Self::NativeObject(_) => "NativeObject", } ) } @@ -202,21 +365,17 @@ impl Object { } } - /// Converts the `Value` to an `Object` type. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-toobject - pub fn from(value: &Value) -> StdResult { - match *value { - Value::Boolean(a) => Ok(Self::boolean(a)), - Value::Rational(a) => Ok(Self::number(a)), - Value::Integer(a) => Ok(Self::number(f64::from(a))), - Value::String(ref a) => Ok(Self::string(a.clone())), - Value::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())), - Value::Object(ref obj) => Ok(obj.borrow().clone()), - _ => Err(()), + pub fn native_object(value: T) -> Self + where + T: NativeObject, + { + Self { + data: ObjectData::NativeObject(Box::new(value)), + indexed_properties: FxHashMap::default(), + string_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), + prototype: Value::null(), + extensible: true, } } @@ -404,20 +563,56 @@ impl Object { assert!(prototype.is_null() || prototype.is_object()); self.prototype = prototype } + + pub fn is_native_object(&self) -> bool { + matches!(self.data, ObjectData::NativeObject(_)) + } + + pub fn is(&self) -> bool + where + T: NativeObject, + { + use std::ops::Deref; + match self.data { + ObjectData::NativeObject(ref object) => object.deref().as_any().is::(), + _ => false, + } + } + + pub fn downcast_ref(&self) -> Option<&T> + where + T: NativeObject, + { + use std::ops::Deref; + match self.data { + ObjectData::NativeObject(ref object) => object.deref().as_any().downcast_ref::(), + _ => None, + } + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: NativeObject, + { + use std::ops::DerefMut; + match self.data { + ObjectData::NativeObject(ref mut object) => { + object.deref_mut().as_mut_any().downcast_mut::() + } + _ => None, + } + } } /// Create a new object. pub fn make_object(_: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { if let Some(arg) = args.get(0) { if !arg.is_null_or_undefined() { - return Ok(Value::object(Object::from(arg).unwrap())); + return arg.to_object(ctx); } } - let global = &ctx.realm.global_obj; - - let object = Value::new_object(Some(global)); - Ok(object) + Ok(Value::new_object(Some(ctx.global()))) } /// `Object.create( proto, [propertiesObject] )` diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 6400543d1f0..ff6bf9777eb 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -26,7 +26,7 @@ use crate::{ builtins, builtins::{ function::{Function, FunctionFlags, NativeFunction}, - object::{GcObject, Object, ObjectData, PROTOTYPE}, + object::{Class, ClassBuilder, GcObject, Object, ObjectData, PROTOTYPE}, property::PropertyKey, value::{PreferredType, RcString, RcSymbol, Type, Value}, Console, Symbol, @@ -107,7 +107,7 @@ impl Interpreter { /// Retrieves the global object of the `Realm` of this executor. #[inline] - pub(crate) fn global(&self) -> &Value { + pub fn global(&self) -> &Value { &self.realm.global_obj } @@ -356,6 +356,18 @@ impl Interpreter { let object_prototype = self.global().get_field("Object").get_field(PROTOTYPE); GcObject::new(Object::create(object_prototype)) } + + pub fn register_global_class(&mut self) -> Result<()> + where + T: Class, + { + let mut class_builder = ClassBuilder::new::(self); + T::methods(&mut class_builder)?; + + let class = class_builder.build(); + self.global().set_field(T::NAME, class); + Ok(()) + } } impl Executable for Node { diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 6e3f9f05855..209d88725d2 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -53,6 +53,8 @@ pub use crate::{ }; use std::result::Result as StdResult; +pub use gc::{custom_trace, unsafe_empty_trace, Finalize, Trace}; + /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) #[must_use] pub type Result = StdResult; From bad71b54513380f07d08bca2b881edf22d53be56 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 18 Aug 2020 18:04:30 +0200 Subject: [PATCH 2/7] Added attribute to class and some documentation --- boa/src/builtins/object/mod.rs | 38 +++++++++++++++------- boa/src/builtins/property/attribute/mod.rs | 4 +++ boa/src/exec/mod.rs | 8 +++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index d081ebbbacc..1df214eb518 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -48,6 +48,9 @@ mod tests; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. pub static PROTOTYPE: &str = "prototype"; +/// This trait allows Rust types to be passed around as objects. +/// +/// This is automatically implemented, when a type implements `Debug`, `Any` and `Trace`. pub trait NativeObject: Debug + Any + Trace { fn as_any(&self) -> &dyn Any; fn as_mut_any(&mut self) -> &mut dyn Any; @@ -63,13 +66,32 @@ impl NativeObject for T { } } -pub trait Class: NativeObject { +/// Native class. +pub trait Class: NativeObject + Sized { /// The binding name of the object. const NAME: &'static str; - /// The amount of arguments the class `constructor` takes. + /// The amount of arguments the class `constructor` takes, default is `0`. const LENGTH: usize = 0; + /// The attibutes the class will be binded with, default is `writable`, `enumerable`, `configurable`. + const ATTRIBUTE: Attribute = Attribute::ALL; + + /// The constructor of the class. + fn constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result; + + /// Initializes the internals and the methods of the class. + fn methods(class: &mut ClassBuilder<'_>) -> Result<()>; +} + +/// This is a wrapper around `Class::constructor` that sets the internal data of a class. +/// +/// This is automatically implemented, when a type implements `Class`. +pub trait ClassConstructor: Class { + fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result + where + Self: Sized; +} - /// This is a wrapper around `Self::constructor` that sets the internal data of the class. +impl ClassConstructor for T { fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result where Self: Sized, @@ -78,14 +100,6 @@ pub trait Class: NativeObject { this.set_data(ObjectData::NativeObject(Box::new(object_instance))); Ok(this.clone()) } - - /// The constructor of the class. - fn constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result - where - Self: Sized; - - /// Initializes the internals and the methods of the class. - fn methods(class: &mut ClassBuilder<'_>) -> Result<()>; } /// Class builder which allows adding methods and static methods to the class. @@ -99,7 +113,7 @@ pub struct ClassBuilder<'context> { impl<'context> ClassBuilder<'context> { pub(crate) fn new(context: &'context mut Interpreter) -> Self where - T: Class, + T: ClassConstructor, { let global = context.global(); diff --git a/boa/src/builtins/property/attribute/mod.rs b/boa/src/builtins/property/attribute/mod.rs index e909a38ad7c..6261b0b48a7 100644 --- a/boa/src/builtins/property/attribute/mod.rs +++ b/boa/src/builtins/property/attribute/mod.rs @@ -20,6 +20,9 @@ bitflags! { /// None of the flags are present. const NONE = 0b0000_0000; + /// All the flags set (`WRITABLE`, `ENUMERABLE`, `CONFIGURABLE`). + const ALL = 0b0011_1111; + /// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value. const WRITABLE = 0b0000_0011; @@ -46,6 +49,7 @@ bitflags! { /// Is the `Configurable` flag defined. const HAS_CONFIGURABLE = 0b0010_0000; + } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index ff6bf9777eb..7d1742c4a73 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -27,7 +27,7 @@ use crate::{ builtins::{ function::{Function, FunctionFlags, NativeFunction}, object::{Class, ClassBuilder, GcObject, Object, ObjectData, PROTOTYPE}, - property::PropertyKey, + property::{Property, PropertyKey}, value::{PreferredType, RcString, RcSymbol, Type, Value}, Console, Symbol, }, @@ -365,7 +365,11 @@ impl Interpreter { T::methods(&mut class_builder)?; let class = class_builder.build(); - self.global().set_field(T::NAME, class); + let property = Property::data_descriptor(class.into(), T::ATTRIBUTE); + self.global() + .as_object_mut() + .unwrap() + .insert_property(T::NAME, property); Ok(()) } } From 8bcbaff9b21c2df1324168dd71e1343d792c1f36 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Wed, 19 Aug 2020 17:29:21 +0200 Subject: [PATCH 3/7] PR feedback --- boa/examples/classes.rs | 15 ++++++++++----- boa/src/builtins/object/mod.rs | 2 +- boa/src/builtins/property/attribute/mod.rs | 6 ------ boa/src/builtins/property/mod.rs | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs index e4a533ea5ea..626378b24f0 100644 --- a/boa/examples/classes.rs +++ b/boa/examples/classes.rs @@ -1,8 +1,13 @@ -use boa::builtins::object::{Class, ClassBuilder}; -use boa::builtins::value::*; -use boa::exec::*; -use boa::realm::Realm; -use boa::*; +use boa::{ + builtins::{ + object::{Class, ClassBuilder}, + value::Value, + }, + exec::Interpreter, + forward_val, + realm::Realm, + Result, +}; use gc::{Finalize, Trace}; diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 1df214eb518..ca81e251d74 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -73,7 +73,7 @@ pub trait Class: NativeObject + Sized { /// The amount of arguments the class `constructor` takes, default is `0`. const LENGTH: usize = 0; /// The attibutes the class will be binded with, default is `writable`, `enumerable`, `configurable`. - const ATTRIBUTE: Attribute = Attribute::ALL; + const ATTRIBUTE: Attribute = Attribute::all(); /// The constructor of the class. fn constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result; diff --git a/boa/src/builtins/property/attribute/mod.rs b/boa/src/builtins/property/attribute/mod.rs index 6261b0b48a7..476e262fd4f 100644 --- a/boa/src/builtins/property/attribute/mod.rs +++ b/boa/src/builtins/property/attribute/mod.rs @@ -17,12 +17,6 @@ bitflags! { /// Additionaly there are flags for when the flags are defined. #[derive(Finalize)] pub struct Attribute: u8 { - /// None of the flags are present. - const NONE = 0b0000_0000; - - /// All the flags set (`WRITABLE`, `ENUMERABLE`, `CONFIGURABLE`). - const ALL = 0b0011_1111; - /// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value. const WRITABLE = 0b0000_0011; diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index f32895e1fc8..4afedf9f94e 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -72,7 +72,7 @@ impl Property { #[inline] pub fn empty() -> Self { Self { - attribute: Attribute::NONE, + attribute: Attribute::empty(), value: None, get: None, set: None, From 8cdd00ef2dfdb0bdbff54e1e81f683de2eb9f113 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Fri, 21 Aug 2020 04:29:03 +0200 Subject: [PATCH 4/7] Added methods to add inherited and static properties to classes --- boa/examples/classes.rs | 13 ++++++++++++- boa/src/builtins/object/mod.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs index 626378b24f0..6e1599a91a9 100644 --- a/boa/examples/classes.rs +++ b/boa/examples/classes.rs @@ -1,6 +1,7 @@ use boa::{ builtins::{ object::{Class, ClassBuilder}, + property::Attribute, value::Value, }, exec::Interpreter, @@ -60,6 +61,12 @@ impl Class for Person { } Ok(false.into()) }); + class.property("inheritedProperty", 10, Attribute::default()); + class.static_property( + "staticProperty", + "Im a static property", + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::PERMANENT, + ); Ok(()) } @@ -83,7 +90,11 @@ fn main() { if (!Person.is('Hello')) { console.log('\'Hello\' string is not a Person class instance.'); } - ", + + console.log(Person.staticProperty); + console.log(person.inheritedProperty); + console.log(Person.prototype.inheritedProperty === person.inheritedProperty); + ", ) .unwrap(); } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index ca81e251d74..1418d85fc52 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -215,6 +215,40 @@ impl<'context> ClassBuilder<'context> { .insert_field(name, Value::from(function)); } + /// Add a property to the class, with the specified attribute. + /// + /// It is added to `prototype`. + #[inline] + pub fn property(&mut self, key: K, value: V, attribute: Attribute) + where + K: Into, + V: Into, + { + // We bitwise or (`|`) with `Attribute::default()` (`READONLY | NON_ENUMERABLE | PERMANENT`) + // so we dont get an empty attribute. + let property = Property::data_descriptor(value.into(), attribute | Attribute::default()); + self.prototype + .borrow_mut() + .insert_property(key.into(), property); + } + + /// Add a static property to the class, with the specified attribute. + /// + /// It is added to class object itself. + #[inline] + pub fn static_property(&mut self, key: K, value: V, attribute: Attribute) + where + K: Into, + V: Into, + { + // We bitwise or (`|`) with `Attribute::default()` (`READONLY | NON_ENUMERABLE | PERMANENT`) + // so we dont get an empty attribute. + let property = Property::data_descriptor(value.into(), attribute | Attribute::default()); + self.object + .borrow_mut() + .insert_property(key.into(), property); + } + pub fn context(&mut self) -> &'_ mut Interpreter { self.context } From d0b89d77584e99145fdc75bf73c42804c11d0881 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Fri, 21 Aug 2020 21:27:14 +0200 Subject: [PATCH 5/7] Added documentation --- boa/examples/classes.rs | 58 +++++++++++++++++++++++++++++++--- boa/src/builtins/object/mod.rs | 7 ++++ boa/src/exec/mod.rs | 13 ++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs index 6e1599a91a9..9f3ecc60251 100644 --- a/boa/examples/classes.rs +++ b/boa/examples/classes.rs @@ -12,56 +12,105 @@ use boa::{ use gc::{Finalize, Trace}; +// We create a new struct that is going to represent a person. +// +// We derive `Debug`, `Trace` and `Finalize`, It automatically implements `NativeObject` +// so we can pass it an object in JavaScript. +// +// The fields of the sturct are not accesable by JavaScript unless accessors are created for them. +/// This Represents a Person. #[derive(Debug, Trace, Finalize)] struct Person { + /// The name of the person. name: String, + /// The age of the preson. age: u32, } +// Here we implement a static method for Person that matches the `NativeFunction` signiture. +// +// NOTE: The function does not have to be implemented of Person it can be a free function, +// or any function that matches that signature. impl Person { + /// This function says hello fn say_hello(this: &Value, _: &[Value], ctx: &mut Interpreter) -> Result { + // We check if this is an object. if let Some(object) = this.as_object() { + // If it is we downcast the type to type `Person`. if let Some(person) = object.downcast_ref::() { + // we print the message to stdout. println!( "Hello my name is {}, I'm {} years old", - person.name, person.age + person.name, + person.age // Here we can access the native rust fields of Person struct. ); return Ok(Value::undefined()); } } + // If `this` was not an object or the type was not an native object `Person`, + // we throw a `TypeError`. ctx.throw_type_error("'this' is not a Person object") } } impl Class for Person { + // we set the binging name of this function to be `"Person"`. + // It does not have to be `"Person"` it can be any string. const NAME: &'static str = "Person"; + // We set the length to `2` since we accept 2 arguments in the constructor. + // + // This is the same as `Object.length`. + // NOTE: If this is not defiend that the default is `0`. const LENGTH: usize = 2; + // This is what is called when we do `new Person()` fn constructor(_this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { + // we get the first arguemnt of undefined if the first one is unavalable and call `to_string`. + // + // This is equivalent to `String(arg)`. let name = args.get(0).cloned().unwrap_or_default().to_string(ctx)?; + // we get the second arguemnt of undefined if the first one is unavalable and call `to_u32`. + // + // This is equivalent to `arg | 0`. let age = args.get(1).cloned().unwrap_or_default().to_u32(ctx)?; + // we construct the the native struct `Person` let person = Person { name: name.to_string(), age, }; - Ok(person) + Ok(person) // and we return it. } + /// This is where the object is intitialized. fn methods(class: &mut ClassBuilder) -> Result<()> { + // we add a inheritable method `sayHello` with length `0` the amount of args it takes. + // + // This function is added to `Person.prototype.sayHello()` class.method("sayHello", 0, Self::say_hello); + // we add a static mathod `is`, and here we use a closure, but it must be converible + // to a NativeFunction. it must not contain state, if it does it will give a compilation error. + // + // This function is added to `Person.is()` class.static_method("is", 1, |_this, args, _ctx| { if let Some(arg) = args.get(0) { if let Some(object) = arg.as_object() { if object.is::() { - return Ok(true.into()); + // we check if the object type is `Person` + return Ok(true.into()); // return `true`. } } } - Ok(false.into()) + Ok(false.into()) // otherwise `false`. }); + + // Add a inherited property with the value `10`, with deafault attribute. + // (`READONLY, NON_ENUMERABLE, PERMANENT). class.property("inheritedProperty", 10, Attribute::default()); + + // Add a static property with the value `"Im a static property"`, with deafault attribute. + // (`WRITABLE, ENUMERABLE, PERMANENT`). class.static_property( "staticProperty", "Im a static property", @@ -76,6 +125,7 @@ fn main() { let realm = Realm::create(); let mut context = Interpreter::new(realm); + // we register the global class `Person`. context.register_global_class::().unwrap(); forward_val( diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 1418d85fc52..ad271b8e676 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -413,6 +413,7 @@ impl Object { } } + /// Create a new native object of type `T`. pub fn native_object(value: T) -> Self where T: NativeObject, @@ -612,10 +613,12 @@ impl Object { self.prototype = prototype } + /// Returns `true` if it holds an Rust type that implements `NativeObject`. pub fn is_native_object(&self) -> bool { matches!(self.data, ObjectData::NativeObject(_)) } + /// Reeturn `true` if it is a native object and the native type is `T`. pub fn is(&self) -> bool where T: NativeObject, @@ -627,6 +630,8 @@ impl Object { } } + /// Downcast a reference to the object, + /// if the object is type native object type `T`. pub fn downcast_ref(&self) -> Option<&T> where T: NativeObject, @@ -638,6 +643,8 @@ impl Object { } } + /// Downcast a mutable reference to the object, + /// if the object is type native object type `T`. pub fn downcast_mut(&mut self) -> Option<&mut T> where T: NativeObject, diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 7d1742c4a73..1be0fb301c0 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -357,6 +357,19 @@ impl Interpreter { GcObject::new(Object::create(object_prototype)) } + /// Register a global class of type `T`, where `T` implemets `Class`. + /// + /// # Example + /// ```no_run + /// #[derive(Debug, Trace, Finalize)] + /// struct MyClass; + /// + /// impl Class for MyClass { + /// // ... + /// } + /// + /// context.register_global_class::(); + /// ``` pub fn register_global_class(&mut self) -> Result<()> where T: Class, From 11de16a9df44175e8ea9654df264a42d506a58df Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 27 Aug 2020 22:25:13 +0200 Subject: [PATCH 6/7] Change class initialization method name --- boa/examples/classes.rs | 2 +- boa/src/builtins/object/mod.rs | 2 +- boa/src/exec/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs index 9f3ecc60251..1ddb74ccd00 100644 --- a/boa/examples/classes.rs +++ b/boa/examples/classes.rs @@ -84,7 +84,7 @@ impl Class for Person { } /// This is where the object is intitialized. - fn methods(class: &mut ClassBuilder) -> Result<()> { + fn init(class: &mut ClassBuilder) -> Result<()> { // we add a inheritable method `sayHello` with length `0` the amount of args it takes. // // This function is added to `Person.prototype.sayHello()` diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index ad271b8e676..987821e448c 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -79,7 +79,7 @@ pub trait Class: NativeObject + Sized { fn constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result; /// Initializes the internals and the methods of the class. - fn methods(class: &mut ClassBuilder<'_>) -> Result<()>; + fn init(class: &mut ClassBuilder<'_>) -> Result<()>; } /// This is a wrapper around `Class::constructor` that sets the internal data of a class. diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 1be0fb301c0..530d01fd5c5 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -360,7 +360,7 @@ impl Interpreter { /// Register a global class of type `T`, where `T` implemets `Class`. /// /// # Example - /// ```no_run + /// ```ignore /// #[derive(Debug, Trace, Finalize)] /// struct MyClass; /// @@ -375,7 +375,7 @@ impl Interpreter { T: Class, { let mut class_builder = ClassBuilder::new::(self); - T::methods(&mut class_builder)?; + T::init(&mut class_builder)?; let class = class_builder.build(); let property = Property::data_descriptor(class.into(), T::ATTRIBUTE); From 1cb6323682303faf9aa554a1f7079b2c700fd9ef Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Sat, 29 Aug 2020 02:25:11 +0200 Subject: [PATCH 7/7] Add documentation to `NativeObject` trait methods --- boa/src/builtins/object/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 987821e448c..33dd2815ccf 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -52,7 +52,10 @@ pub static PROTOTYPE: &str = "prototype"; /// /// This is automatically implemented, when a type implements `Debug`, `Any` and `Trace`. pub trait NativeObject: Debug + Any + Trace { + /// Convert the Rust type which implements `NativeObject` to a `&dyn Any`. fn as_any(&self) -> &dyn Any; + + /// Convert the Rust type which implements `NativeObject` to a `&mut dyn Any`. fn as_mut_any(&mut self) -> &mut dyn Any; }