#[cfg(test)]
mod tests;
pub mod val_type;
pub use crate::builtins::value::val_type::Type;
use crate::builtins::{
function::Function,
object::{
GcObject, InternalState, InternalStateCell, Object, ObjectData, INSTANCE_PROTOTYPE,
PROTOTYPE,
},
property::Property,
BigInt, Symbol,
};
use crate::exec::Interpreter;
use crate::BoaProfiler;
use gc::{Finalize, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{
any::Any,
collections::HashSet,
convert::TryFrom,
f64::NAN,
fmt::{self, Display},
ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub},
str::FromStr,
};
pub mod conversions;
pub mod display;
pub mod equality;
pub mod hash;
pub mod operations;
pub mod rcbigint;
pub mod rcstring;
pub mod rcsymbol;
pub use conversions::*;
pub(crate) use display::display_obj;
pub use equality::*;
pub use hash::*;
pub use operations::*;
pub use rcbigint::RcBigInt;
pub use rcstring::RcString;
pub use rcsymbol::RcSymbol;
#[must_use]
pub type ResultValue = Result<Value, Value>;
#[derive(Trace, Finalize, Debug, Clone)]
pub enum Value {
Null,
Undefined,
Boolean(bool),
String(RcString),
Rational(f64),
Integer(i32),
BigInt(RcBigInt),
Object(GcObject),
Symbol(RcSymbol),
}
impl Value {
#[inline]
pub fn undefined() -> Self {
Self::Undefined
}
#[inline]
pub fn null() -> Self {
Self::Null
}
#[inline]
pub fn nan() -> Self {
Self::number(NAN)
}
#[inline]
pub fn string<S>(value: S) -> Self
where
S: Into<RcString>,
{
Self::String(value.into())
}
#[inline]
pub fn rational<N>(value: N) -> Self
where
N: Into<f64>,
{
Self::Rational(value.into())
}
#[inline]
pub fn integer<I>(value: I) -> Self
where
I: Into<i32>,
{
Self::Integer(value.into())
}
#[inline]
pub fn number<N>(value: N) -> Self
where
N: Into<f64>,
{
Self::rational(value.into())
}
#[inline]
pub fn bigint<B>(value: B) -> Self
where
B: Into<RcBigInt>,
{
Self::BigInt(value.into())
}
#[inline]
pub fn boolean(value: bool) -> Self {
Self::Boolean(value)
}
#[inline]
pub fn object(object: Object) -> Self {
Self::Object(GcObject::new(object))
}
#[inline]
pub(crate) fn symbol(symbol: Symbol) -> Self {
Self::Symbol(RcSymbol::from(symbol))
}
pub fn as_num_to_power(&self, other: Self) -> Self {
match (self, other) {
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)),
(a, b) => Self::rational(a.to_number().powf(b.to_number())),
}
}
pub fn new_object(global: Option<&Value>) -> Self {
let _timer = BoaProfiler::global().start_event("new_object", "value");
if let Some(global) = global {
let object_prototype = global.get_field("Object").get_field(PROTOTYPE);
let object = Object::create(object_prototype);
Self::object(object)
} else {
Self::object(Object::default())
}
}
pub fn new_object_from_prototype(proto: Value, data: ObjectData) -> Self {
let mut object = Object::default();
object.data = data;
object
.internal_slots_mut()
.insert(INSTANCE_PROTOTYPE.to_string(), proto);
Self::object(object)
}
pub fn from_json(json: JSONValue, interpreter: &mut Interpreter) -> Self {
match json {
JSONValue::Number(v) => {
if let Some(Ok(integer_32)) = v.as_i64().map(i32::try_from) {
Self::integer(integer_32)
} else {
Self::rational(v.as_f64().expect("Could not convert value to f64"))
}
}
JSONValue::String(v) => Self::string(v),
JSONValue::Bool(v) => Self::boolean(v),
JSONValue::Array(vs) => {
let global_array_prototype = interpreter
.realm
.global_obj
.get_field("Array")
.get_field(PROTOTYPE);
let new_obj =
Value::new_object_from_prototype(global_array_prototype, ObjectData::Array);
let length = vs.len();
for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property(
idx.to_string(),
Property::default()
.value(Self::from_json(json, interpreter))
.writable(true)
.configurable(true)
.enumerable(true),
);
}
new_obj.set_property(
"length".to_string(),
Property::default().value(Self::from(length)),
);
new_obj
}
JSONValue::Object(obj) => {
let new_obj = Value::new_object(Some(&interpreter.realm.global_obj));
for (key, json) in obj.into_iter() {
let value = Self::from_json(json, interpreter);
new_obj.set_property(
key,
Property::default()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true),
);
}
new_obj
}
JSONValue::Null => Self::null(),
}
}
pub fn to_json(&self, interpreter: &mut Interpreter) -> Result<JSONValue, Value> {
match *self {
Self::Null => Ok(JSONValue::Null),
Self::Boolean(b) => Ok(JSONValue::Bool(b)),
Self::Object(ref obj) => {
if obj.borrow().is_array() {
let mut arr: Vec<JSONValue> = Vec::new();
for k in obj.borrow().properties().keys() {
if k != "length" {
let value = self.get_field(k.to_string());
if value.is_undefined() || value.is_function() {
arr.push(JSONValue::Null);
} else {
arr.push(self.get_field(k.to_string()).to_json(interpreter)?);
}
}
}
Ok(JSONValue::Array(arr))
} else {
let mut new_obj = Map::new();
for k in obj.borrow().properties().keys() {
let key = k.clone();
let value = self.get_field(k.to_string());
if !value.is_undefined() && !value.is_function() {
new_obj.insert(key.to_string(), value.to_json(interpreter)?);
}
}
Ok(JSONValue::Object(new_obj))
}
}
Self::String(ref str) => Ok(JSONValue::String(str.to_string())),
Self::Rational(num) => Ok(JSONNumber::from_f64(num)
.map(JSONValue::Number)
.unwrap_or(JSONValue::Null)),
Self::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))),
Self::BigInt(_) => {
Err(interpreter.construct_type_error("BigInt value can't be serialized in JSON"))
}
Self::Symbol(_) | Self::Undefined => {
unreachable!("Symbols and Undefined JSON Values depend on parent type");
}
}
}
pub fn is_extensible(&self) -> bool {
true
}
#[inline]
pub fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
#[inline]
pub fn as_object(&self) -> Option<GcCellRef<'_, Object>> {
match *self {
Self::Object(ref o) => Some(o.borrow()),
_ => None,
}
}
#[inline]
pub fn as_object_mut(&self) -> Option<GcCellRefMut<'_, Object>> {
match *self {
Self::Object(ref o) => Some(o.borrow_mut()),
_ => None,
}
}
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
#[inline]
pub fn is_function(&self) -> bool {
matches!(self, Self::Object(o) if o.borrow().is_function())
}
#[inline]
pub fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[inline]
pub fn is_null_or_undefined(&self) -> bool {
matches!(self, Self::Null | Self::Undefined)
}
#[inline]
pub fn is_double(&self) -> bool {
matches!(self, Self::Rational(_))
}
#[inline]
#[allow(clippy::float_cmp)]
pub fn is_integer(&self) -> bool {
let is_racional_intiger = |n: f64| n == ((n as i32) as f64);
match *self {
Self::Integer(_) => true,
Self::Rational(n) if is_racional_intiger(n) => true,
_ => false,
}
}
#[inline]
pub fn is_number(&self) -> bool {
matches!(self, Self::Rational(_) | Self::Integer(_))
}
#[inline]
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self, Self::BigInt(_))
}
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
match self {
Self::BigInt(bigint) => Some(bigint),
_ => None,
}
}
pub fn is_true(&self) -> bool {
match *self {
Self::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::Boolean(v) => v,
Self::BigInt(ref n) if *n.as_inner() != 0 => true,
_ => false,
}
}
pub fn to_number(&self) -> f64 {
match *self {
Self::Object(_) | Self::Symbol(_) | Self::Undefined => NAN,
Self::String(ref str) => {
if str.is_empty() {
return 0.0;
}
match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => NAN,
}
}
Self::Boolean(true) => 1.0,
Self::Boolean(false) | Self::Null => 0.0,
Self::Rational(num) => num,
Self::Integer(num) => f64::from(num),
Self::BigInt(_) => {
panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions")
}
}
}
pub fn to_integer(&self) -> i32 {
match *self {
Self::Object(_)
| Self::Undefined
| Self::Symbol(_)
| Self::Null
| Self::Boolean(false) => 0,
Self::String(ref str) => match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => 0,
},
Self::Rational(num) => num as i32,
Self::Boolean(true) => 1,
Self::Integer(num) => num,
Self::BigInt(_) => {
panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions")
}
}
}
pub fn to_boolean(&self) -> bool {
match *self {
Self::Undefined | Self::Null => false,
Self::Symbol(_) | Self::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::BigInt(ref n) if *n.as_inner() != 0 => true,
Self::Boolean(v) => v,
_ => false,
}
}
pub fn remove_property(&self, field: &str) -> bool {
self.as_object_mut()
.and_then(|mut x| x.properties_mut().remove(field))
.is_some()
}
pub fn get_property(&self, field: &str) -> Option<Property> {
let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
match self {
Self::Undefined => None,
Self::String(ref s) if field == "length" => {
Some(Property::default().value(Value::from(s.chars().count())))
}
Self::Object(ref object) => {
let object = object.borrow();
match object.properties().get(field) {
Some(value) => Some(value.clone()),
None => object
.internal_slots()
.get(INSTANCE_PROTOTYPE)
.and_then(|value| value.get_property(field)),
}
}
_ => None,
}
}
pub fn update_property(
&self,
field: &str,
value: Option<Value>,
enumerable: Option<bool>,
writable: Option<bool>,
configurable: Option<bool>,
) {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
if let Some(ref mut object) = self.as_object_mut() {
if let Some(ref mut property) = object.properties_mut().get_mut(field) {
property.value = value;
property.enumerable = enumerable;
property.writable = writable;
property.configurable = configurable;
}
}
}
#[inline]
pub fn get_internal_slot(&self, field: &str) -> Value {
let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value");
self.as_object()
.and_then(|x| x.internal_slots().get(field).cloned())
.unwrap_or_else(Value::undefined)
}
pub fn get_field<F>(&self, field: F) -> Self
where
F: Into<Value>,
{
let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
match field.into() {
Self::String(ref s) => {
match self.get_property(s) {
Some(prop) => {
let prop_getter = match prop.get {
Some(_) => None,
None => None,
};
if let Some(val) = prop_getter {
val
} else {
let val = prop
.value
.as_ref()
.expect("Could not get property as reference");
val.clone()
}
}
None => Value::undefined(),
}
}
Self::Symbol(_) => unimplemented!(),
_ => Value::undefined(),
}
}
#[inline]
pub fn has_internal_state(&self) -> bool {
matches!(self.as_object(), Some(object) if object.state().is_some())
}
pub fn get_internal_state(&self) -> Option<InternalStateCell> {
self.as_object()
.and_then(|object| object.state().as_ref().cloned())
}
pub fn with_internal_state_ref<S, R, F>(&self, f: F) -> R
where
S: Any + InternalState,
F: FnOnce(&S) -> R,
{
if let Some(object) = self.as_object() {
let state = object
.state()
.as_ref()
.expect("no state")
.downcast_ref()
.expect("wrong state type");
f(state)
} else {
panic!("not an object");
}
}
pub fn with_internal_state_mut<S, R, F>(&self, f: F) -> R
where
S: Any + InternalState,
F: FnOnce(&mut S) -> R,
{
if let Some(mut object) = self.as_object_mut() {
let state = object
.state_mut()
.as_mut()
.expect("no state")
.downcast_mut()
.expect("wrong state type");
f(state)
} else {
panic!("not an object");
}
}
#[inline]
pub fn has_field(&self, field: &str) -> bool {
let _timer = BoaProfiler::global().start_event("Value::has_field", "value");
self.get_property(field).is_some()
}
pub fn set_field<F, V>(&self, field: F, val: V) -> Value
where
F: Into<Value>,
V: Into<Value>,
{
let _timer = BoaProfiler::global().start_event("Value::set_field", "value");
let field = field.into();
let val = val.into();
if let Self::Object(ref obj) = *self {
if obj.borrow().is_array() {
if let Ok(num) = field.to_string().parse::<usize>() {
if num > 0 {
let len = i32::from(&self.get_field("length"));
if len < (num + 1) as i32 {
self.set_field("length", Value::from(num + 1));
}
}
}
}
if field.is_symbol() {
obj.borrow_mut().set(field, val.clone());
} else {
obj.borrow_mut()
.set(Value::from(field.to_string()), val.clone());
}
}
val
}
pub fn set_internal_slot(&self, field: &str, value: Value) -> Value {
let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec");
if let Some(mut object) = self.as_object_mut() {
object
.internal_slots_mut()
.insert(field.to_string(), value.clone());
}
value
}
#[inline]
pub fn set_data(&self, data: ObjectData) {
if let Self::Object(ref obj) = *self {
(*obj.deref().borrow_mut()).data = data;
}
}
pub fn set_property<S>(&self, field: S, property: Property) -> Property
where
S: Into<RcString>,
{
if let Some(mut object) = self.as_object_mut() {
object
.properties_mut()
.insert(field.into(), property.clone());
}
property
}
pub fn set_internal_state<T: Any + InternalState>(&self, state: T) {
if let Some(mut object) = self.as_object_mut() {
object.state_mut().replace(InternalStateCell::new(state));
}
}
pub fn from_func(function: Function) -> Value {
let length = function.params.len();
let new_func = Object::function(function);
let new_func_val = Value::from(new_func);
new_func_val.set_field("length", Value::from(length));
new_func_val
}
}
impl Default for Value {
fn default() -> Self {
Self::Undefined
}
}