#[cfg(test)]
mod tests;
use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
BigInt, Number,
},
object::{GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, Result,
};
use gc::{Finalize, Trace};
use serde_json::{Number as JSONNumber, Value as JSONValue};
use std::{
collections::HashSet,
convert::TryFrom,
f64::NAN,
fmt::{self, Display},
str::FromStr,
};
mod conversions;
pub(crate) mod display;
mod equality;
mod hash;
mod operations;
mod rcbigint;
mod rcstring;
mod rcsymbol;
mod r#type;
pub use conversions::*;
pub use display::ValueDisplay;
pub use equality::*;
pub use hash::*;
pub use operations::*;
pub use r#type::Type;
pub use rcbigint::RcBigInt;
pub use rcstring::RcString;
pub use rcsymbol::RcSymbol;
#[derive(Trace, Finalize, Debug, Clone)]
pub enum Value {
Null,
Undefined,
Boolean(bool),
String(RcString),
Rational(f64),
Integer(i32),
BigInt(RcBigInt),
Object(GcObject),
Symbol(RcSymbol),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegerOrInfinity {
Integer(i64),
PositiveInfinity,
NegativeInfinity,
}
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 fn symbol(symbol: RcSymbol) -> Self {
Self::Symbol(symbol)
}
pub fn new_object(context: &Context) -> Self {
let _timer = BoaProfiler::global().start_event("new_object", "value");
context.construct_object().into()
}
pub fn from_json(json: JSONValue, context: &mut Context) -> 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 array_prototype = context.standard_objects().array_object().prototype();
let new_obj: Value =
Object::with_prototype(array_prototype.into(), ObjectData::Array).into();
let length = vs.len();
for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property(
idx.to_string(),
DataDescriptor::new(
Self::from_json(json, context),
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
),
);
}
new_obj.set_property(
"length",
DataDescriptor::new(length, Attribute::all()),
);
new_obj
}
JSONValue::Object(obj) => {
let new_obj = Value::new_object(context);
for (key, json) in obj.into_iter() {
let value = Self::from_json(json, context);
new_obj.set_property(
key,
DataDescriptor::new(
value,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
),
);
}
new_obj
}
JSONValue::Null => Self::null(),
}
}
pub fn to_json(&self, context: &mut Context) -> Result<JSONValue> {
let to_json = self.get_field("toJSON", context)?;
if to_json.is_function() {
let json_value = context.call(&to_json, self, &[])?;
return json_value.to_json(context);
}
match *self {
Self::Null => Ok(JSONValue::Null),
Self::Boolean(b) => Ok(JSONValue::Bool(b)),
Self::Object(ref obj) => obj.to_json(context),
Self::String(ref str) => Ok(JSONValue::String(str.to_string())),
Self::Rational(num) => {
if num.is_finite() {
Ok(JSONValue::Number(
JSONNumber::from_str(&Number::to_native_string(num))
.expect("invalid number found"),
))
} else {
Ok(JSONValue::Null)
}
}
Self::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))),
Self::BigInt(_) => {
Err(context.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<GcObject> {
match *self {
Self::Object(ref o) => Some(o.clone()),
_ => None,
}
}
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
pub fn as_symbol(&self) -> Option<RcSymbol> {
match self {
Self::Symbol(symbol) => Some(symbol.clone()),
_ => None,
}
}
#[inline]
pub fn is_function(&self) -> bool {
matches!(self, Self::Object(o) if o.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 as_number(&self) -> Option<f64> {
match *self {
Self::Integer(integer) => Some(integer.into()),
Self::Rational(rational) => Some(rational),
_ => None,
}
}
#[inline]
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
#[inline]
pub fn as_string(&self) -> Option<&RcString> {
match self {
Self::String(ref string) => Some(string),
_ => None,
}
}
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(boolean) => Some(*boolean),
_ => None,
}
}
#[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 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<Key>(&self, key: Key) -> bool
where
Key: Into<PropertyKey>,
{
self.as_object()
.map(|mut x| x.remove(&key.into()))
.is_some()
}
pub fn get_property<Key>(&self, key: Key) -> Option<PropertyDescriptor>
where
Key: Into<PropertyKey>,
{
let key = key.into();
let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
match self {
Self::Object(ref object) => {
let property = object.get_own_property(&key);
if property.is_some() {
return property;
}
object.borrow().prototype_instance().get_property(key)
}
_ => None,
}
}
pub fn get_field<K>(&self, key: K, context: &mut Context) -> Result<Self>
where
K: Into<PropertyKey>,
{
let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
if let Self::Object(ref obj) = *self {
obj.clone().get(&key.into(), obj.clone().into(), context)
} else {
Ok(Value::undefined())
}
}
#[inline]
pub fn has_field<K>(&self, key: K) -> bool
where
K: Into<PropertyKey>,
{
let _timer = BoaProfiler::global().start_event("Value::has_field", "value");
self.as_object()
.map(|object| object.has_property(&key.into()))
.unwrap_or(false)
}
#[inline]
pub fn set_field<K, V>(&self, key: K, value: V, context: &mut Context) -> Result<Value>
where
K: Into<PropertyKey>,
V: Into<Value>,
{
let key = key.into();
let value = value.into();
let _timer = BoaProfiler::global().start_event("Value::set_field", "value");
if let Self::Object(ref obj) = *self {
obj.clone()
.set(key, value.clone(), obj.clone().into(), context)?;
}
Ok(value)
}
#[inline]
pub fn set_data(&self, data: ObjectData) {
if let Self::Object(ref obj) = *self {
obj.borrow_mut().data = data;
}
}
#[inline]
pub fn set_property<K, P>(&self, key: K, property: P)
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
if let Some(mut object) = self.as_object() {
object.insert(key.into(), property.into());
}
}
pub fn to_primitive(
&self,
context: &mut Context,
preferred_type: PreferredType,
) -> Result<Value> {
if let Value::Object(obj) = self {
if let Some(exotic_to_prim) =
obj.get_method(context, context.well_known_symbols().to_primitive_symbol())?
{
let hint = match preferred_type {
PreferredType::String => "string",
PreferredType::Number => "number",
PreferredType::Default => "default",
}
.into();
let result = exotic_to_prim.call(&self, &[hint], context)?;
return if result.is_object() {
Err(context.construct_type_error("Symbol.toPrimitive cannot return an object"))
} else {
Ok(result)
};
}
let mut hint = preferred_type;
if hint == PreferredType::Default {
hint = PreferredType::Number;
};
obj.ordinary_to_primitive(context, hint)
} else {
Ok(self.clone())
}
}
pub fn to_bigint(&self, context: &mut Context) -> Result<RcBigInt> {
match self {
Value::Null => Err(context.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(context.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, context)?)),
Value::Boolean(true) => Ok(RcBigInt::from(BigInt::from(1))),
Value::Boolean(false) => Ok(RcBigInt::from(BigInt::from(0))),
Value::Integer(num) => Ok(RcBigInt::from(BigInt::from(*num))),
Value::Rational(num) => {
if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(RcBigInt::from(bigint));
}
Err(context.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
num
)))
}
Value::BigInt(b) => Ok(b.clone()),
Value::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_bigint(context)
}
Value::Symbol(_) => {
Err(context.construct_type_error("cannot convert Symbol to a BigInt"))
}
}
}
#[inline]
pub fn display(&self) -> ValueDisplay<'_> {
ValueDisplay { value: self }
}
pub fn to_string(&self, context: &mut Context) -> Result<RcString> {
match self {
Value::Null => Ok("null".into()),
Value::Undefined => Ok("undefined".into()),
Value::Boolean(boolean) => Ok(boolean.to_string().into()),
Value::Rational(rational) => Ok(Number::to_native_string(*rational).into()),
Value::Integer(integer) => Ok(integer.to_string().into()),
Value::String(string) => Ok(string.clone()),
Value::Symbol(_) => Err(context.construct_type_error("can't convert symbol to string")),
Value::BigInt(ref bigint) => Ok(bigint.to_string().into()),
Value::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::String)?;
primitive.to_string(context)
}
}
}
pub fn to_object(&self, context: &mut Context) -> Result<GcObject> {
match self {
Value::Undefined | Value::Null => {
Err(context.construct_type_error("cannot convert 'null' or 'undefined' to object"))
}
Value::Boolean(boolean) => {
let prototype = context.standard_objects().boolean_object().prototype();
Ok(GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Boolean(*boolean),
)))
}
Value::Integer(integer) => {
let prototype = context.standard_objects().number_object().prototype();
Ok(GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Number(f64::from(*integer)),
)))
}
Value::Rational(rational) => {
let prototype = context.standard_objects().number_object().prototype();
Ok(GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Number(*rational),
)))
}
Value::String(ref string) => {
let prototype = context.standard_objects().string_object().prototype();
let mut object = GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::String(string.clone()),
));
object.insert_property(
PropertyKey::String("length".into()),
Value::from(string.encode_utf16().count()),
Attribute::NON_ENUMERABLE,
);
Ok(object)
}
Value::Symbol(ref symbol) => {
let prototype = context.standard_objects().symbol_object().prototype();
Ok(GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Symbol(symbol.clone()),
)))
}
Value::BigInt(ref bigint) => {
let prototype = context.standard_objects().bigint_object().prototype();
Ok(GcObject::new(Object::with_prototype(
prototype.into(),
ObjectData::BigInt(bigint.clone()),
)))
}
Value::Object(gcobject) => Ok(gcobject.clone()),
}
}
pub fn to_property_key(&self, context: &mut Context) -> Result<PropertyKey> {
Ok(match self {
Value::String(string) => string.clone().into(),
Value::Symbol(symbol) => symbol.clone().into(),
_ => match self.to_primitive(context, PreferredType::String)? {
Value::String(ref string) => string.clone().into(),
Value::Symbol(ref symbol) => symbol.clone().into(),
primitive => primitive.to_string(context)?.into(),
},
})
}
pub fn to_numeric(&self, context: &mut Context) -> Result<Numeric> {
let primitive = self.to_primitive(context, PreferredType::Number)?;
if let Some(bigint) = primitive.as_bigint() {
return Ok(bigint.clone().into());
}
Ok(self.to_number(context)?.into())
}
pub fn to_u32(&self, context: &mut Context) -> Result<u32> {
if let Value::Integer(number) = *self {
return Ok(number as u32);
}
let number = self.to_number(context)?;
Ok(f64_to_uint32(number))
}
pub fn to_i32(&self, context: &mut Context) -> Result<i32> {
if let Value::Integer(number) = *self {
return Ok(number);
}
let number = self.to_number(context)?;
Ok(f64_to_int32(number))
}
pub fn to_index(&self, context: &mut Context) -> Result<usize> {
if self.is_undefined() {
return Ok(0);
}
let integer_index = self.to_integer(context)?;
if integer_index < 0.0 {
return Err(context.construct_range_error("Integer index must be >= 0"));
}
if integer_index > Number::MAX_SAFE_INTEGER {
return Err(
context.construct_range_error("Integer index must be less than 2**(53) - 1")
);
}
Ok(integer_index as usize)
}
pub fn to_length(&self, context: &mut Context) -> Result<usize> {
let len = self.to_integer(context)?;
if len < 0.0 {
return Ok(0);
}
Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
}
pub fn to_integer(&self, context: &mut Context) -> Result<f64> {
let number = self.to_number(context)?;
if !number.is_finite() {
if number.is_nan() {
return Ok(0.0);
}
return Ok(number);
}
Ok(number.trunc() + 0.0)
}
pub fn to_number(&self, context: &mut Context) -> Result<f64> {
match *self {
Value::Null => Ok(0.0),
Value::Undefined => Ok(f64::NAN),
Value::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
Value::String(ref string) => {
if string.trim().is_empty() {
return Ok(0.0);
}
Ok(string.parse().unwrap_or(f64::NAN))
}
Value::Rational(number) => Ok(number),
Value::Integer(integer) => Ok(f64::from(integer)),
Value::Symbol(_) => Err(context.construct_type_error("argument must not be a symbol")),
Value::BigInt(_) => Err(context.construct_type_error("argument must not be a bigint")),
Value::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_number(context)
}
}
}
pub fn to_numeric_number(&self, context: &mut Context) -> Result<f64> {
let primitive = self.to_primitive(context, PreferredType::Number)?;
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
primitive.to_number(context)
}
#[inline]
pub fn require_object_coercible(&self, context: &mut Context) -> Result<&Value> {
if self.is_null_or_undefined() {
Err(context.construct_type_error("cannot convert null or undefined to Object"))
} else {
Ok(self)
}
}
#[inline]
pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> {
if let Self::Object(ref object) = self {
object.to_property_descriptor(context)
} else {
Err(context.construct_type_error("Property description must be an object"))
}
}
pub fn to_integer_or_infinity(&self, context: &mut Context) -> Result<IntegerOrInfinity> {
let number = self.to_number(context)?;
if number.is_nan() || number == 0.0 || number == -0.0 {
Ok(IntegerOrInfinity::Integer(0))
} else if number.is_infinite() && number.is_sign_positive() {
Ok(IntegerOrInfinity::PositiveInfinity)
} else if number.is_infinite() && number.is_sign_negative() {
Ok(IntegerOrInfinity::NegativeInfinity)
} else {
let integer = number.abs().floor();
let integer = integer.min(Number::MAX_SAFE_INTEGER) as i64;
if number < 0.0 {
Ok(IntegerOrInfinity::Integer(-integer))
} else {
Ok(IntegerOrInfinity::Integer(integer))
}
}
}
}
impl Default for Value {
fn default() -> Self {
Self::Undefined
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PreferredType {
String,
Number,
Default,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Numeric {
Number(f64),
BigInt(RcBigInt),
}
impl From<f64> for Numeric {
#[inline]
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<i32> for Numeric {
#[inline]
fn from(value: i32) -> Self {
Self::Number(value.into())
}
}
impl From<i16> for Numeric {
#[inline]
fn from(value: i16) -> Self {
Self::Number(value.into())
}
}
impl From<i8> for Numeric {
#[inline]
fn from(value: i8) -> Self {
Self::Number(value.into())
}
}
impl From<u32> for Numeric {
#[inline]
fn from(value: u32) -> Self {
Self::Number(value.into())
}
}
impl From<u16> for Numeric {
#[inline]
fn from(value: u16) -> Self {
Self::Number(value.into())
}
}
impl From<u8> for Numeric {
#[inline]
fn from(value: u8) -> Self {
Self::Number(value.into())
}
}
impl From<BigInt> for Numeric {
#[inline]
fn from(value: BigInt) -> Self {
Self::BigInt(value.into())
}
}
impl From<RcBigInt> for Numeric {
#[inline]
fn from(value: RcBigInt) -> Self {
Self::BigInt(value)
}
}
impl From<Numeric> for Value {
fn from(value: Numeric) -> Self {
match value {
Numeric::Number(number) => Self::rational(number),
Numeric::BigInt(bigint) => Self::bigint(bigint),
}
}
}