use crate::{
ecmascript::{
Agent, BigInt, ExceptionType, Function, InternalMethods, JsResult, NonRevokedProxy, Number,
Numeric, Object, PreferredType, Primitive, String, Value, string_to_big_int,
string_to_number, to_numeric_primitive, to_primitive, validate_non_revoked_proxy,
},
engine::{Bindable, GcScope, NoGcScope, Scopable},
heap::{ArenaAccess, PrimitiveHeapAccess},
};
#[cfg(feature = "regexp")]
use crate::{
ecmascript::{PropertyKey, get, to_boolean},
heap::WellKnownSymbols,
};
pub(crate) fn require_object_coercible<'gc>(
agent: &mut Agent,
argument: Value,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
if argument.is_undefined() || argument.is_null() {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Argument cannot be converted into an object",
gc,
))
} else {
Ok(argument.bind(gc))
}
}
pub(crate) fn is_array<'a, 'gc>(
agent: &mut Agent,
argument: impl Into<Value<'a>>,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, bool> {
let argument = argument.into().bind(gc);
match argument {
Value::Array(_) => Ok(true),
Value::Proxy(proxy) => {
let NonRevokedProxy { target, handler: _ } =
validate_non_revoked_proxy(agent, proxy, gc)?;
is_array(agent, target, gc)
}
_ => Ok(false),
}
}
pub(crate) fn is_callable<'a, 'b>(
argument: impl TryInto<Function<'b>>,
_: NoGcScope<'a, '_>,
) -> Option<Function<'a>> {
if let Ok(f) = argument.try_into() {
Some(f.unbind())
} else {
None
}
}
pub(crate) fn is_constructor<'a>(
agent: &mut Agent,
constructor: impl TryInto<Function<'a>>,
) -> Option<Function<'a>> {
let Ok(constructor) = constructor.try_into() else {
return None;
};
if constructor.is_constructor(agent) {
Some(constructor)
} else {
None
}
}
#[cfg(feature = "regexp")]
pub(crate) fn is_reg_exp<'a>(
agent: &mut Agent,
argument: Value,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let argument = argument.bind(gc.nogc());
let Ok(argument) = Object::try_from(argument) else {
return Ok(false);
};
let is_native_reg_exp = matches!(argument, Object::RegExp(_));
let matcher = get(
agent,
argument.unbind(),
PropertyKey::Symbol(WellKnownSymbols::Match.into()),
gc,
)?;
if !matcher.is_undefined() {
return Ok(to_boolean(agent, matcher));
}
if is_native_reg_exp {
return Ok(true);
}
Ok(false)
}
#[cfg(not(feature = "regexp"))]
#[inline(always)]
pub(crate) fn is_reg_exp<'a>(
_agent: &mut Agent,
_argument: Value,
_gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
Ok(false)
}
pub(crate) fn is_extensible<'a>(
agent: &mut Agent,
o: Object,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
o.internal_is_extensible(agent, gc)
}
pub(crate) fn is_same_type<'a, V1: Copy + Into<Value<'a>>, V2: Copy + Into<Value<'a>>>(
x: V1,
y: V2,
) -> bool {
(x.into().is_undefined() && y.into().is_undefined())
|| (x.into().is_null() && y.into().is_null())
|| (x.into().is_boolean() && y.into().is_boolean())
|| (x.into().is_string() && y.into().is_string())
|| (x.into().is_symbol() && y.into().is_symbol())
|| (x.into().is_number() && y.into().is_number())
|| (x.into().is_bigint() && y.into().is_bigint())
|| (x.into().is_object() && y.into().is_object())
}
pub(crate) fn is_integral_number<'a>(
agent: &mut Agent,
argument: impl Copy + Into<Value<'a>>,
) -> bool {
let argument = argument.into();
if let Value::Integer(_) = argument {
return true;
}
let Ok(argument) = Number::try_from(argument) else {
return false;
};
if !argument.is_finite_(agent) {
return false;
}
argument.to_real(agent).fract() == 0.0
}
pub(crate) fn same_value<'a, V1: Copy + Into<Value<'a>>, V2: Copy + Into<Value<'a>>>(
agent: &impl PrimitiveHeapAccess,
x: V1,
y: V2,
) -> bool {
if !is_same_type(x, y) {
return false;
}
if let (Ok(x), Ok(y)) = (Number::try_from(x.into()), Number::try_from(y.into())) {
return Number::same_value_(agent, x, y);
}
let x: Value = x.into();
let y: Value = y.into();
same_value_non_number(agent, x, y)
}
pub(crate) fn same_value_zero<'a>(
agent: &impl PrimitiveHeapAccess,
x: impl Copy + Into<Value<'a>>,
y: impl Copy + Into<Value<'a>>,
) -> bool {
let (x, y) = (Into::<Value>::into(x), Into::<Value>::into(y));
if !is_same_type(x, y) {
return false;
}
if let (Ok(x), Ok(y)) = (Number::try_from(x), Number::try_from(y)) {
return Number::same_value_zero_(agent, x, y);
}
same_value_non_number(agent, x, y)
}
pub(crate) fn same_value_non_number<'a, T: Copy + Into<Value<'a>>>(
agent: &impl PrimitiveHeapAccess,
x: T,
y: T,
) -> bool {
let x: Value = x.into();
let y: Value = y.into();
debug_assert!(is_same_type(x, y));
if x.is_null() || x.is_undefined() {
return true;
}
if let (Ok(x), Ok(y)) = (BigInt::try_from(x), BigInt::try_from(y)) {
return BigInt::equal(agent, x.unbind(), y.unbind());
}
if let (Ok(x), Ok(y)) = (String::try_from(x), String::try_from(y)) {
return String::eq_(agent, x, y);
}
if x.is_boolean() {
return x.is_true() == y.is_true();
}
x == y
}
pub(crate) fn is_less_than<'a, const LEFT_FIRST: bool>(
agent: &mut Agent,
x: impl Into<Value<'a>> + Copy,
y: impl Into<Value<'a>> + Copy,
mut gc: GcScope,
) -> JsResult<'a, Option<bool>> {
let (px, py, gc) = match (Primitive::try_from(x.into()), Primitive::try_from(y.into())) {
(Ok(px), Ok(py)) => {
let gc = gc.into_nogc();
(px.bind(gc), py.bind(gc), gc)
}
(Ok(px), Err(_)) => {
let px = px.scope(agent, gc.nogc());
let py = to_primitive(agent, y.into(), Some(PreferredType::Number), gc.reborrow())
.unbind()?;
let gc = gc.into_nogc();
let px = px.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
(Err(_), Ok(py)) => {
let py = py.scope(agent, gc.nogc());
let px = to_primitive(agent, x.into(), Some(PreferredType::Number), gc.reborrow())
.unbind()?;
let gc = gc.into_nogc();
let py = py.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
(Err(_), Err(_)) => {
if LEFT_FIRST {
let y: Value = y.into();
let y = y.scope(agent, gc.nogc());
let px = to_primitive(agent, x.into(), Some(PreferredType::Number), gc.reborrow())
.unbind()?
.scope(agent, gc.nogc());
let py = to_primitive(
agent,
y.get(agent),
Some(PreferredType::Number),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let px = px.get(agent);
(px.bind(gc), py.bind(gc), gc)
} else {
let x: Value = x.into();
let x = x.scope(agent, gc.nogc());
let py = to_primitive(agent, y.into(), Some(PreferredType::Number), gc.reborrow())
.unbind()?
.scope(agent, gc.nogc());
let px = to_primitive(
agent,
x.get(agent),
Some(PreferredType::Number),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let py = py.get(agent);
(px.bind(gc), py.bind(gc), gc)
}
}
};
if px.is_string() && py.is_string() {
let sx = String::try_from(px).unwrap();
let sy = String::try_from(py).unwrap();
if let (Some(sx), Some(sy)) = (sx.as_str_(agent), sy.as_str_(agent)) {
Ok(Some(sx < sy))
} else {
let lx = sx.utf16_len_(agent);
let ly = sy.utf16_len_(agent);
let l = lx.min(ly);
let mut sx = sx.as_wtf8_(agent).to_ill_formed_utf16();
let mut sy = sy.as_wtf8_(agent).to_ill_formed_utf16();
for _ in 0..l {
let cx = sx.next().unwrap();
let cy = sy.next().unwrap();
match cx.cmp(&cy) {
std::cmp::Ordering::Less => {
return Ok(Some(true));
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
return Ok(Some(false));
}
}
}
Ok(Some(lx < ly))
}
}
else {
if px.is_bigint() && py.is_string() {
let Ok(px) = BigInt::try_from(px) else {
unreachable!()
};
let Ok(py) = String::try_from(py) else {
unreachable!()
};
let ny = string_to_big_int(agent, py, gc).unbind()?.bind(gc);
return Ok(Some(BigInt::less_than(agent, px, ny)));
}
if px.is_string() && py.is_bigint() {
let Ok(px) = String::try_from(px) else {
unreachable!()
};
let Ok(py) = BigInt::try_from(py) else {
unreachable!()
};
let nx = string_to_big_int(agent, px, gc).unbind()?.bind(gc);
return Ok(Some(BigInt::less_than(agent, nx, py)));
}
let nx = to_numeric_primitive(agent, px, gc).unbind()?.bind(gc);
let ny = to_numeric_primitive(agent, py, gc).unbind()?.bind(gc);
if is_same_type(nx, ny) {
if let Ok(nx) = Number::try_from(nx) {
let ny = Number::try_from(ny).unwrap();
return Ok(Number::less_than(agent, nx, ny));
}
else {
let nx = BigInt::try_from(nx).unwrap();
let ny = BigInt::try_from(ny).unwrap();
return Ok(Some(BigInt::less_than(agent, nx, ny)));
}
}
assert!(nx.is_bigint() && ny.is_number() || nx.is_number() && ny.is_bigint());
if nx.is_nan(agent) || ny.is_nan(agent) {
return Ok(None);
}
if nx.is_neg_infinity(agent) || ny.is_pos_infinity(agent) {
return Ok(Some(true));
}
if nx.is_pos_infinity(agent) || ny.is_neg_infinity(agent) {
return Ok(Some(false));
}
Ok(Some(match (nx, ny) {
(Numeric::Number(x), Numeric::Number(y)) => x != y && x.get(agent) < y.get(agent),
(Numeric::Number(x), Numeric::Integer(y)) => *x.get(agent) < y.into_i64() as f64,
(Numeric::Number(x), Numeric::SmallF64(y)) => *x.get(agent) < y.into_f64(),
(Numeric::Integer(x), Numeric::Number(y)) => (x.into_i64() as f64) < *y.get(agent),
(Numeric::Integer(x), Numeric::Integer(y)) => x.into_i64() < y.into_i64(),
(Numeric::Number(x), Numeric::BigInt(y)) => y.get(agent).ge(x.get(agent)),
(Numeric::Number(x), Numeric::SmallBigInt(y)) => *x.get(agent) < y.into_i64() as f64,
(Numeric::Integer(x), Numeric::SmallF64(y)) => (x.into_i64() as f64) < y.into_f64(),
(Numeric::Integer(x), Numeric::BigInt(y)) => y.get(agent).ge(&x.into_i64()),
(Numeric::Integer(x), Numeric::SmallBigInt(y)) => x.into_i64() < y.into_i64(),
(Numeric::SmallF64(x), Numeric::Number(y)) => x.into_f64() < *y.get(agent),
(Numeric::SmallF64(x), Numeric::Integer(y)) => x.into_f64() < y.into_i64() as f64,
(Numeric::SmallF64(x), Numeric::SmallF64(y)) => x.into_f64() < y.into_f64(),
(Numeric::SmallF64(x), Numeric::BigInt(y)) => y.get(agent).ge(&x.into_f64()),
(Numeric::SmallF64(x), Numeric::SmallBigInt(y)) => x.into_f64() < y.into_i64() as f64,
(Numeric::BigInt(x), Numeric::Number(y)) => x.get(agent).le(y.get(agent)),
(Numeric::BigInt(x), Numeric::Integer(y)) => x.get(agent).le(&y.into_i64()),
(Numeric::BigInt(x), Numeric::SmallF64(y)) => x.get(agent).le(&y.into_f64()),
(Numeric::BigInt(x), Numeric::BigInt(y)) => x.get(agent).data < y.get(agent).data,
(Numeric::BigInt(x), Numeric::SmallBigInt(y)) => x.get(agent).le(&y.into_i64()),
(Numeric::SmallBigInt(x), Numeric::Number(y)) => (x.into_i64() as f64) < *y.get(agent),
(Numeric::SmallBigInt(x), Numeric::Integer(y)) => x.into_i64() < y.into_i64(),
(Numeric::SmallBigInt(x), Numeric::SmallF64(y)) => (x.into_i64() as f64) < y.into_f64(),
(Numeric::SmallBigInt(x), Numeric::BigInt(y)) => y.get(agent).ge(&x.into_i64()),
(Numeric::SmallBigInt(x), Numeric::SmallBigInt(y)) => x.into_i64() < y.into_i64(),
}))
}
}
pub(crate) fn is_loosely_equal<'a>(
agent: &mut Agent,
x: impl Into<Value<'a>> + Copy,
y: impl Into<Value<'a>> + Copy,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let x: Value = x.into();
let y: Value = y.into();
if is_same_type(x, y) {
return Ok(is_strictly_equal(agent, x, y));
}
if (x.is_null() && y.is_undefined()) || (x.is_undefined() && y.is_null()) {
return Ok(true);
}
if let (Ok(x), Ok(y)) = (Number::try_from(x), String::try_from(y)) {
let gc = gc.into_nogc();
let y = string_to_number(agent, y, gc);
return Ok(Number::equal_(agent, x.bind(gc), y.bind(gc)));
}
if let (Ok(x), Ok(y)) = (String::try_from(x), Number::try_from(y)) {
let gc = gc.into_nogc();
let x = string_to_number(agent, x, gc);
return Ok(Number::equal_(agent, x.bind(gc), y.bind(gc)));
}
if let (Ok(x), Ok(y)) = (BigInt::try_from(x), String::try_from(y)) {
let gc = gc.into_nogc();
if let Ok(n) = string_to_big_int(agent, y, gc) {
return Ok(BigInt::equal(agent, x.bind(gc), n.bind(gc)));
} else {
return Ok(false);
}
}
if let (Ok(x), Ok(y)) = (String::try_from(x), BigInt::try_from(y)) {
let gc = gc.into_nogc();
if let Ok(n) = string_to_big_int(agent, x, gc) {
return Ok(BigInt::equal(agent, y.bind(gc), n.bind(gc)));
} else {
return Ok(false);
}
}
if let Ok(x) = bool::try_from(x) {
let x = if x { 1 } else { 0 };
return Ok(is_loosely_equal(agent, x, y, gc).unwrap());
}
if let Ok(y) = bool::try_from(y) {
let y = if y { 1 } else { 0 };
return Ok(is_loosely_equal(agent, x, y, gc).unwrap());
}
if (x.is_string() || x.is_number() || x.is_bigint() || x.is_symbol()) && y.is_object() {
let x = x.scope(agent, gc.nogc());
let y = to_primitive(agent, y, None, gc.reborrow())
.unbind()?
.bind(gc.nogc());
return Ok(is_loosely_equal(agent, x.get(agent), y.unbind(), gc).unwrap());
}
if x.is_object() && (y.is_string() || y.is_number() || y.is_bigint() || y.is_symbol()) {
let y = y.scope(agent, gc.nogc());
let x = to_primitive(agent, x, None, gc.reborrow())
.unbind()?
.bind(gc.nogc());
return Ok(is_loosely_equal(agent, x.unbind(), y.get(agent), gc).unwrap());
}
if let Some((a, b)) = if let (Ok(x), Ok(y)) = (BigInt::try_from(x), Number::try_from(y)) {
Some((x, y))
} else if let (Ok(x), Ok(y)) = (Number::try_from(x), BigInt::try_from(y)) {
Some((y, x))
} else {
None
} {
if !b.is_finite_(agent) {
return Ok(false);
}
let b = b.to_real(agent);
return Ok(match a {
BigInt::BigInt(heap_big_int) => heap_big_int.get(agent) == &b,
BigInt::SmallBigInt(small_big_int) => small_big_int.into_i64() as f64 == b,
});
}
Ok(false)
}
pub(crate) fn is_strictly_equal<'a>(
agent: &Agent,
x: impl Into<Value<'a>> + Copy,
y: impl Into<Value<'a>> + Copy,
) -> bool {
let (x, y) = (x.into(), y.into());
if !is_same_type(x, y) {
return false;
}
if let (Ok(x), Ok(y)) = (Number::try_from(x), Number::try_from(y)) {
return Number::equal_(agent, x, y);
}
same_value_non_number(agent, x, y)
}