Browse Source

Class module (#677)

- Added example and documenation
pull/674/head
Halid Odat 4 years ago committed by GitHub
parent
commit
76aba2ad55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      boa/examples/classes.rs
  2. 197
      boa/src/builtins/object/mod.rs
  3. 269
      boa/src/class.rs
  4. 5
      boa/src/exec/mod.rs
  5. 1
      boa/src/lib.rs

13
boa/examples/classes.rs

@ -1,17 +1,12 @@
use boa::{ use boa::{
builtins::{ builtins::{property::Attribute, value::Value},
object::{Class, ClassBuilder}, class::{Class, ClassBuilder},
property::Attribute,
value::Value,
},
exec::Interpreter, exec::Interpreter,
forward_val, forward_val,
realm::Realm, realm::Realm,
Result, Finalize, Result, Trace,
}; };
use gc::{Finalize, Trace};
// We create a new struct that is going to represent a person. // We create a new struct that is going to represent a person.
// //
// We derive `Debug`, `Trace` and `Finalize`, It automatically implements `NativeObject` // We derive `Debug`, `Trace` and `Finalize`, It automatically implements `NativeObject`
@ -140,7 +135,7 @@ fn main() {
if (!Person.is('Hello')) { if (!Person.is('Hello')) {
console.log('\'Hello\' string is not a Person class instance.'); console.log('\'Hello\' string is not a Person class instance.');
} }
console.log(Person.staticProperty); console.log(Person.staticProperty);
console.log(person.inheritedProperty); console.log(person.inheritedProperty);
console.log(Person.prototype.inheritedProperty === person.inheritedProperty); console.log(Person.prototype.inheritedProperty === person.inheritedProperty);

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

@ -17,7 +17,7 @@ use crate::{
builtins::{ builtins::{
function::Function, function::Function,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
property::{Attribute, Property, PropertyKey}, property::{Property, PropertyKey},
value::{RcBigInt, RcString, RcSymbol, Value}, value::{RcBigInt, RcString, RcSymbol, Value},
BigInt, Date, RegExp, BigInt, Date, RegExp,
}, },
@ -26,13 +26,10 @@ use crate::{
}; };
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::any::Any;
use std::fmt::{Debug, Display, Error, Formatter}; use std::fmt::{Debug, Display, Error, Formatter};
use std::result::Result as StdResult; use std::{any::Any, result::Result as StdResult};
use super::function::{ use super::function::{make_builtin_fn, make_constructor_fn};
make_builtin_fn, make_constructor_fn, BuiltInFunction, FunctionFlags, NativeFunction,
};
use crate::builtins::value::same_value; use crate::builtins::value::same_value;
mod gcobject; mod gcobject;
@ -69,194 +66,6 @@ impl<T: Any + Debug + Trace> 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<Self>;
/// 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<Value>
where
Self: Sized;
}
impl<T: Class> ClassConstructor for T {
fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value>
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<T>(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<N>(&mut self, name: N, length: usize, function: NativeFunction)
where
N: Into<String>,
{
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<N>(&mut self, name: N, length: usize, function: NativeFunction)
where
N: Into<String>,
{
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<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<Value>,
{
// 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<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<Value>,
{
// 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. /// The internal representation of an JavaScript object.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub struct Object { pub struct Object {

269
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<Self> {
//! // 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::<Animal>() {
//! 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<Self>;
/// 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<Value>
where
Self: Sized;
}
impl<T: Class> ClassConstructor for T {
fn raw_constructor(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value>
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<T>(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<N>(&mut self, name: N, length: usize, function: NativeFunction)
where
N: Into<String>,
{
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<N>(&mut self, name: N, length: usize, function: NativeFunction)
where
N: Into<String>,
{
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<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<Value>,
{
// 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<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<Value>,
{
// 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
}
}

5
boa/src/exec/mod.rs

@ -26,11 +26,12 @@ use crate::{
builtins, builtins,
builtins::{ builtins::{
function::{Function, FunctionFlags, NativeFunction}, function::{Function, FunctionFlags, NativeFunction},
object::{Class, ClassBuilder, GcObject, Object, ObjectData, PROTOTYPE}, object::{GcObject, Object, ObjectData, PROTOTYPE},
property::{Property, PropertyKey}, property::{Property, PropertyKey},
value::{PreferredType, RcString, RcSymbol, Type, Value}, value::{PreferredType, RcString, RcSymbol, Type, Value},
Console, Symbol, Console, Symbol,
}, },
class::{Class, ClassBuilder},
realm::Realm, realm::Realm,
syntax::ast::{ syntax::ast::{
constant::Const, constant::Const,
@ -365,7 +366,7 @@ impl Interpreter {
/// struct MyClass; /// struct MyClass;
/// ///
/// impl Class for MyClass { /// impl Class for MyClass {
/// // ... /// // ...
/// } /// }
/// ///
/// context.register_global_class::<MyClass>(); /// context.register_global_class::<MyClass>();

1
boa/src/lib.rs

@ -35,6 +35,7 @@
)] )]
pub mod builtins; pub mod builtins;
pub mod class;
pub mod environment; pub mod environment;
pub mod exec; pub mod exec;
pub mod profiler; pub mod profiler;

Loading…
Cancel
Save