#![allow(clippy::collapsible_if, clippy::collapsible_match)]
use crate::core::{
ClosureData, DestructuringElement, Expr, JSObjectDataPtr, PropertyKey, Statement, Value, evaluate_expr, get_well_known_symbol_rc,
new_js_object_data, obj_get_key_value, obj_set_key_value, prepare_function_call_env, value_to_string,
};
use crate::error::JSError;
use crate::js_array::{get_array_length, is_array, set_array_length};
use crate::js_date::is_date_object;
use crate::unicode::utf8_to_utf16;
use std::rc::Rc;
fn define_property_internal(target_obj: &JSObjectDataPtr, prop_key: PropertyKey, desc_obj: &JSObjectDataPtr) -> Result<(), JSError> {
let value_rc_opt = obj_get_key_value(desc_obj, &"value".into())?;
if let Some(existing_rc) = obj_get_key_value(target_obj, &prop_key)? {
if !target_obj.borrow().is_configurable(&prop_key) {
if let Some(cfg_rc) = obj_get_key_value(desc_obj, &"configurable".into())? {
if let Value::Boolean(true) = &*cfg_rc.borrow() {
return Err(raise_type_error!("Cannot make non-configurable property configurable"));
}
}
if let Some(enum_rc) = obj_get_key_value(desc_obj, &"enumerable".into())? {
if let Value::Boolean(new_enum) = &*enum_rc.borrow() {
let existing_enum = target_obj.borrow().is_enumerable(&prop_key);
if *new_enum != existing_enum {
return Err(raise_type_error!("Cannot change enumerability of non-configurable property"));
}
}
}
let existing_is_accessor = match &*existing_rc.borrow() {
Value::Property { value: _, getter, setter } => getter.is_some() || setter.is_some(),
Value::Getter(..) | Value::Setter(..) => true,
_ => false,
};
if !existing_is_accessor {
if obj_get_key_value(desc_obj, &"get".into())?.is_some() || obj_get_key_value(desc_obj, &"set".into())?.is_some() {
return Err(raise_type_error!("Cannot convert non-configurable data property to an accessor"));
}
if let Some(wrc) = obj_get_key_value(desc_obj, &"writable".into())? {
if let Value::Boolean(new_writable) = &*wrc.borrow() {
if *new_writable && !target_obj.borrow().is_writable(&prop_key) {
return Err(raise_type_error!("Cannot make non-writable property writable"));
}
}
}
if let Some(new_val_rc) = value_rc_opt.as_ref() {
if !target_obj.borrow().is_writable(&prop_key) {
let existing_val = match &*existing_rc.borrow() {
Value::Property { value: Some(v), .. } => v.borrow().clone(),
other => other.clone(),
};
if !crate::core::values_equal(&existing_val, &new_val_rc.borrow().clone()) {
return Err(raise_type_error!("Cannot change value of non-writable, non-configurable property"));
}
}
}
} else {
if value_rc_opt.is_some() || obj_get_key_value(desc_obj, &"writable".into())?.is_some() {
return Err(raise_type_error!("Cannot convert non-configurable accessor to a data property"));
}
if obj_get_key_value(desc_obj, &"get".into())?.is_some() || obj_get_key_value(desc_obj, &"set".into())?.is_some() {
return Err(raise_type_error!(
"Cannot change getter/setter of non-configurable accessor property"
));
}
}
}
}
let mut getter_opt: Option<(Vec<crate::core::Statement>, JSObjectDataPtr, Option<JSObjectDataPtr>)> = None;
if let Some(get_rc) = obj_get_key_value(desc_obj, &"get".into())? {
let get_val = get_rc.borrow();
if let Some((_params, body, env)) = crate::core::extract_closure_from_value(&get_val) {
getter_opt = Some((body, env, None));
}
}
#[allow(clippy::type_complexity)]
let mut setter_opt: Option<(Vec<DestructuringElement>, Vec<Statement>, JSObjectDataPtr, Option<JSObjectDataPtr>)> = None;
if let Some(set_rc) = obj_get_key_value(desc_obj, &"set".into())? {
let set_val = set_rc.borrow();
if let Some((params, body, env)) = crate::core::extract_closure_from_value(&set_val) {
setter_opt = Some((params, body, env, None));
}
}
let prop_descriptor = Value::Property {
value: value_rc_opt.clone(),
getter: getter_opt,
setter: setter_opt,
};
obj_set_key_value(target_obj, &prop_key, prop_descriptor)?;
Ok(())
}
pub fn handle_object_method(method: &str, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
match method {
"keys" => {
if args.is_empty() {
return Err(raise_type_error!("Object.keys requires at least one argument"));
}
if args.len() > 1 {
return Err(raise_type_error!("Object.keys accepts only one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
let mut keys = Vec::new();
for key in obj.borrow().keys() {
if !obj.borrow().is_enumerable(key) {
continue;
}
if let PropertyKey::String(s) = key
&& s != "length"
{
keys.push(Value::String(utf8_to_utf16(s)));
}
}
let result_obj = crate::js_array::create_array(env)?;
let len = keys.len();
for (i, key) in keys.into_iter().enumerate() {
obj_set_key_value(&result_obj, &i.to_string().into(), key)?;
}
set_array_length(&result_obj, len)?;
Ok(Value::Object(result_obj))
}
Value::Undefined => Err(raise_type_error!("Object.keys called on undefined")),
_ => {
let result_obj = crate::js_array::create_array(env)?;
set_array_length(&result_obj, 0)?;
Ok(Value::Object(result_obj))
}
}
}
"values" => {
if args.is_empty() {
return Err(raise_type_error!("Object.values requires at least one argument"));
}
if args.len() > 1 {
return Err(raise_type_error!("Object.values accepts only one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
let mut values = Vec::new();
for (key, value) in obj.borrow().properties.iter() {
if !obj.borrow().is_enumerable(key) {
continue;
}
if let PropertyKey::String(s) = key
&& s != "length"
{
values.push(value.borrow().clone());
}
}
let result_obj = crate::js_array::create_array(env)?;
let len = values.len();
for (i, value) in values.into_iter().enumerate() {
obj_set_key_value(&result_obj, &i.to_string().into(), value)?;
}
set_array_length(&result_obj, len)?;
Ok(Value::Object(result_obj))
}
Value::Undefined => Err(raise_type_error!("Object.values called on undefined")),
_ => {
let result_obj = crate::js_array::create_array(env)?;
set_array_length(&result_obj, 0)?;
Ok(Value::Object(result_obj))
}
}
}
"hasOwn" => {
if args.len() != 2 {
return Err(raise_type_error!("Object.hasOwn requires exactly two arguments"));
}
let obj_val = evaluate_expr(env, &args[0])?;
let prop_val = evaluate_expr(env, &args[1])?;
if matches!(obj_val, Value::Undefined | Value::Null) {
return Err(raise_type_error!("Cannot convert undefined or null to object"));
}
let key = match prop_val {
Value::String(s) => PropertyKey::String(String::from_utf16_lossy(&s)),
Value::BigInt(b) => PropertyKey::String(b.to_string()),
val @ Value::Symbol(_) => PropertyKey::Symbol(std::rc::Rc::new(std::cell::RefCell::new(val))),
val => PropertyKey::String(value_to_string(&val)),
};
let has_own = match obj_val {
Value::Object(obj) => obj.borrow().properties.contains_key(&key),
Value::String(s) => {
if let PropertyKey::String(k) = key {
if k == "length" {
true
} else if let Ok(idx) = k.parse::<usize>() {
idx < s.len()
} else {
false
}
} else {
false
}
}
_ => false,
};
Ok(Value::Boolean(has_own))
}
"getPrototypeOf" => {
if args.len() != 1 {
return Err(raise_type_error!("Object.getPrototypeOf requires exactly one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
if let Some(proto) = &obj.borrow().prototype {
Ok(Value::Object(proto.clone()))
} else {
Ok(Value::Null)
}
}
Value::Undefined | Value::Null => Err(raise_type_error!("Cannot convert undefined or null to object")),
_ => {
Ok(Value::Null)
}
}
}
"groupBy" => {
if args.len() != 2 {
return Err(raise_type_error!("Object.groupBy requires exactly two arguments"));
}
let items_val = evaluate_expr(env, &args[0])?;
let callback_val = evaluate_expr(env, &args[1])?;
let items_obj = match items_val {
Value::Object(obj) => obj,
_ => return Err(raise_type_error!("Object.groupBy expects an object as first argument")),
};
let result_obj = new_js_object_data();
result_obj.borrow_mut().prototype = None;
let len = get_array_length(&items_obj).unwrap_or(0);
for i in 0..len {
if let Some(val_rc) = obj_get_key_value(&items_obj, &i.to_string().into())? {
let val = val_rc.borrow().clone();
let key_val = if let Some((params, body, captured_env)) = crate::core::extract_closure_from_value(&callback_val) {
let args = vec![val.clone(), Value::Number(i as f64)];
let func_env = prepare_function_call_env(Some(&captured_env), None, Some(¶ms), &args, None, None)?;
crate::core::evaluate_statements(&func_env, &body)?
} else {
return Err(raise_type_error!("Object.groupBy expects a function as second argument"));
};
let key = match key_val {
Value::String(s) => PropertyKey::String(String::from_utf16_lossy(&s)),
Value::BigInt(b) => PropertyKey::String(b.to_string()),
Value::Symbol(_) => PropertyKey::Symbol(std::rc::Rc::new(std::cell::RefCell::new(key_val))),
_ => PropertyKey::String(value_to_string(&key_val)),
};
let group_arr = if let Some(arr_rc) = obj_get_key_value(&result_obj, &key)? {
if let Value::Object(arr) = &*arr_rc.borrow() {
arr.clone()
} else {
crate::js_array::create_array(env)?
}
} else {
let arr = crate::js_array::create_array(env)?;
obj_set_key_value(&result_obj, &key, Value::Object(arr.clone()))?;
arr
};
let current_len = get_array_length(&group_arr).unwrap_or(0);
obj_set_key_value(&group_arr, ¤t_len.to_string().into(), val)?;
crate::js_array::set_array_length(&group_arr, current_len + 1)?;
}
}
Ok(Value::Object(result_obj))
}
"create" => {
if args.is_empty() {
return Err(raise_type_error!("Object.create requires at least one argument"));
}
let proto_val = evaluate_expr(env, &args[0])?;
let proto_obj = match proto_val {
Value::Object(obj) => Some(obj),
Value::Undefined | Value::Null => None,
_ => {
return Err(raise_type_error!("Object.create prototype must be an object, null, or undefined"));
}
};
let new_obj = new_js_object_data();
if let Some(proto) = proto_obj {
new_obj.borrow_mut().prototype = Some(proto);
}
if args.len() > 1 {
let props_val = evaluate_expr(env, &args[1])?;
if let Value::Object(props_obj) = props_val {
for (key, desc_val) in props_obj.borrow().properties.iter() {
if let Value::Object(desc_obj) = &*desc_val.borrow() {
let value = if let Some(val) = obj_get_key_value(desc_obj, &"value".into())? {
val.borrow().clone()
} else {
Value::Undefined
};
obj_set_key_value(&new_obj, key, value)?;
}
}
}
}
Ok(Value::Object(new_obj))
}
"getOwnPropertySymbols" => {
if args.len() != 1 {
return Err(raise_type_error!("Object.getOwnPropertySymbols requires exactly one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
let result_obj = crate::js_array::create_array(env)?;
let mut idx = 0;
for (key, _value) in obj.borrow().properties.iter() {
if let PropertyKey::Symbol(sym) = key
&& let Value::Symbol(symbol_data) = &*sym.borrow()
{
obj_set_key_value(&result_obj, &idx.to_string().into(), Value::Symbol(symbol_data.clone()))?;
idx += 1;
}
}
set_array_length(&result_obj, idx)?;
Ok(Value::Object(result_obj))
}
_ => Err(raise_type_error!("Object.getOwnPropertySymbols called on non-object")),
}
}
"getOwnPropertyNames" => {
if args.len() != 1 {
return Err(raise_type_error!("Object.getOwnPropertyNames requires exactly one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
let result_obj = crate::js_array::create_array(env)?;
let mut idx = 0;
for (key, _value) in obj.borrow().properties.iter() {
if let PropertyKey::String(s) = key
&& s != "length"
{
obj_set_key_value(&result_obj, &idx.to_string().into(), Value::String(utf8_to_utf16(s)))?;
idx += 1;
}
}
set_array_length(&result_obj, idx)?;
Ok(Value::Object(result_obj))
}
_ => Err(raise_type_error!("Object.getOwnPropertyNames called on non-object")),
}
}
"getOwnPropertyDescriptors" => {
if args.len() != 1 {
return Err(raise_type_error!("Object.getOwnPropertyDescriptors requires exactly one argument"));
}
let obj_val = evaluate_expr(env, &args[0])?;
match obj_val {
Value::Object(obj) => {
let result_obj = new_js_object_data();
for (key, val_rc) in obj.borrow().properties.iter() {
if !obj.borrow().is_enumerable(key) {
}
let desc_obj = new_js_object_data();
match &*val_rc.borrow() {
Value::Property { value, getter, setter } => {
if let Some(v) = value {
obj_set_key_value(&desc_obj, &"value".into(), v.borrow().clone())?;
obj_set_key_value(&desc_obj, &"writable".into(), Value::Boolean(true))?;
}
if let Some((gbody, genv, _)) = getter {
let closure_data = ClosureData::new(&Vec::new(), gbody, genv, None);
obj_set_key_value(&desc_obj, &"get".into(), Value::Closure(Rc::new(closure_data)))?;
}
if let Some((sparams, sbody, senv, _)) = setter {
let closure_data = ClosureData::new(sparams, sbody, senv, None);
obj_set_key_value(&desc_obj, &"set".into(), Value::Closure(Rc::new(closure_data)))?;
}
let enum_flag = Value::Boolean(obj.borrow().is_enumerable(key));
obj_set_key_value(&desc_obj, &"enumerable".into(), enum_flag)?;
let config_flag = Value::Boolean(obj.borrow().is_configurable(key));
obj_set_key_value(&desc_obj, &"configurable".into(), config_flag)?;
}
other => {
obj_set_key_value(&desc_obj, &"value".into(), other.clone())?;
let writable_flag = Value::Boolean(obj.borrow().is_writable(key));
obj_set_key_value(&desc_obj, &"writable".into(), writable_flag)?;
let enum_flag = Value::Boolean(obj.borrow().is_enumerable(key));
obj_set_key_value(&desc_obj, &"enumerable".into(), enum_flag)?;
let config_flag = Value::Boolean(obj.borrow().is_configurable(key));
obj_set_key_value(&desc_obj, &"configurable".into(), config_flag)?;
}
}
log::trace!("descriptor for key={} created: {:?}", key, desc_obj.borrow().properties);
match key {
PropertyKey::String(s) => {
obj_set_key_value(&result_obj, &s.clone().into(), Value::Object(desc_obj.clone()))?;
}
PropertyKey::Symbol(sym_rc) => {
let property_key = PropertyKey::Symbol(sym_rc.clone());
obj_set_key_value(&result_obj, &property_key, Value::Object(desc_obj.clone()))?;
}
}
}
Ok(Value::Object(result_obj))
}
_ => Err(raise_type_error!("Object.getOwnPropertyDescriptors called on non-object")),
}
}
"assign" => {
if args.is_empty() {
return Err(raise_type_error!("Object.assign requires at least one argument"));
}
let target_val = evaluate_expr(env, &args[0])?;
let target_obj = match target_val {
Value::Object(o) => o,
Value::Undefined => return Err(raise_type_error!("Object.assign target cannot be undefined or null")),
Value::Number(n) => {
let obj = new_js_object_data();
obj_set_key_value(&obj, &"valueOf".into(), Value::Function("Number_valueOf".to_string()))?;
obj_set_key_value(&obj, &"toString".into(), Value::Function("Number_toString".to_string()))?;
obj_set_key_value(&obj, &"__value__".into(), Value::Number(n))?;
let _ = crate::core::set_internal_prototype_from_constructor(&obj, env, "Number");
obj
}
Value::Boolean(b) => {
let obj = new_js_object_data();
obj_set_key_value(&obj, &"valueOf".into(), Value::Function("Boolean_valueOf".to_string()))?;
obj_set_key_value(&obj, &"toString".into(), Value::Function("Boolean_toString".to_string()))?;
obj_set_key_value(&obj, &"__value__".into(), Value::Boolean(b))?;
let _ = crate::core::set_internal_prototype_from_constructor(&obj, env, "Boolean");
obj
}
Value::String(s) => {
let obj = new_js_object_data();
obj_set_key_value(&obj, &"valueOf".into(), Value::Function("String_valueOf".to_string()))?;
obj_set_key_value(&obj, &"toString".into(), Value::Function("String_toString".to_string()))?;
obj_set_key_value(&obj, &"length".into(), Value::Number(s.len() as f64))?;
obj_set_key_value(&obj, &"__value__".into(), Value::String(s.clone()))?;
let _ = crate::core::set_internal_prototype_from_constructor(&obj, env, "String");
obj
}
Value::BigInt(h) => {
let obj = new_js_object_data();
obj_set_key_value(&obj, &"valueOf".into(), Value::Function("BigInt_valueOf".to_string()))?;
obj_set_key_value(&obj, &"toString".into(), Value::Function("BigInt_toString".to_string()))?;
obj_set_key_value(&obj, &"__value__".into(), Value::BigInt(h.clone()))?;
let _ = crate::core::set_internal_prototype_from_constructor(&obj, env, "BigInt");
obj
}
Value::Symbol(sd) => {
let obj = new_js_object_data();
obj_set_key_value(&obj, &"valueOf".into(), Value::Function("Symbol_valueOf".to_string()))?;
obj_set_key_value(&obj, &"toString".into(), Value::Function("Symbol_toString".to_string()))?;
obj_set_key_value(&obj, &"__value__".into(), Value::Symbol(sd.clone()))?;
let _ = crate::core::set_internal_prototype_from_constructor(&obj, env, "Symbol");
obj
}
_ => new_js_object_data(),
};
for src_expr in args.iter().skip(1) {
let src_val = evaluate_expr(env, src_expr)?;
if let Value::Object(source_obj) = src_val {
for (key, _val_rc) in source_obj.borrow().properties.iter() {
if *key != "length".into() && *key != "__proto__".into() {
if let PropertyKey::String(_) = key
&& let Some(v_rc) = obj_get_key_value(&source_obj, key)?
{
obj_set_key_value(&target_obj, key, v_rc.borrow().clone())?;
}
}
}
}
}
Ok(Value::Object(target_obj))
}
"defineProperty" => {
if args.len() < 3 {
return Err(raise_type_error!("Object.defineProperty requires three arguments"));
}
let target_val = evaluate_expr(env, &args[0])?;
let target_obj = match target_val {
Value::Object(o) => o,
_ => return Err(raise_type_error!("Object.defineProperty called on non-object")),
};
let prop_val = evaluate_expr(env, &args[1])?;
let prop_key = match prop_val {
Value::String(s) => PropertyKey::String(String::from_utf16_lossy(&s)),
Value::Number(n) => PropertyKey::String(n.to_string()),
_ => return Err(raise_type_error!("Unsupported property key type in Object.defineProperty")),
};
let desc_val = evaluate_expr(env, &args[2])?;
let desc_obj = match desc_val {
Value::Object(o) => o,
_ => return Err(raise_type_error!("Property descriptor must be an object")),
};
define_property_internal(&target_obj, prop_key, &desc_obj)?;
Ok(Value::Object(target_obj))
}
"defineProperties" => {
if args.len() < 2 {
return Err(raise_type_error!("Object.defineProperties requires two arguments"));
}
let target_val = evaluate_expr(env, &args[0])?;
let target_obj = match target_val {
Value::Object(o) => o,
_ => return Err(raise_type_error!("Object.defineProperties called on non-object")),
};
let props_val = evaluate_expr(env, &args[1])?;
let props_obj = match props_val {
Value::Object(o) => o,
_ => return Err(raise_type_error!("Object.defineProperties requires an object as second argument")),
};
for (key, val_rc) in props_obj.borrow().properties.iter() {
if !props_obj.borrow().is_enumerable(key) {
continue;
}
let desc_val = val_rc.borrow().clone();
let desc_obj = match desc_val {
Value::Object(o) => o,
_ => return Err(raise_type_error!("Property descriptor must be an object")),
};
define_property_internal(&target_obj, key.clone(), &desc_obj)?;
}
Ok(Value::Object(target_obj))
}
_ => Err(raise_eval_error!(format!("Object.{method} is not implemented"))),
}
}
pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if !args.is_empty() {
return Err(raise_type_error!(format!(
"{}.toString() takes no arguments, but {} were provided",
match obj_val {
Value::Number(_) => "Number",
Value::BigInt(_) => "BigInt",
Value::String(_) => "String",
Value::Boolean(_) => "Boolean",
Value::Object(_) => "Object",
Value::Function(_) => "Function",
Value::Closure(..) | Value::AsyncClosure(..) => "Function",
Value::Undefined => "undefined",
Value::Null => "null",
Value::ClassDefinition(_) => "Class",
Value::Getter(..) => "Getter",
Value::Setter(..) => "Setter",
Value::Property { .. } => "Property",
Value::Promise(_) => "Promise",
Value::Symbol(_) => "Symbol",
Value::Map(_) => "Map",
Value::Set(_) => "Set",
Value::WeakMap(_) => "WeakMap",
Value::WeakSet(_) => "WeakSet",
Value::GeneratorFunction(..) => "GeneratorFunction",
Value::Generator(_) => "Generator",
Value::Proxy(_) => "Proxy",
Value::ArrayBuffer(_) => "ArrayBuffer",
Value::DataView(_) => "DataView",
Value::TypedArray(_) => "TypedArray",
Value::Uninitialized => "undefined",
},
args.len()
)));
}
match obj_val {
Value::Number(n) => Ok(Value::String(utf8_to_utf16(&n.to_string()))),
Value::BigInt(h) => Ok(Value::String(utf8_to_utf16(&h.to_string()))),
Value::String(s) => Ok(Value::String(s.clone())),
Value::Boolean(b) => Ok(Value::String(utf8_to_utf16(&b.to_string()))),
Value::Undefined => Ok(Value::String(utf8_to_utf16("[object Undefined]"))),
Value::Null => Ok(Value::String(utf8_to_utf16("[object Null]"))),
Value::Uninitialized => Ok(Value::String(utf8_to_utf16("[object Undefined]"))),
Value::Object(obj_map) => {
if let Some(wrapped_val) = obj_get_key_value(obj_map, &"__value__".into())? {
match &*wrapped_val.borrow() {
Value::Number(n) => return Ok(Value::String(utf8_to_utf16(&n.to_string()))),
Value::BigInt(h) => return Ok(Value::String(utf8_to_utf16(&h.to_string()))),
Value::Boolean(b) => return Ok(Value::String(utf8_to_utf16(&b.to_string()))),
Value::String(s) => return Ok(Value::String(s.clone())),
_ => {}
}
}
if is_date_object(obj_map) {
return crate::js_date::handle_date_method(obj_map, "toString", args, env);
}
if is_array(obj_map) {
let current_len = get_array_length(obj_map).unwrap_or(0);
let mut parts = Vec::new();
for i in 0..current_len {
if let Some(val_rc) = obj_get_key_value(obj_map, &i.to_string().into())? {
match &*val_rc.borrow() {
Value::Undefined | Value::Null => parts.push("".to_string()), Value::String(s) => parts.push(String::from_utf16_lossy(s)),
Value::Number(n) => parts.push(n.to_string()),
Value::Boolean(b) => parts.push(b.to_string()),
Value::BigInt(b) => parts.push(format!("{}n", b)),
_ => parts.push("[object Object]".to_string()),
}
} else {
parts.push("".to_string())
}
}
return Ok(Value::String(utf8_to_utf16(&parts.join(","))));
}
if let Some(tag_sym_rc) = get_well_known_symbol_rc("toStringTag") {
let key = PropertyKey::Symbol(tag_sym_rc.clone());
if let Some(tag_val_rc) = obj_get_key_value(obj_map, &key)?
&& let Value::String(s) = &*tag_val_rc.borrow()
{
return Ok(Value::String(utf8_to_utf16(&format!("[object {}]", String::from_utf16_lossy(s)))));
}
}
Ok(Value::String(utf8_to_utf16("[object Object]")))
}
Value::Function(name) => Ok(Value::String(utf8_to_utf16(&format!("[Function: {}]", name)))),
Value::Closure(..) | Value::AsyncClosure(..) => Ok(Value::String(utf8_to_utf16("[Function]"))),
Value::ClassDefinition(_) => Ok(Value::String(utf8_to_utf16("[Class]"))),
Value::Getter(..) => Ok(Value::String(utf8_to_utf16("[Getter]"))),
Value::Setter(..) => Ok(Value::String(utf8_to_utf16("[Setter]"))),
Value::Property { .. } => Ok(Value::String(utf8_to_utf16("[Property]"))),
Value::Promise(_) => Ok(Value::String(utf8_to_utf16("[object Promise]"))),
Value::Symbol(symbol_data) => {
let desc_str = symbol_data.description.as_deref().unwrap_or("");
Ok(Value::String(utf8_to_utf16(&format!("Symbol({})", desc_str))))
}
Value::Map(_) => Ok(Value::String(utf8_to_utf16("[object Map]"))),
Value::Set(_) => Ok(Value::String(utf8_to_utf16("[object Set]"))),
Value::WeakMap(_) => Ok(Value::String(utf8_to_utf16("[object WeakMap]"))),
Value::WeakSet(_) => Ok(Value::String(utf8_to_utf16("[object WeakSet]"))),
Value::GeneratorFunction(..) => Ok(Value::String(utf8_to_utf16("[GeneratorFunction]"))),
Value::Generator(_) => Ok(Value::String(utf8_to_utf16("[object Generator]"))),
Value::Proxy(_) => Ok(Value::String(utf8_to_utf16("[object Proxy]"))),
Value::ArrayBuffer(_) => Ok(Value::String(utf8_to_utf16("[object ArrayBuffer]"))),
Value::DataView(_) => Ok(Value::String(utf8_to_utf16("[object DataView]"))),
Value::TypedArray(_) => Ok(Value::String(utf8_to_utf16("[object TypedArray]"))),
}
}
pub(crate) fn handle_error_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<Value, JSError> {
if !args.is_empty() {
return Err(raise_type_error!("Error.prototype.toString takes no arguments"));
}
if let Value::Object(obj_map) = obj_val {
let name = if let Some(n_rc) = obj_get_key_value(obj_map, &"name".into())? {
if let Value::String(s) = &*n_rc.borrow() {
String::from_utf16_lossy(s)
} else {
"Error".to_string()
}
} else {
"Error".to_string()
};
let message = if let Some(m_rc) = obj_get_key_value(obj_map, &"message".into())? {
if let Value::String(s) = &*m_rc.borrow() {
String::from_utf16_lossy(s)
} else {
"".to_string()
}
} else {
"".to_string()
};
if message.is_empty() {
Ok(Value::String(utf8_to_utf16(&name)))
} else {
Ok(Value::String(utf8_to_utf16(&format!("{}: {}", name, message))))
}
} else {
Err(raise_type_error!("Error.prototype.toString called on non-object"))
}
}
pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if !args.is_empty() {
return Err(raise_type_error!(format!(
"{}.valueOf() takes no arguments, but {} were provided",
match obj_val {
Value::Number(_) => "Number",
Value::String(_) => "String",
Value::Boolean(_) => "Boolean",
Value::Object(_) => "Object",
Value::Function(_) => "Function",
Value::Closure(..) | Value::AsyncClosure(..) => "Function",
Value::Undefined => "undefined",
Value::Null => "null",
Value::ClassDefinition(_) => "Class",
&Value::Getter(..) => "Getter",
&Value::Setter(..) => "Setter",
&Value::Property { .. } => "Property",
&Value::Promise(_) => "Promise",
Value::BigInt(_) => "BigInt",
Value::Symbol(_) => "Symbol",
Value::Map(_) => "Map",
Value::Set(_) => "Set",
Value::WeakMap(_) => "WeakMap",
Value::WeakSet(_) => "WeakSet",
&Value::GeneratorFunction(..) => "GeneratorFunction",
&Value::Generator(_) => "Generator",
&Value::Proxy(_) => "Proxy",
&Value::ArrayBuffer(_) => "ArrayBuffer",
&Value::DataView(_) => "DataView",
&Value::TypedArray(_) => "TypedArray",
Value::Uninitialized => "undefined",
},
args.len()
)));
}
match obj_val {
Value::Number(n) => Ok(Value::Number(*n)),
Value::BigInt(s) => Ok(Value::BigInt(s.clone())),
Value::String(s) => Ok(Value::String(s.clone())),
Value::Boolean(b) => Ok(Value::Boolean(*b)),
Value::Undefined => Err(raise_type_error!("Cannot convert undefined to object")),
Value::Null => Err(raise_type_error!("Cannot convert null to object")),
Value::Object(obj) => {
if let Some(wrapped_val) = obj_get_key_value(obj, &"__value__".into())? {
return Ok(wrapped_val.borrow().clone());
}
if let Some(method_rc) = obj_get_key_value(obj, &"valueOf".into())? {
let method_val = method_rc.borrow().clone();
match method_val {
Value::Closure(data) | Value::AsyncClosure(data) => {
let _params = &data.params;
let body = &data.body;
let captured_env = &data.env;
let func_env =
prepare_function_call_env(Some(captured_env), Some(Value::Object(obj.clone())), None, &[], None, None)?;
let result = crate::core::evaluate_statements(&func_env, body)?;
if matches!(
result,
Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_) | Value::Symbol(_)
) {
return Ok(result);
}
}
Value::Function(func_name) => {
if func_name == "Object.prototype.valueOf" {
return Ok(Value::Object(obj.clone()));
}
if func_name == "Object.prototype.toString" {
return crate::js_object::handle_to_string_method(&Value::Object(obj.clone()), args, env);
}
let func_env = prepare_function_call_env(None, Some(Value::Object(obj.clone())), None, &[], None, None)?;
let res = crate::js_function::handle_global_function(&func_name, &[], &func_env)?;
if matches!(
res,
Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_) | Value::Symbol(_)
) {
return Ok(res);
}
}
_ => {}
}
if let Value::Object(func_obj_map) = &*method_rc.borrow() {
if let Some(cl_rc) = obj_get_key_value(func_obj_map, &"__closure__".into())? {
match &*cl_rc.borrow() {
Value::Closure(data) | Value::AsyncClosure(data) => {
let _params = &data.params;
let body = &data.body;
let captured_env = &data.env;
let func_env =
prepare_function_call_env(Some(captured_env), Some(Value::Object(obj.clone())), None, &[], None, None)?;
let result = crate::core::evaluate_statements(&func_env, body)?;
if matches!(
result,
Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_) | Value::Symbol(_)
) {
return Ok(result);
}
}
_ => {}
}
}
}
}
if is_date_object(obj) {
return crate::js_date::handle_date_method(obj, "valueOf", args, env);
}
Ok(Value::Object(obj.clone()))
}
Value::Function(name) => Ok(Value::Function(name.clone())),
Value::Closure(data) | Value::AsyncClosure(data) => {
let closure_data = ClosureData::new(&data.params, &data.body, &data.env, None);
Ok(Value::Closure(Rc::new(closure_data)))
}
Value::ClassDefinition(class_def) => Ok(Value::ClassDefinition(class_def.clone())),
Value::Getter(body, env, _) => Ok(Value::Getter(body.clone(), env.clone(), None)),
Value::Setter(param, body, env, _) => Ok(Value::Setter(param.clone(), body.clone(), env.clone(), None)),
Value::Property { value, getter, setter } => Ok(Value::Property {
value: value.clone(),
getter: getter.clone(),
setter: setter.clone(),
}),
Value::Promise(promise) => Ok(Value::Promise(promise.clone())),
Value::Symbol(symbol_data) => Ok(Value::Symbol(symbol_data.clone())),
Value::Map(map) => Ok(Value::Map(map.clone())),
Value::Set(set) => Ok(Value::Set(set.clone())),
Value::WeakMap(weakmap) => Ok(Value::WeakMap(weakmap.clone())),
Value::WeakSet(weakset) => Ok(Value::WeakSet(weakset.clone())),
Value::GeneratorFunction(_, data) => {
let closure_data = ClosureData::new(&data.params, &data.body, &data.env, None);
Ok(Value::GeneratorFunction(None, Rc::new(closure_data)))
}
Value::Generator(generator) => Ok(Value::Generator(generator.clone())),
Value::Proxy(proxy) => Ok(Value::Proxy(proxy.clone())),
Value::ArrayBuffer(array_buffer) => Ok(Value::ArrayBuffer(array_buffer.clone())),
Value::DataView(data_view) => Ok(Value::DataView(data_view.clone())),
Value::TypedArray(typed_array) => Ok(Value::TypedArray(typed_array.clone())),
Value::Uninitialized => Err(raise_type_error!("Cannot convert uninitialized to object")),
}
}
pub(crate) fn handle_object_prototype_builtin(
func_name: &str,
obj_map: &JSObjectDataPtr,
args: &[Expr],
env: &JSObjectDataPtr,
) -> Result<Option<Value>, JSError> {
match func_name {
"Object.prototype.hasOwnProperty" => {
if args.len() != 1 {
return Err(raise_eval_error!("hasOwnProperty requires one argument"));
}
let key_val = crate::core::evaluate_expr(env, &args[0])?;
let exists = crate::core::has_own_property_value(obj_map, &key_val);
Ok(Some(Value::Boolean(exists)))
}
"Object.prototype.isPrototypeOf" => {
if args.len() != 1 {
return Err(raise_eval_error!("isPrototypeOf requires one argument"));
}
let target_val = crate::core::evaluate_expr(env, &args[0])?;
match target_val {
Value::Object(target_map) => {
let mut current_opt = target_map.borrow().prototype.clone();
while let Some(parent) = current_opt {
if Rc::ptr_eq(&parent, obj_map) {
return Ok(Some(Value::Boolean(true)));
}
current_opt = parent.borrow().prototype.clone();
}
Ok(Some(Value::Boolean(false)))
}
_ => Ok(Some(Value::Boolean(false))),
}
}
"Object.prototype.propertyIsEnumerable" => {
if args.len() != 1 {
return Err(raise_eval_error!("propertyIsEnumerable requires one argument"));
}
let key_val = crate::core::evaluate_expr(env, &args[0])?;
let exists = crate::core::has_own_property_value(obj_map, &key_val);
Ok(Some(Value::Boolean(exists)))
}
"Object.prototype.toString" => Ok(Some(crate::js_object::handle_to_string_method(
&Value::Object(obj_map.clone()),
args,
env,
)?)),
"Object.prototype.valueOf" => Ok(Some(crate::js_object::handle_value_of_method(
&Value::Object(obj_map.clone()),
args,
env,
)?)),
"Object.prototype.toLocaleString" => Ok(Some(crate::js_object::handle_to_string_method(
&Value::Object(obj_map.clone()),
args,
env,
)?)),
_ => Ok(None),
}
}