use crate::{
builtins::{
function::NativeFunctionData,
property::Property,
value::{from_value, same_value, to_value, ResultValue, Value, ValueData},
},
exec::Interpreter,
};
use gc::Gc;
use gc_derive::{Finalize, Trace};
use std::{borrow::Borrow, collections::HashMap, ops::Deref};
pub use internal_state::{InternalState, InternalStateCell};
mod internal_state;
pub static PROTOTYPE: &str = "prototype";
pub static INSTANCE_PROTOTYPE: &str = "__proto__";
#[derive(Trace, Finalize, Debug, Clone)]
pub struct Object {
pub kind: ObjectKind,
pub internal_slots: Box<HashMap<String, Value>>,
pub properties: Box<HashMap<String, Property>>,
pub sym_properties: Box<HashMap<i32, Property>>,
pub state: Option<Box<InternalStateCell>>,
}
impl Object {
pub fn default() -> Self {
let mut object = Object {
kind: ObjectKind::Ordinary,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
state: None,
};
object.set_internal_slot("extensible", to_value(true));
object
}
pub fn create(proto: Value) -> Object {
let mut obj = Object::default();
obj.internal_slots
.insert(INSTANCE_PROTOTYPE.to_string(), proto.clone());
obj.internal_slots
.insert("extensible".to_string(), to_value(true));
obj
}
pub fn get_internal_slot(&self, name: &str) -> Value {
match self.internal_slots.get(name) {
Some(v) => v.clone(),
None => Gc::new(ValueData::Null),
}
}
pub fn set_internal_slot(&mut self, name: &str, val: Value) {
self.internal_slots.insert(name.to_string(), val);
}
pub fn set_internal_method(&mut self, name: &str, val: NativeFunctionData) {
self.internal_slots.insert(name.to_string(), to_value(val));
}
pub fn set_method(&mut self, name: &str, val: NativeFunctionData) {
self.properties
.insert(name.to_string(), Property::default().value(to_value(val)));
}
fn from_boolean(argument: &Value) -> Self {
let mut obj = Object {
kind: ObjectKind::Boolean,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
state: None,
};
obj.internal_slots
.insert("BooleanData".to_string(), argument.clone());
obj
}
fn from_number(argument: &Value) -> Self {
let mut obj = Object {
kind: ObjectKind::Number,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
state: None,
};
obj.internal_slots
.insert("NumberData".to_string(), argument.clone());
obj
}
fn from_string(argument: &Value) -> Self {
let mut obj = Object {
kind: ObjectKind::String,
internal_slots: Box::new(HashMap::new()),
properties: Box::new(HashMap::new()),
sym_properties: Box::new(HashMap::new()),
state: None,
};
obj.internal_slots
.insert("StringData".to_string(), argument.clone());
obj
}
pub fn from(value: &Value) -> Result<Self, ()> {
match *value.deref().borrow() {
ValueData::Boolean(_) => Ok(Self::from_boolean(value)),
ValueData::Number(_) => Ok(Self::from_number(value)),
ValueData::String(_) => Ok(Self::from_string(value)),
ValueData::Object(ref obj) => Ok(obj.borrow().clone()),
_ => Err(()),
}
}
pub fn get_prototype_of(&self) -> Value {
match self.internal_slots.get(PROTOTYPE) {
Some(v) => v.clone(),
None => Gc::new(ValueData::Null),
}
}
pub fn set_prototype_of(&mut self, val: Value) -> bool {
debug_assert!(val.is_object() || val.is_null());
let current = self.get_internal_slot(PROTOTYPE);
if current == val {
return true;
}
let extensible = self.get_internal_slot("extensible");
if extensible.is_null() {
return false;
}
let mut p = val.clone();
let mut done = false;
while !done {
if p.is_null() {
done = true
} else if same_value(&to_value(self.clone()), &p, false) {
return false;
} else {
p = p.get_internal_slot(PROTOTYPE);
}
}
self.set_internal_slot(PROTOTYPE, val);
true
}
pub fn is_extensible(&self) -> bool {
match self.internal_slots.get("extensible") {
Some(ref v) => {
from_value((*v).clone()).expect("boolean expected")
}
None => false,
}
}
pub fn prevent_extensions(&mut self) -> bool {
self.set_internal_slot("extensible", to_value(false));
true
}
pub fn get_own_property(&self, prop: &Value) -> Property {
debug_assert!(Property::is_property_key(prop));
match *(*prop) {
ValueData::String(ref st) => {
match self.properties.get(st) {
None => Property::default(),
Some(ref v) => {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}
}
}
ValueData::Symbol(ref sym) => {
let sym_id = sym
.borrow()
.get_internal_slot("SymbolData")
.to_string()
.parse::<i32>()
.expect("Could not get Symbol ID");
match self.sym_properties.get(&sym_id) {
None => Property::default(),
Some(ref v) => {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}
}
}
_ => Property::default(),
}
}
pub fn has_property(&self, val: &Value) -> bool {
debug_assert!(Property::is_property_key(val));
let prop = self.get_own_property(val);
if prop.value.is_none() {
let parent: Value = self.get_prototype_of();
if !parent.is_null() {
return match *parent {
ValueData::Object(ref obj) => obj.borrow().has_property(val),
_ => false,
};
}
return false;
}
true
}
#[allow(clippy::option_unwrap_used)]
pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool {
let mut current = self.get_own_property(&to_value(property_key.to_string()));
let extensible = self.is_extensible();
if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() {
if !extensible {
return false;
}
if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() {
let sym_id = desc
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, desc);
} else {
self.properties.insert(property_key, desc);
}
return true;
}
if desc.is_none() {
return true;
}
if !current.configurable.unwrap_or(false) {
if desc.configurable.is_some() && desc.configurable.unwrap() {
return false;
}
if desc.enumerable.is_some()
&& (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap())
{
return false;
}
}
if desc.is_generic_descriptor() {
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.configurable.unwrap() {
return false;
}
if current.is_data_descriptor() {
current.value = None;
current.writable = None;
} else {
current.get = None;
current.set = None;
}
if current.value.is_some() && current.value.clone().unwrap().is_symbol() {
let sym_id = current
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, current.clone());
} else {
self.properties
.insert(property_key.clone(), current.clone());
}
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.configurable.unwrap() && !current.writable.unwrap() {
if desc.writable.is_some() && desc.writable.unwrap() {
return false;
}
if desc.value.is_some()
&& !same_value(
&desc.value.clone().unwrap(),
¤t.value.clone().unwrap(),
false,
)
{
return false;
}
return true;
}
} else {
if !current.configurable.unwrap() {
if desc.set.is_some()
&& !same_value(
&desc.set.clone().unwrap(),
¤t.set.clone().unwrap(),
false,
)
{
return false;
}
if desc.get.is_some()
&& !same_value(
&desc.get.clone().unwrap(),
¤t.get.clone().unwrap(),
false,
)
{
return false;
}
}
return true;
}
self.properties.insert(property_key, desc);
true
}
pub fn delete(&mut self, prop_key: &Value) -> bool {
debug_assert!(Property::is_property_key(prop_key));
let desc = self.get_own_property(prop_key);
if desc
.value
.clone()
.expect("unable to get value")
.is_undefined()
{
return true;
}
if desc.configurable.expect("unable to get value") {
self.properties.remove(&prop_key.to_string());
return true;
}
false
}
pub fn get(&self, val: &Value) -> Value {
debug_assert!(Property::is_property_key(val));
let desc = self.get_own_property(val);
if desc.value.clone().is_none()
|| desc
.value
.clone()
.expect("Failed to get object")
.is_undefined()
{
let parent = self.get_prototype_of();
if parent.is_null() {
return Gc::new(ValueData::Undefined);
}
let parent_obj = Object::from(&parent).expect("Failed to get object");
return parent_obj.get(val);
}
if desc.is_data_descriptor() {
return desc.value.clone().expect("failed to extract value");
}
let getter = desc.get.clone();
if getter.is_none() || getter.expect("Failed to get object").is_undefined() {
return Gc::new(ValueData::Undefined);
}
Gc::new(ValueData::Undefined)
}
pub fn set(&mut self, field: Value, val: Value) -> bool {
debug_assert!(Property::is_property_key(&field));
let mut own_desc = self.get_own_property(&field);
if own_desc.is_none() {
let parent = self.get_prototype_of();
if !parent.is_null() {
}
own_desc = Property::new()
.writable(true)
.enumerable(true)
.configurable(true);
}
if own_desc.is_data_descriptor() {
if !own_desc.writable.unwrap() {
return false;
}
own_desc = own_desc.value(val);
return self.define_own_property(field.to_string(), own_desc);
}
debug_assert!(own_desc.is_accessor_descriptor());
match own_desc.set {
None => false,
Some(_) => {
unimplemented!();
}
}
}
}
#[derive(Trace, Finalize, Clone, Debug, Eq, PartialEq)]
pub enum ObjectKind {
Function,
Array,
String,
Symbol,
Error,
Ordinary,
Boolean,
Number,
}
pub fn make_object(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(Gc::new(ValueData::Undefined))
}
pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object");
Ok(obj.get_field_slice(INSTANCE_PROTOTYPE))
}
pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object").clone();
let proto = args.get(1).expect("Cannot get object").clone();
obj.set_internal_slot(INSTANCE_PROTOTYPE, proto);
Ok(obj)
}
pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object");
let prop = from_value::<String>(args.get(1).expect("Cannot get object").clone())
.expect("Cannot get object");
let desc = from_value::<Property>(args.get(2).expect("Cannot get object").clone())
.expect("Cannot get object");
obj.set_prop(prop, desc);
Ok(Gc::new(ValueData::Undefined))
}
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(to_value(this.to_string()))
}
pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let prop = if args.is_empty() {
None
} else {
from_value::<String>(args.get(0).expect("Cannot get object").clone()).ok()
};
Ok(to_value(
prop.is_some() && this.get_prop(&prop.expect("Cannot get object")).is_some(),
))
}
pub fn create_constructor(_: &Value) -> Value {
let object = to_value(make_object as NativeFunctionData);
let mut prototype = Object::default();
prototype.set_method("hasOwnProperty", has_own_prop);
prototype.set_method("toString", to_string);
object.set_field_slice("length", to_value(1_i32));
object.set_field_slice(PROTOTYPE, to_value(prototype));
make_builtin_fn!(set_proto_of, named "setPrototypeOf", with length 2, of object);
make_builtin_fn!(get_proto_of, named "getPrototypeOf", with length 1, of object);
make_builtin_fn!(define_prop, named "defineProperty", with length 3, of object);
object
}