use crate::{
exec::Interpreter,
js::{
function::NativeFunctionData,
value::{from_value, to_value, FromValue, ResultValue, ToValue, Value, ValueData},
},
};
use gc::Gc;
use gc_derive::{Finalize, Trace};
use std::collections::HashMap;
pub static PROTOTYPE: &'static str = "prototype";
pub static INSTANCE_PROTOTYPE: &'static str = "__proto__";
#[derive(Trace, Finalize, Debug, Clone)]
pub struct ObjectData {
pub kind: ObjectKind,
pub internal_slots: Box<HashMap<String, Value>>,
pub properties: Box<HashMap<String, Property>>,
pub sym_properties: Box<HashMap<usize, Property>>,
}
impl ObjectData {
pub fn default() -> ObjectData {
Self {
kind: ObjectKind::Ordinary,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
}
}
}
#[derive(Trace, Finalize, Clone, Debug)]
pub enum ObjectKind {
Function,
Array,
String,
Symbol,
Error,
Ordinary,
}
#[derive(Trace, Finalize, Clone, Debug)]
pub struct Property {
pub configurable: bool,
pub enumerable: bool,
pub writable: bool,
pub value: Value,
pub get: Value,
pub set: Value,
}
impl Property {
pub fn is_property_key(value: &Value) -> bool {
value.is_string() }
pub fn new(value: Value) -> Self {
Self {
configurable: false,
enumerable: false,
writable: false,
value,
get: Gc::new(ValueData::Undefined),
set: Gc::new(ValueData::Undefined),
}
}
}
impl ToValue for Property {
fn to_value(&self) -> Value {
let prop = ValueData::new_obj(None);
prop.set_field_slice("configurable", to_value(self.configurable));
prop.set_field_slice("enumerable", to_value(self.enumerable));
prop.set_field_slice("writable", to_value(self.writable));
prop.set_field_slice("value", self.value.clone());
prop.set_field_slice("get", self.get.clone());
prop.set_field_slice("set", self.set.clone());
prop
}
}
impl FromValue for Property {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(Self {
configurable: from_value(v.get_field_slice("configurable")).unwrap(),
enumerable: from_value(v.get_field_slice("enumerable")).unwrap(),
writable: from_value(v.get_field_slice("writable")).unwrap(),
value: v.get_field_slice("value"),
get: v.get_field_slice("get"),
set: v.get_field_slice("set"),
})
}
}
pub fn make_object(_: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
Ok(Gc::new(ValueData::Undefined))
}
pub fn get_proto_of(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let obj = args.get(0).unwrap();
Ok(obj.get_field_slice(INSTANCE_PROTOTYPE))
}
pub fn set_proto_of(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let obj = args.get(0).unwrap().clone();
let proto = args.get(1).unwrap().clone();
obj.set_field_slice(INSTANCE_PROTOTYPE, proto);
Ok(obj)
}
pub fn define_prop(_: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let obj = args.get(0).unwrap();
let prop = from_value::<String>(args.get(1).unwrap().clone()).unwrap();
let desc = from_value::<Property>(args.get(2).unwrap().clone()).unwrap();
obj.set_prop(prop, desc);
Ok(Gc::new(ValueData::Undefined))
}
pub fn to_string(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
Ok(to_value(this.to_string()))
}
pub fn has_own_prop(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let prop = if args.is_empty() {
None
} else {
from_value::<String>(args.get(0).unwrap().clone()).ok()
};
Ok(to_value(
prop.is_some() && this.get_prop(&prop.unwrap()).is_some(),
))
}
pub fn _create(global: &Value) -> Value {
let object = to_value(make_object as NativeFunctionData);
let prototype = ValueData::new_obj(Some(global));
prototype.set_field_slice(
"hasOwnProperty",
to_value(has_own_prop as NativeFunctionData),
);
prototype.set_field_slice("toString", to_value(to_string as NativeFunctionData));
object.set_field_slice("length", to_value(1_i32));
object.set_field_slice(PROTOTYPE, prototype);
object.set_field_slice(
"setPrototypeOf",
to_value(set_proto_of as NativeFunctionData),
);
object.set_field_slice(
"getPrototypeOf",
to_value(get_proto_of as NativeFunctionData),
);
object.set_field_slice(
"defineProperty",
to_value(define_prop as NativeFunctionData),
);
object
}
pub fn init(global: &Value) {
global.set_field_slice("Object", _create(global));
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_property_key_test() {
let v = Value::new(ValueData::String(String::from("Boop")));
assert!(Property::is_property_key(&v));
let v = Value::new(ValueData::Boolean(true));
assert!(!Property::is_property_key(&v));
}
}