use crate::error::JsError;
use crate::gc::Gc;
use crate::interpreter::Interpreter;
use crate::prelude::{String, ToString, Vec, format, math};
use crate::value::{ExoticObject, Guarded, JsObject, JsString, JsValue, PropertyKey};
pub fn init_number_prototype(interp: &mut Interpreter) {
let proto = interp.number_prototype.clone();
interp.register_method(&proto, "toFixed", number_to_fixed, 1);
interp.register_method(&proto, "toString", number_to_string, 1);
interp.register_method(&proto, "toPrecision", number_to_precision, 1);
interp.register_method(&proto, "toExponential", number_to_exponential, 1);
interp.register_method(&proto, "valueOf", number_value_of, 0);
}
pub fn number_constructor_fn(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let num_val = args
.first()
.cloned()
.unwrap_or(JsValue::Number(0.0))
.to_number();
if let JsValue::Object(obj) = &this {
let is_new_call = {
let borrowed = obj.borrow();
if let Some(ref proto) = borrowed.prototype {
core::ptr::eq(
&*proto.borrow() as *const _,
&*interp.number_prototype.borrow() as *const _,
)
} else {
false
}
};
if is_new_call {
obj.borrow_mut().exotic = ExoticObject::Number(num_val);
return Ok(Guarded::unguarded(this));
}
}
Ok(Guarded::unguarded(JsValue::Number(num_val)))
}
pub fn number_value_of(
interp: &mut Interpreter,
this: JsValue,
_args: &[JsValue],
) -> Result<Guarded, JsError> {
let num_val = get_number_value(interp, &this)?;
Ok(Guarded::unguarded(JsValue::Number(num_val)))
}
fn get_number_value(interp: &mut Interpreter, this: &JsValue) -> Result<f64, JsError> {
match this {
JsValue::Number(n) => Ok(*n),
JsValue::Object(obj) => {
let to_fixed_key = interp.property_key("toFixed");
let borrowed = obj.borrow();
match borrowed.exotic {
ExoticObject::Number(n) => Ok(n),
ExoticObject::Ordinary => {
if borrowed.get_property(&to_fixed_key).is_some() {
Ok(0.0)
} else {
Err(JsError::type_error(
"Number.prototype method called on incompatible receiver",
))
}
}
_ => Err(JsError::type_error(
"Number.prototype method called on incompatible receiver",
)),
}
}
_ => Err(JsError::type_error(
"Number.prototype method called on incompatible receiver",
)),
}
}
pub fn create_number_constructor(interp: &mut Interpreter) -> Gc<JsObject> {
let constructor = interp.create_native_function("Number", number_constructor_fn, 1);
interp.register_method(&constructor, "isNaN", number_is_nan, 1);
interp.register_method(&constructor, "isFinite", number_is_finite, 1);
interp.register_method(&constructor, "isInteger", number_is_integer, 1);
interp.register_method(&constructor, "isSafeInteger", number_is_safe_integer, 1);
interp.register_method(&constructor, "parseFloat", number_parse_float, 1);
interp.register_method(&constructor, "parseInt", number_parse_int, 2);
let max_value_key = PropertyKey::String(interp.intern("MAX_VALUE"));
let min_value_key = PropertyKey::String(interp.intern("MIN_VALUE"));
let max_safe_key = PropertyKey::String(interp.intern("MAX_SAFE_INTEGER"));
let min_safe_key = PropertyKey::String(interp.intern("MIN_SAFE_INTEGER"));
let nan_key = PropertyKey::String(interp.intern("NaN"));
let pos_inf_key = PropertyKey::String(interp.intern("POSITIVE_INFINITY"));
let neg_inf_key = PropertyKey::String(interp.intern("NEGATIVE_INFINITY"));
let epsilon_key = PropertyKey::String(interp.intern("EPSILON"));
{
let mut c = constructor.borrow_mut();
c.set_property(max_value_key, JsValue::Number(f64::MAX));
c.set_property(min_value_key, JsValue::Number(f64::MIN_POSITIVE));
c.set_property(max_safe_key, JsValue::Number(9007199254740991.0));
c.set_property(min_safe_key, JsValue::Number(-9007199254740991.0));
c.set_property(nan_key, JsValue::Number(f64::NAN));
c.set_property(pos_inf_key, JsValue::Number(f64::INFINITY));
c.set_property(neg_inf_key, JsValue::Number(f64::NEG_INFINITY));
c.set_property(epsilon_key, JsValue::Number(f64::EPSILON));
}
let proto_key = PropertyKey::String(interp.intern("prototype"));
constructor
.borrow_mut()
.set_property(proto_key, JsValue::Object(interp.number_prototype.clone()));
let constructor_key = PropertyKey::String(interp.intern("constructor"));
interp
.number_prototype
.borrow_mut()
.set_property(constructor_key, JsValue::Object(constructor.clone()));
constructor
}
pub fn number_parse_float(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let s = interp.to_js_string(&arg).to_string();
let trimmed = s.trim_start();
let result = trimmed.parse::<f64>().unwrap_or(f64::NAN);
Ok(Guarded::unguarded(JsValue::Number(result)))
}
pub fn number_parse_int(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let s = interp.to_js_string(&arg).to_string();
let radix = args.get(1).map(|v| v.to_number() as i32).unwrap_or(10);
let trimmed = s.trim_start();
let radix = if radix == 0 {
10
} else if !(2..=36).contains(&radix) {
return Ok(Guarded::unguarded(JsValue::Number(f64::NAN)));
} else {
radix
};
let result = i64::from_str_radix(trimmed, radix as u32)
.map(|n| n as f64)
.unwrap_or(f64::NAN);
Ok(Guarded::unguarded(JsValue::Number(result)))
}
pub fn number_is_nan(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
match args.first() {
Some(JsValue::Number(n)) => Ok(Guarded::unguarded(JsValue::Boolean(n.is_nan()))),
_ => Ok(Guarded::unguarded(JsValue::Boolean(false))),
}
}
pub fn number_is_finite(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
match args.first() {
Some(JsValue::Number(n)) => Ok(Guarded::unguarded(JsValue::Boolean(n.is_finite()))),
_ => Ok(Guarded::unguarded(JsValue::Boolean(false))),
}
}
pub fn number_is_integer(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
match args.first() {
Some(JsValue::Number(n)) => {
let is_int = n.is_finite() && math::trunc(*n) == *n;
Ok(Guarded::unguarded(JsValue::Boolean(is_int)))
}
_ => Ok(Guarded::unguarded(JsValue::Boolean(false))),
}
}
pub fn number_is_safe_integer(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
const MAX_SAFE: f64 = 9007199254740991.0;
match args.first() {
Some(JsValue::Number(n)) => {
let is_safe = n.is_finite() && math::trunc(*n) == *n && n.abs() <= MAX_SAFE;
Ok(Guarded::unguarded(JsValue::Boolean(is_safe)))
}
_ => Ok(Guarded::unguarded(JsValue::Boolean(false))),
}
}
pub fn number_to_fixed(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let n = get_number_value(interp, &this)?;
let digits = args.first().map(|v| v.to_number() as i32).unwrap_or(0);
if !(0..=100).contains(&digits) {
return Err(JsError::range_error(
"toFixed() digits argument must be between 0 and 100",
));
}
let result = format!("{:.prec$}", n, prec = digits as usize);
Ok(Guarded::unguarded(JsValue::String(JsString::from(result))))
}
fn format_number_js(n: f64) -> String {
if n.is_nan() {
"NaN".to_string()
} else if n.is_infinite() {
if n.is_sign_positive() {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else {
format!("{}", n)
}
}
pub fn number_to_string(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let n = get_number_value(interp, &this)?;
let radix = args.first().map(|v| v.to_number() as i32).unwrap_or(10);
if !(2..=36).contains(&radix) {
return Err(JsError::range_error(
"toString() radix must be between 2 and 36",
));
}
if radix == 10 {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format_number_js(n),
))));
}
if !n.is_finite() || math::fract(n) != 0.0 {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format_number_js(n),
))));
}
let int_val = n as i64;
let result = match radix {
2 => format!("{:b}", int_val.abs()),
8 => format!("{:o}", int_val.abs()),
16 => format!("{:x}", int_val.abs()),
_ => {
const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
let mut num = int_val.abs();
let mut result = String::new();
while num > 0 {
let digit_idx = (num % radix as i64) as usize;
if let Some(&ch) = DIGITS.get(digit_idx) {
result.insert(0, ch as char);
}
num /= radix as i64;
}
if result.is_empty() {
result = "0".to_string();
}
result
}
};
let result = if int_val < 0 {
format!("-{}", result)
} else {
result
};
Ok(Guarded::unguarded(JsValue::String(JsString::from(result))))
}
pub fn number_to_precision(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let n = get_number_value(interp, &this)?;
if args.is_empty() || matches!(args.first(), Some(JsValue::Undefined)) {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format_number_js(n),
))));
}
let precision = args.first().map(|v| v.to_number() as i32).unwrap_or(1);
if !(1..=100).contains(&precision) {
return Err(JsError::range_error(
"toPrecision() argument must be between 1 and 100",
));
}
if !n.is_finite() {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format_number_js(n),
))));
}
let result = format!("{:.prec$e}", n, prec = (precision - 1) as usize);
let parts: Vec<&str> = result.split('e').collect();
if let [mantissa_str, exp_str] = parts.as_slice() {
let mantissa = mantissa_str.parse::<f64>().unwrap_or(0.0);
let exp: i32 = exp_str.parse().unwrap_or(0);
if exp >= 0 && exp < precision {
let decimals = precision - 1 - exp;
if decimals >= 0 {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format!("{:.prec$}", n, prec = decimals as usize),
))));
}
} else if (-4..0).contains(&exp) {
let decimals = precision - 1 - exp;
if (0..=100).contains(&decimals) {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format!("{:.prec$}", n, prec = decimals as usize),
))));
}
}
let exp_sign = if exp >= 0 { "+" } else { "" };
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format!("{}e{}{}", mantissa, exp_sign, exp),
))));
}
Ok(Guarded::unguarded(JsValue::String(JsString::from(
format!("{}", n),
))))
}
pub fn number_to_exponential(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let n = get_number_value(interp, &this)?;
if !n.is_finite() {
return Ok(Guarded::unguarded(JsValue::String(JsString::from(
format_number_js(n),
))));
}
let digits = args.first().map(|v| v.to_number() as i32).unwrap_or(6);
if !(0..=100).contains(&digits) {
return Err(JsError::range_error(
"toExponential() argument must be between 0 and 100",
));
}
let result = format!("{:.prec$e}", n, prec = digits as usize);
let result = result.replace("e", "e+").replace("e+-", "e-");
Ok(Guarded::unguarded(JsValue::String(JsString::from(result))))
}