use crate::{
builtins::function::NativeFunctionSignature,
object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE},
property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue,
};
pub trait Class: NativeObject + Sized {
const NAME: &'static str;
const LENGTH: usize = 0;
const ATTRIBUTES: Attribute = Attribute::all();
fn constructor(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Self>;
fn init(class: &mut ClassBuilder<'_>) -> JsResult<()>;
}
pub trait ClassConstructor: Class {
fn raw_constructor(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue>
where
Self: Sized;
}
impl<T: Class> ClassConstructor for T {
fn raw_constructor(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue>
where
Self: Sized,
{
if this.is_undefined() {
return context.throw_type_error(format!(
"cannot call constructor of native class `{}` without new",
T::NAME
));
}
let class_constructor = context.global_object().clone().get(T::NAME, context)?;
let class_constructor = if let JsValue::Object(ref obj) = class_constructor {
obj
} else {
return context.throw_type_error(format!(
"invalid constructor for native class `{}` ",
T::NAME
));
};
let class_prototype =
if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? {
obj.clone()
} else {
return context.throw_type_error(format!(
"invalid default prototype for native class `{}`",
T::NAME
));
};
let prototype = this
.as_object()
.cloned()
.map(|obj| {
obj.get(PROTOTYPE, context)
.map(|val| val.as_object().cloned())
})
.transpose()?
.flatten()
.unwrap_or(class_prototype);
let native_instance = Self::constructor(this, args, context)?;
let object_instance = JsObject::from_proto_and_data(
prototype,
ObjectData::native_object(Box::new(native_instance)),
);
Ok(object_instance.into())
}
}
#[derive(Debug)]
pub struct ClassBuilder<'context> {
builder: ConstructorBuilder<'context>,
}
impl<'context> ClassBuilder<'context> {
#[inline]
pub(crate) fn new<T>(context: &'context mut Context) -> Self
where
T: ClassConstructor,
{
let mut builder = ConstructorBuilder::new(context, T::raw_constructor);
builder.name(T::NAME);
builder.length(T::LENGTH);
Self { builder }
}
#[inline]
pub(crate) fn build(mut self) -> JsFunction {
JsFunction::from_object_unchecked(self.builder.build().into())
}
#[inline]
pub fn method<N>(
&mut self,
name: N,
length: usize,
function: NativeFunctionSignature,
) -> &mut Self
where
N: AsRef<str>,
{
self.builder.method(function, name.as_ref(), length);
self
}
#[inline]
pub fn static_method<N>(
&mut self,
name: N,
length: usize,
function: NativeFunctionSignature,
) -> &mut Self
where
N: AsRef<str>,
{
self.builder.static_method(function, name.as_ref(), length);
self
}
#[inline]
pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.builder.property(key, value, attribute);
self
}
#[inline]
pub fn static_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.builder.static_property(key, value, attribute);
self
}
#[inline]
pub fn accessor<K>(
&mut self,
key: K,
get: Option<JsFunction>,
set: Option<JsFunction>,
attribute: Attribute,
) -> &mut Self
where
K: Into<PropertyKey>,
{
self.builder.accessor(key, get, set, attribute);
self
}
#[inline]
pub fn static_accessor<K>(
&mut self,
key: K,
get: Option<JsFunction>,
set: Option<JsFunction>,
attribute: Attribute,
) -> &mut Self
where
K: Into<PropertyKey>,
{
self.builder.static_accessor(key, get, set, attribute);
self
}
#[inline]
pub fn property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.builder.property_descriptor(key, property);
self
}
#[inline]
pub fn static_property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.builder.static_property_descriptor(key, property);
self
}
#[inline]
pub fn context(&mut self) -> &'_ mut Context {
self.builder.context()
}
}