#[cfg(test)]
mod tests;
use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
BigInt, Number,
},
object::{GcObject, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey},
BoaProfiler, Context, Result,
};
use gc::{Finalize, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{
collections::HashSet,
convert::TryFrom,
f64::NAN,
fmt::{self, Display},
str::FromStr,
};
mod conversions;
mod display;
mod equality;
mod hash;
mod operations;
mod rcbigint;
mod rcstring;
mod rcsymbol;
mod r#type;
pub use conversions::*;
pub(crate) use display::display_obj;
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),
}
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(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 from_json(json: JSONValue, interpreter: &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 global_array_prototype = interpreter
.global_object()
.get_field("Array")
.get_field(PROTOTYPE);
let new_obj_obj = Object::with_prototype(global_array_prototype, ObjectData::Array);
let new_obj = Value::object(new_obj_obj);
let length = vs.len();
for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property(
idx.to_string(),
Property::data_descriptor(
Self::from_json(json, interpreter),
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
),
);
}
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.global_object()));
for (key, json) in obj.into_iter() {
let value = Self::from_json(json, interpreter);
new_obj.set_property(
key,
Property::data_descriptor(
value,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
),
);
}
new_obj
}
JSONValue::Null => Self::null(),
}
}
pub fn to_json(&self, interpreter: &mut Context) -> Result<JSONValue> {
let to_json = self.get_field("toJSON");
if to_json.is_function() {
let json_value = interpreter.call(&to_json, self, &[])?;
return json_value.to_json(interpreter);
}
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 keys: Vec<u32> = obj.borrow().index_property_keys().cloned().collect();
keys.sort();
let mut arr: Vec<JSONValue> = Vec::with_capacity(keys.len());
for key in keys {
let value = self.get_field(key);
if value.is_undefined() || value.is_function() || value.is_symbol() {
arr.push(JSONValue::Null);
} else {
arr.push(value.to_json(interpreter)?);
}
}
Ok(JSONValue::Array(arr))
} else {
let mut new_obj = Map::new();
for k in obj.borrow().keys() {
let key = k.clone();
let value = self.get_field(k.to_string());
if !value.is_undefined() && !value.is_function() && !value.is_symbol() {
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
}
pub fn is_global(&self) -> bool {
match self {
Value::Object(object) => matches!(object.borrow().data, ObjectData::Global),
_ => false,
}
}
#[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 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 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_mut()
.map(|mut x| x.remove_property(&key.into()))
.is_some()
}
pub fn get_property<Key>(&self, key: Key) -> Option<Property>
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 object = object.borrow();
let property = object.get_own_property(&key);
if !property.is_none() {
return Some(property);
}
object.prototype_instance().get_property(key)
}
_ => None,
}
}
pub(crate) fn update_property(&self, field: &str, new_property: Property) {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
if let Some(ref mut object) = self.as_object_mut() {
object.insert_property(field, new_property);
}
}
pub fn get_field<K>(&self, key: K) -> Self
where
K: Into<PropertyKey>,
{
let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
let key = key.into();
match self.get_property(key) {
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(),
}
}
#[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) -> 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 {
if let PropertyKey::Index(index) = key {
if obj.borrow().is_array() {
let len = self.get_field("length").as_number().unwrap() as u32;
if len < index + 1 {
self.set_field("length", index + 1);
}
}
}
obj.borrow_mut().set(key, value.clone());
}
value
}
#[inline]
pub fn set_data(&self, data: ObjectData) {
if let Self::Object(ref obj) = *self {
obj.borrow_mut().data = data;
}
}
pub fn set_property<K>(&self, key: K, property: Property) -> Property
where
K: Into<PropertyKey>,
{
if let Some(mut object) = self.as_object_mut() {
object.insert_property(key.into(), property.clone());
}
property
}
pub fn to_primitive(&self, ctx: &mut Context, preferred_type: PreferredType) -> Result<Value> {
if let Value::Object(_) = self {
let mut hint = preferred_type;
if hint == PreferredType::Default {
hint = PreferredType::Number;
};
ctx.ordinary_to_primitive(self, hint)
} else {
Ok(self.clone())
}
}
pub fn to_bigint(&self, ctx: &mut Context) -> Result<RcBigInt> {
match self {
Value::Null => Err(ctx.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(ctx.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, ctx)?)),
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(ctx.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(ctx, PreferredType::Number)?;
primitive.to_bigint(ctx)
}
Value::Symbol(_) => Err(ctx.construct_type_error("cannot convert Symbol to a BigInt")),
}
}
#[inline]
pub fn display(&self) -> ValueDisplay<'_> {
ValueDisplay { value: self }
}
pub fn to_string(&self, ctx: &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(ctx.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(ctx, PreferredType::String)?;
primitive.to_string(ctx)
}
}
}
pub fn to_object(&self, ctx: &mut Context) -> Result<GcObject> {
match self {
Value::Undefined | Value::Null => {
Err(ctx.construct_type_error("cannot convert 'null' or 'undefined' to object"))
}
Value::Boolean(boolean) => {
let proto = ctx
.realm()
.environment
.get_binding_value("Boolean")
.expect("Boolean was not initialized")
.get_field(PROTOTYPE);
Ok(GcObject::new(Object::with_prototype(
proto,
ObjectData::Boolean(*boolean),
)))
}
Value::Integer(integer) => {
let proto = ctx
.realm()
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(GcObject::new(Object::with_prototype(
proto,
ObjectData::Number(f64::from(*integer)),
)))
}
Value::Rational(rational) => {
let proto = ctx
.realm()
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(GcObject::new(Object::with_prototype(
proto,
ObjectData::Number(*rational),
)))
}
Value::String(ref string) => {
let proto = ctx
.realm()
.environment
.get_binding_value("String")
.expect("String was not initialized")
.get_field(PROTOTYPE);
let mut obj = Object::with_prototype(proto, ObjectData::String(string.clone()));
obj.set("length".into(), string.chars().count().into());
Ok(GcObject::new(obj))
}
Value::Symbol(ref symbol) => {
let proto = ctx
.realm()
.environment
.get_binding_value("Symbol")
.expect("Symbol was not initialized")
.get_field(PROTOTYPE);
Ok(GcObject::new(Object::with_prototype(
proto,
ObjectData::Symbol(symbol.clone()),
)))
}
Value::BigInt(ref bigint) => {
let proto = ctx
.realm()
.environment
.get_binding_value("BigInt")
.expect("BigInt was not initialized")
.get_field(PROTOTYPE);
let bigint_obj = GcObject::new(Object::with_prototype(
proto,
ObjectData::BigInt(bigint.clone()),
));
Ok(bigint_obj)
}
Value::Object(gcobject) => Ok(gcobject.clone()),
}
}
pub fn to_property_key(&self, ctx: &mut Context) -> Result<PropertyKey> {
Ok(match self {
Value::String(string) => string.clone().into(),
Value::Symbol(symbol) => symbol.clone().into(),
_ => match self.to_primitive(ctx, PreferredType::String)? {
Value::String(ref string) => string.clone().into(),
Value::Symbol(ref symbol) => symbol.clone().into(),
primitive => primitive.to_string(ctx)?.into(),
},
})
}
pub fn to_numeric(&self, ctx: &mut Context) -> Result<Numeric> {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
if let Some(bigint) = primitive.as_bigint() {
return Ok(bigint.clone().into());
}
Ok(self.to_number(ctx)?.into())
}
pub fn to_u32(&self, ctx: &mut Context) -> Result<u32> {
if let Value::Integer(number) = *self {
return Ok(number as u32);
}
let number = self.to_number(ctx)?;
Ok(f64_to_uint32(number))
}
pub fn to_i32(&self, ctx: &mut Context) -> Result<i32> {
if let Value::Integer(number) = *self {
return Ok(number);
}
let number = self.to_number(ctx)?;
Ok(f64_to_int32(number))
}
pub fn to_index(&self, ctx: &mut Context) -> Result<usize> {
if self.is_undefined() {
return Ok(0);
}
let integer_index = self.to_integer(ctx)?;
if integer_index < 0.0 {
return Err(ctx.construct_range_error("Integer index must be >= 0"));
}
if integer_index > Number::MAX_SAFE_INTEGER {
return Err(ctx.construct_range_error("Integer index must be less than 2**(53) - 1"));
}
Ok(integer_index as usize)
}
pub fn to_length(&self, ctx: &mut Context) -> Result<usize> {
let len = self.to_integer(ctx)?;
if len < 0.0 {
return Ok(0);
}
Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
}
pub fn to_integer(&self, ctx: &mut Context) -> Result<f64> {
let number = self.to_number(ctx)?;
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, ctx: &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(ctx.construct_type_error("argument must not be a symbol")),
Value::BigInt(_) => Err(ctx.construct_type_error("argument must not be a bigint")),
Value::Object(_) => {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
primitive.to_number(ctx)
}
}
}
pub fn to_numeric_number(&self, ctx: &mut Context) -> Result<f64> {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
primitive.to_number(ctx)
}
#[inline]
pub fn require_object_coercible<'a>(&'a self, ctx: &mut Context) -> Result<&'a Value> {
if self.is_null_or_undefined() {
Err(ctx.construct_type_error("cannot convert null or undefined to Object"))
} else {
Ok(self)
}
}
}
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),
}
}
}