use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
Number,
},
error::JsNativeError,
js_string,
value::{JsSymbol, Numeric, PreferredType},
Context, JsBigInt, JsResult, JsValue,
};
impl JsValue {
pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => x
.checked_add(*y)
.map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x + y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)),
(Self::String(ref x), Self::String(ref y)) => Self::from(js_string!(x, y)),
(_, _) => match (
self.to_primitive(context, PreferredType::Default)?,
other.to_primitive(context, PreferredType::Default)?,
) {
(Self::String(ref x), ref y) => Self::from(js_string!(x, &y.to_string(context)?)),
(ref x, Self::String(ref y)) => Self::from(js_string!(&x.to_string(context)?, y)),
(x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::add(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message(
"cannot mix BigInt and other types, use explicit conversions",
)
.into())
}
},
},
})
}
pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => x
.checked_sub(*y)
.map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x - y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a - b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => x
.checked_mul(*y)
.map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x * y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a * b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => x
.checked_div(*y)
.filter(|div| *y * div == *x)
.map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x / y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
if y.is_zero() {
return Err(JsNativeError::range()
.with_message("BigInt division by zero")
.into());
}
Self::new(JsBigInt::div(x, y))
}
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a / b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
if y.is_zero() {
return Err(JsNativeError::range()
.with_message("BigInt division by zero")
.into());
}
Self::new(JsBigInt::div(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => {
if *y == 0 {
Self::nan()
} else {
match x % *y {
rem if rem == 0 && *x < 0 => Self::new(-0.0),
rem => Self::new(rem),
}
}
}
(Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)),
(Self::Integer(x), Self::Rational(y)) => {
let x = f64::from(*x);
Self::new((x % y).copysign(x))
}
(Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
if y.is_zero() {
return Err(JsNativeError::range()
.with_message("BigInt division by zero")
.into());
}
Self::new(JsBigInt::rem(x, y))
}
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
if y.is_zero() {
return Err(JsNativeError::range()
.with_message("BigInt division by zero")
.into());
}
Self::new(JsBigInt::rem(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
#[allow(clippy::float_cmp)]
pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => u32::try_from(*y)
.ok()
.and_then(|y| x.checked_pow(y))
.map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => {
if x.abs() == 1.0 && y.is_infinite() {
Self::nan()
} else {
Self::new(x.powf(*y))
}
}
(Self::Integer(x), Self::Rational(y)) => {
if x.wrapping_abs() == 1 && y.is_infinite() {
Self::nan()
} else {
Self::new(f64::from(*x).powf(*y))
}
}
(Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
if a.abs() == 1.0 && b.is_infinite() {
Self::nan()
} else {
Self::new(a.powf(b))
}
}
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?),
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new(x & y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) & f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) & f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitand(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new(x | y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) | f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) | f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitor(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) ^ f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) ^ f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitxor(x, y))
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_left(a, b)?),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::shift_left(x, y)?)
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::shift_right(a, b)?),
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::shift_right(x, y)?)
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => {
Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32))
}
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
}
(Numeric::BigInt(_), Numeric::BigInt(_)) => {
return Err(JsNativeError::typ()
.with_message("BigInts have no unsigned right shift, use >> instead")
.into());
}
(_, _) => {
return Err(JsNativeError::typ()
.with_message("cannot mix BigInt and other types, use explicit conversions")
.into());
}
},
})
}
pub fn instance_of(&self, target: &Self, context: &mut Context) -> JsResult<bool> {
if !target.is_object() {
return Err(JsNativeError::typ()
.with_message(format!(
"right-hand side of 'instanceof' should be an object, got `{}`",
target.type_of()
))
.into());
}
match target.get_method(JsSymbol::has_instance(), context)? {
Some(instance_of_handler) => {
Ok(instance_of_handler
.call(target, std::slice::from_ref(self), context)?
.to_boolean())
}
None if target.is_callable() => {
Self::ordinary_has_instance(target, self, context)
}
None => {
Err(JsNativeError::typ()
.with_message("right-hand side of 'instanceof' is not callable")
.into())
}
}
}
pub fn neg(&self, context: &mut Context) -> JsResult<Self> {
Ok(match *self {
Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN),
Self::Object(_) => Self::new(
self.to_numeric_number(context)
.map_or(f64::NAN, std::ops::Neg::neg),
),
Self::String(ref str) => Self::new(-str.to_number()),
Self::Rational(num) => Self::new(-num),
Self::Integer(0) => Self::new(-f64::from(0)),
Self::Integer(num) => Self::new(-num),
Self::Boolean(true) => Self::new(1),
Self::Boolean(false) | Self::Null => Self::new(0),
Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)),
})
}
#[inline]
pub fn not(&self) -> JsResult<bool> {
Ok(!self.to_boolean())
}
pub fn abstract_relation(
&self,
other: &Self,
left_first: bool,
context: &mut Context,
) -> JsResult<AbstractRelation> {
Ok(match (self, other) {
(Self::Integer(x), Self::Integer(y)) => (x < y).into(),
(Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(),
(_, _) => {
let (px, py) = if left_first {
let px = self.to_primitive(context, PreferredType::Number)?;
let py = other.to_primitive(context, PreferredType::Number)?;
(px, py)
} else {
let py = other.to_primitive(context, PreferredType::Number)?;
let px = self.to_primitive(context, PreferredType::Number)?;
(px, py)
};
match (px, py) {
(Self::String(ref x), Self::String(ref y)) => (x < y).into(),
(Self::BigInt(ref x), Self::String(ref y)) => y
.to_big_int()
.map_or(AbstractRelation::Undefined, |y| (*x < y).into()),
(Self::String(ref x), Self::BigInt(ref y)) => x
.to_big_int()
.map_or(AbstractRelation::Undefined, |x| (x < *y).into()),
(px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(Numeric::BigInt(ref x), Numeric::Number(y)) => {
if y.is_nan() {
return Ok(AbstractRelation::Undefined);
}
if y.is_infinite() {
return Ok(y.is_sign_positive().into());
}
if let Ok(y) = JsBigInt::try_from(y) {
return Ok((*x < y).into());
}
(x.to_f64() < y).into()
}
(Numeric::Number(x), Numeric::BigInt(ref y)) => {
if x.is_nan() {
return Ok(AbstractRelation::Undefined);
}
if x.is_infinite() {
return Ok(x.is_sign_negative().into());
}
if let Ok(x) = JsBigInt::try_from(x) {
return Ok((x < *y).into());
}
(x < y.to_f64()).into()
}
},
}
}
})
}
#[inline]
pub fn lt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match self.abstract_relation(other, true, context)? {
AbstractRelation::True => Ok(true),
AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
}
}
#[inline]
pub fn le(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match other.abstract_relation(self, false, context)? {
AbstractRelation::False => Ok(true),
AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
}
}
#[inline]
pub fn gt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match other.abstract_relation(self, false, context)? {
AbstractRelation::True => Ok(true),
AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
}
}
#[inline]
pub fn ge(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match self.abstract_relation(other, true, context)? {
AbstractRelation::False => Ok(true),
AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AbstractRelation {
True,
False,
Undefined,
}
impl From<bool> for AbstractRelation {
#[inline]
fn from(value: bool) -> Self {
if value {
Self::True
} else {
Self::False
}
}
}