diff --git a/boa/examples/classes.rs b/boa/examples/classes.rs index 1ddb74ccd0..784fa69c51 100644 --- a/boa/examples/classes.rs +++ b/boa/examples/classes.rs @@ -1,17 +1,12 @@ use boa::{ - builtins::{ - object::{Class, ClassBuilder}, - property::Attribute, - value::Value, - }, + builtins::{property::Attribute, value::Value}, + class::{Class, ClassBuilder}, exec::Interpreter, forward_val, realm::Realm, - Result, + Finalize, Result, Trace, }; -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` @@ -140,7 +135,7 @@ 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); diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 33dd2815cc..15b1fb39a4 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::{Attribute, Property, PropertyKey}, + property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, Value}, BigInt, Date, RegExp, }, @@ -26,13 +26,10 @@ 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 std::{any::Any, result::Result as StdResult}; -use super::function::{ - make_builtin_fn, make_constructor_fn, BuiltInFunction, FunctionFlags, NativeFunction, -}; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::builtins::value::same_value; mod gcobject; @@ -69,194 +66,6 @@ impl NativeObject for T { } } -/// 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, 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 init(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; -} - -impl ClassConstructor for T { - 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()) - } -} - -/// 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: ClassConstructor, - { - 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)); - } - - /// 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 - } -} - /// The internal representation of an JavaScript object. #[derive(Debug, Trace, Finalize)] pub struct Object { diff --git a/boa/src/class.rs b/boa/src/class.rs new file mode 100644 index 0000000000..56e588b160 --- /dev/null +++ b/boa/src/class.rs @@ -0,0 +1,269 @@ +//! Traits and structs for implementing native classes. +//! +//! Native classes are implemented through the [`Class`][class-trait] trait. +//! ``` +//!# use boa::{ +//!# builtins::{property::Attribute, value::Value}, +//!# class::{Class, ClassBuilder}, +//!# exec::Interpreter, +//!# forward_val, +//!# realm::Realm, +//!# Finalize, Result, Trace, +//!# }; +//!# +//! // This does not have to be an enum it can also be a struct. +//! #[derive(Debug, Trace, Finalize)] +//! enum Animal { +//! Cat, +//! Dog, +//! Other, +//! } +//! +//! impl Class for Animal { +//! // we set the binging name of this function to be `"Animal"`. +//! const NAME: &'static str = "Animal"; +//! +//! // We set the length to `1` since we accept 1 arguments in the constructor. +//! const LENGTH: usize = 1; +//! +//! // This is what is called when we do `new Animal()` +//! fn constructor(_this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { +//! // This is equivalent to `String(arg)`. +//! let kind = args.get(0).cloned().unwrap_or_default().to_string(ctx)?; +//! +//! let animal = match kind.as_str() { +//! "cat" => Self::Cat, +//! "dog" => Self::Dog, +//! _ => Self::Other, +//! }; +//! +//! Ok(animal) +//! } +//! +//! /// This is where the object is intitialized. +//! fn init(class: &mut ClassBuilder) -> Result<()> { +//! class.method("speak", 0, |this, _args, _ctx| { +//! if let Some(object) = this.as_object() { +//! if let Some(animal) = object.downcast_ref::() { +//! match animal { +//! Self::Cat => println!("meow"), +//! Self::Dog => println!("woof"), +//! Self::Other => println!(r"¯\_(ツ)_/¯"), +//! } +//! } +//! } +//! Ok(Value::undefined()) +//! }); +//! +//! Ok(()) +//! } +//! } +//! ``` +//! +//! [class-trait]: ./trait.Class.html + +use crate::{ + builtins::{ + function::{BuiltInFunction, Function, FunctionFlags, NativeFunction}, + object::{GcObject, NativeObject, Object, ObjectData, PROTOTYPE}, + property::{Attribute, Property, PropertyKey}, + Value, + }, + Interpreter, Result, +}; +use std::fmt::Debug; + +/// 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, 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 init(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 { + /// The raw constructor that mathces the `NativeFunction` signature. + fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result + where + Self: Sized; +} + +impl ClassConstructor for T { + 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()) + } +} + +/// Class builder which allows adding methods and static methods to the class. +#[derive(Debug)] +pub struct ClassBuilder<'context> { + /// The current context. + context: &'context mut Interpreter, + + /// The constructor object. + object: GcObject, + + /// The prototype of the object. + prototype: GcObject, +} + +impl<'context> ClassBuilder<'context> { + pub(crate) fn new(context: &'context mut Interpreter) -> Self + where + T: ClassConstructor, + { + 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)); + } + + /// 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); + } + + /// Return the current context. + pub fn context(&mut self) -> &'_ mut Interpreter { + self.context + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 735c58b1af..03ed31d12e 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -26,11 +26,12 @@ use crate::{ builtins, builtins::{ function::{Function, FunctionFlags, NativeFunction}, - object::{Class, ClassBuilder, GcObject, Object, ObjectData, PROTOTYPE}, + object::{GcObject, Object, ObjectData, PROTOTYPE}, property::{Property, PropertyKey}, value::{PreferredType, RcString, RcSymbol, Type, Value}, Console, Symbol, }, + class::{Class, ClassBuilder}, realm::Realm, syntax::ast::{ constant::Const, @@ -365,7 +366,7 @@ impl Interpreter { /// struct MyClass; /// /// impl Class for MyClass { - /// // ... + /// // ... /// } /// /// context.register_global_class::(); diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 209d88725d..3ba4a2448a 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -35,6 +35,7 @@ )] pub mod builtins; +pub mod class; pub mod environment; pub mod exec; pub mod profiler;