use super::{Native, define, install_constructor, link_constructor, methods, string_text};
use crate::heap::HeapError;
use crate::heap::object::ObjectKind;
use crate::runtime::Intrinsic;
use crate::shape::PropertyFlags;
use crate::value::Value;
use crate::vm::native::CallSite;
use crate::vm::object as internal;
use crate::vm::{Cx, Throw, error, number, ops, radix};
pub(super) fn install(cx: &mut Cx<'_>) -> Result<(), HeapError> {
let prototype = cx.realm.intrinsic(Intrinsic::NumberPrototype);
let constructor = install_constructor(cx, Native::NumberConstructor)?;
cx.set_intrinsic(Intrinsic::Number, constructor);
let global = cx.realm.global();
for name in ["parseInt", "parseFloat"] {
let key = cx.heap.property_key(name)?;
let (slot, _) = cx.heap.lookup_own(global, key).expect("a global function");
let function = cx.heap.get_slot(global, slot);
define(cx, constructor, name, function, super::hidden())?;
}
methods(
cx,
constructor,
&[
Native::NumberIsNaN,
Native::NumberIsFinite,
Native::NumberIsInteger,
Native::NumberIsSafeInteger,
],
)?;
let constant = PropertyFlags::data(false, false, false);
for (name, value) in [
("MAX_VALUE", f64::MAX),
("MIN_VALUE", f64::from_bits(1)),
("NaN", f64::NAN),
("NEGATIVE_INFINITY", f64::NEG_INFINITY),
("POSITIVE_INFINITY", f64::INFINITY),
("EPSILON", f64::EPSILON),
("MAX_SAFE_INTEGER", 9_007_199_254_740_991.0),
("MIN_SAFE_INTEGER", -9_007_199_254_740_991.0),
] {
define(cx, constructor, name, Value::number(value), constant)?;
}
methods(
cx,
prototype,
&[
Native::NumberToExponential,
Native::NumberToFixed,
Native::NumberToPrecision,
Native::NumberToString,
Native::NumberToLocaleString,
Native::NumberValueOf,
],
)?;
link_constructor(cx, constructor, prototype)
}
pub(super) fn constructor(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = if site.argc == 0 {
Value::int32(0)
} else {
let value = site.arg(cx, 0);
let numeric = ops::to_numeric(cx, value)?;
if numeric.as_bigint().is_some() {
let text = ops::big(cx.heap, numeric).to_string_radix10();
let units: Vec<u16> = text.encode_utf16().collect();
number::number(number::parse(&units))
} else {
numeric
}
};
if !site.construct {
return Ok(value);
}
let prototype =
internal::prototype_from_constructor(cx, site.new_target, Intrinsic::NumberPrototype)?;
internal::wrap(cx, ObjectKind::NumberWrapper, value, prototype).map(Value::object)
}
pub(crate) fn this_number(cx: &mut Cx<'_>, site: &CallSite) -> Result<f64, Throw> {
let this = site.this(cx);
if let Some(value) = this.as_f64() {
return Ok(value);
}
if let Some(object) = this.as_object()
&& cx.heap.object_kind(object) == ObjectKind::NumberWrapper
{
return Ok(cx.heap.internal_slot(object, 0).as_f64().unwrap());
}
Err(error::type_error(cx, "not a number"))
}
pub(super) fn to_string(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = this_number(cx, &site)?;
let radix = site.arg(cx, 0);
let radix = if radix.is_undefined() {
10.0
} else {
internal::to_integer_or_infinity(cx, radix)?
};
if !(2.0..=36.0).contains(&radix) {
return Err(error::range_error(cx, "radix must be between 2 and 36"));
}
if radix == 10.0 {
return ops::to_string(cx, number::number(value));
}
string_text(cx, &radix::to_string_radix(value, radix as u32))
}
pub(super) fn to_locale_string(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = this_number(cx, &site)?;
ops::to_string(cx, number::number(value))
}
pub(super) fn value_of(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
this_number(cx, &site).map(number::number)
}
fn digits(cx: &mut Cx<'_>, value: Value) -> Result<f64, Throw> {
internal::to_integer_or_infinity(cx, value)
}
pub(super) fn to_fixed(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = this_number(cx, &site)?;
let fraction = site.arg(cx, 0);
let fraction = digits(cx, fraction)?;
if !(0.0..=f64::from(radix::MAX_DIGITS)).contains(&fraction) {
return Err(error::range_error(cx, "invalid number of digits"));
}
string_text(cx, &radix::to_fixed(value, fraction as i32))
}
pub(super) fn to_exponential(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = this_number(cx, &site)?;
let argument = site.arg(cx, 0);
let fraction = digits(cx, argument)?;
if !value.is_finite() {
return string_text(cx, &radix::to_exponential(value, None));
}
if !(0.0..=f64::from(radix::MAX_DIGITS)).contains(&fraction) {
return Err(error::range_error(cx, "invalid number of digits"));
}
let fraction = if argument.is_undefined() {
None
} else {
Some(fraction as i32)
};
string_text(cx, &radix::to_exponential(value, fraction))
}
pub(super) fn to_precision(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = this_number(cx, &site)?;
let argument = site.arg(cx, 0);
if argument.is_undefined() {
return ops::to_string(cx, number::number(value));
}
let precision = digits(cx, argument)?;
if !value.is_finite() {
return ops::to_string(cx, number::number(value));
}
if !(1.0..=f64::from(radix::MAX_DIGITS)).contains(&precision) {
return Err(error::range_error(cx, "invalid number of digits"));
}
string_text(cx, &radix::to_precision(value, precision as i32))
}
pub(super) fn is_finite(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(Value::bool(
site.arg(cx, 0).as_f64().is_some_and(f64::is_finite),
))
}
pub(super) fn is_nan(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(Value::bool(
site.arg(cx, 0).as_f64().is_some_and(f64::is_nan),
))
}
pub(super) fn is_integer(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(Value::bool(site.arg(cx, 0).as_f64().is_some_and(|value| {
value.is_finite() && value.trunc() == value
})))
}
pub(super) fn is_safe_integer(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
Ok(Value::bool(site.arg(cx, 0).as_f64().is_some_and(|value| {
value.is_finite() && value.trunc() == value && value.abs() <= 9_007_199_254_740_991.0
})))
}