//! 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 } }