#![allow(clippy::collapsible_if, clippy::collapsible_match)]
use crate::core::{Expr, JSObjectDataPtr, Value, evaluate_expr, new_js_object_data, obj_get_key_value, obj_set_key_value, to_primitive};
use crate::error::JSError;
use crate::unicode::utf8_to_utf16;
pub fn make_number_object() -> Result<JSObjectDataPtr, JSError> {
let number_obj = new_js_object_data();
obj_set_key_value(&number_obj, &"MAX_VALUE".into(), Value::Number(f64::MAX))?;
obj_set_key_value(&number_obj, &"MIN_VALUE".into(), Value::Number(f64::MIN_POSITIVE))?;
obj_set_key_value(&number_obj, &"NaN".into(), Value::Number(f64::NAN))?;
obj_set_key_value(&number_obj, &"POSITIVE_INFINITY".into(), Value::Number(f64::INFINITY))?;
obj_set_key_value(&number_obj, &"NEGATIVE_INFINITY".into(), Value::Number(f64::NEG_INFINITY))?;
obj_set_key_value(&number_obj, &"EPSILON".into(), Value::Number(f64::EPSILON))?;
obj_set_key_value(&number_obj, &"MAX_SAFE_INTEGER".into(), Value::Number(9007199254740991.0))?;
obj_set_key_value(&number_obj, &"MIN_SAFE_INTEGER".into(), Value::Number(-9007199254740991.0))?;
obj_set_key_value(&number_obj, &"isNaN".into(), Value::Function("Number.isNaN".to_string()))?;
obj_set_key_value(&number_obj, &"isFinite".into(), Value::Function("Number.isFinite".to_string()))?;
obj_set_key_value(&number_obj, &"isInteger".into(), Value::Function("Number.isInteger".to_string()))?;
obj_set_key_value(
&number_obj,
&"isSafeInteger".into(),
Value::Function("Number.isSafeInteger".to_string()),
)?;
obj_set_key_value(&number_obj, &"parseFloat".into(), Value::Function("Number.parseFloat".to_string()))?;
obj_set_key_value(&number_obj, &"parseInt".into(), Value::Function("Number.parseInt".to_string()))?;
let number_prototype = new_js_object_data();
obj_set_key_value(
&number_prototype,
&"toString".into(),
Value::Function("Number.prototype.toString".to_string()),
)?;
obj_set_key_value(
&number_prototype,
&"valueOf".into(),
Value::Function("Number.prototype.valueOf".to_string()),
)?;
obj_set_key_value(
&number_prototype,
&"toLocaleString".into(),
Value::Function("Number.prototype.toLocaleString".to_string()),
)?;
obj_set_key_value(
&number_prototype,
&"toExponential".into(),
Value::Function("Number.prototype.toExponential".to_string()),
)?;
obj_set_key_value(
&number_prototype,
&"toFixed".into(),
Value::Function("Number.prototype.toFixed".to_string()),
)?;
obj_set_key_value(
&number_prototype,
&"toPrecision".into(),
Value::Function("Number.prototype.toPrecision".to_string()),
)?;
obj_set_key_value(&number_obj, &"prototype".into(), Value::Object(number_prototype))?;
Ok(number_obj)
}
pub(crate) fn number_constructor(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
match arg_val {
Value::Number(n) => Ok(Value::Number(n)),
Value::String(s) => {
let str_val = String::from_utf16_lossy(&s);
match str_val.trim().parse::<f64>() {
Ok(n) => Ok(Value::Number(n)),
Err(_) => Ok(Value::Number(f64::NAN)),
}
}
Value::Boolean(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })),
Value::Object(obj) => {
let prim = to_primitive(&Value::Object(obj.clone()), "number", env)?;
match prim {
Value::Number(n) => Ok(Value::Number(n)),
Value::String(s) => {
let str_val = String::from_utf16_lossy(&s);
match str_val.trim().parse::<f64>() {
Ok(n) => Ok(Value::Number(n)),
Err(_) => Ok(Value::Number(f64::NAN)),
}
}
Value::Boolean(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })),
_ => Ok(Value::Number(f64::NAN)),
}
}
_ => Ok(Value::Number(f64::NAN)),
}
} else {
Ok(Value::Number(0.0)) }
}
pub fn handle_number_method(method: &str, args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
match method {
"isNaN" => {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
if let Value::Number(n) = arg_val {
Ok(Value::Boolean(n.is_nan()))
} else {
Ok(Value::Boolean(false))
}
} else {
Err(raise_eval_error!("Number.isNaN expects exactly one argument"))
}
}
"isFinite" => {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
if let Value::Number(n) = arg_val {
Ok(Value::Boolean(n.is_finite()))
} else {
Ok(Value::Boolean(false))
}
} else {
Err(raise_eval_error!("Number.isFinite expects exactly one argument"))
}
}
"isInteger" => {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
if let Value::Number(n) = arg_val {
Ok(Value::Boolean(n.fract() == 0.0 && n.is_finite()))
} else {
Ok(Value::Boolean(false))
}
} else {
Err(raise_eval_error!("Number.isInteger expects exactly one argument"))
}
}
"isSafeInteger" => {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
if let Value::Number(n) = arg_val {
let is_int = n.fract() == 0.0 && n.is_finite();
let is_safe = (-9007199254740991.0..=9007199254740991.0).contains(&n);
Ok(Value::Boolean(is_int && is_safe))
} else {
Ok(Value::Boolean(false))
}
} else {
Err(raise_eval_error!("Number.isSafeInteger expects exactly one argument"))
}
}
"parseFloat" => {
if args.len() == 1 {
let arg_val = evaluate_expr(env, &args[0])?;
match arg_val {
Value::String(s) => {
let str_val = String::from_utf16_lossy(&s);
match str_val.trim().parse::<f64>() {
Ok(n) => Ok(Value::Number(n)),
Err(_) => Ok(Value::Number(f64::NAN)),
}
}
Value::Number(n) => Ok(Value::Number(n)),
_ => Ok(Value::Number(f64::NAN)),
}
} else {
Err(raise_eval_error!("Number.parseFloat expects exactly one argument"))
}
}
"parseInt" => {
if !args.is_empty() {
let arg_val = evaluate_expr(env, &args[0])?;
let radix = if args.len() >= 2 {
let radix_val = evaluate_expr(env, &args[1])?;
if let Value::Number(r) = radix_val { r as u32 } else { 10 }
} else {
10
};
match arg_val {
Value::String(s) => {
let str_val = String::from_utf16_lossy(&s);
let trimmed = str_val.trim();
if trimmed.is_empty() {
Ok(Value::Number(f64::NAN))
} else {
let mut end_pos = 0;
let chars: Vec<char> = trimmed.chars().collect();
let mut start_pos = 0;
if !chars.is_empty() && (chars[0] == '+' || chars[0] == '-') {
start_pos = 1;
}
for (i, &c) in chars.iter().enumerate().skip(start_pos) {
let digit_val = match c {
'0'..='9' => (c as u32) - ('0' as u32),
'a'..='z' => (c as u32) - ('a' as u32) + 10,
'A'..='Z' => (c as u32) - ('A' as u32) + 10,
_ => break,
};
if digit_val >= radix {
break;
}
end_pos = i + 1;
}
if end_pos > start_pos || (start_pos == 1 && end_pos >= 1) {
let int_part = &trimmed[..end_pos];
if radix == 10 {
match int_part.parse::<i64>() {
Ok(n) => Ok(Value::Number(n as f64)),
Err(_) => Ok(Value::Number(f64::NAN)),
}
} else {
match i64::from_str_radix(int_part, radix) {
Ok(n) => Ok(Value::Number(n as f64)),
Err(_) => Ok(Value::Number(f64::NAN)),
}
}
} else {
Ok(Value::Number(f64::NAN))
}
}
}
Value::Number(n) => Ok(Value::Number(n.trunc())),
_ => Ok(Value::Number(f64::NAN)),
}
} else {
Err(raise_eval_error!("Number.parseInt expects at least one argument"))
}
}
_ => Err(raise_eval_error!(format!("Number.{method} is not implemented"))),
}
}
pub fn handle_number_instance_method(n: &f64, method: &str, args: &[Expr], _env: &JSObjectDataPtr) -> Result<Value, JSError> {
match method {
"toString" => {
if args.is_empty() {
Ok(Value::String(utf8_to_utf16(&n.to_string())))
} else {
let msg = format!("toString method expects no arguments, got {}", args.len());
Err(raise_eval_error!(msg))
}
}
"valueOf" => {
if args.is_empty() {
Ok(Value::Number(*n))
} else {
Err(raise_eval_error!(format!(
"valueOf method expects no arguments, got {}",
args.len()
)))
}
}
"toLocaleString" => {
if args.is_empty() {
Ok(Value::String(utf8_to_utf16(&n.to_string())))
} else {
let msg = format!("toLocaleString method expects no arguments, got {}", args.len());
Err(raise_eval_error!(msg))
}
}
"toExponential" => {
let fraction_digits = if !args.is_empty() {
match evaluate_expr(_env, &args[0])? {
Value::Number(d) => Some(d as usize),
_ => None,
}
} else {
None
};
match fraction_digits {
Some(d) => Ok(Value::String(utf8_to_utf16(&format!("{:.1$e}", n, d)))),
None => Ok(Value::String(utf8_to_utf16(&format!("{:e}", n)))),
}
}
"toFixed" => {
let digits = if !args.is_empty() {
match evaluate_expr(_env, &args[0])? {
Value::Number(d) => d as usize,
_ => 0,
}
} else {
0
};
if digits > 100 {
return Err(raise_eval_error!("toFixed() digits argument must be between 0 and 100"));
}
Ok(Value::String(utf8_to_utf16(&format!("{:.1$}", n, digits))))
}
"toPrecision" => {
let precision = if !args.is_empty() {
match evaluate_expr(_env, &args[0])? {
Value::Number(p) => Some(p as usize),
Value::Undefined => None,
_ => None,
}
} else {
None
};
match precision {
Some(p) => {
if !(1..=100).contains(&p) {
return Err(raise_eval_error!("toPrecision() argument must be between 1 and 100"));
}
if n.is_nan() || n.is_infinite() {
return Ok(Value::String(utf8_to_utf16(&n.to_string())));
}
let s_exp = format!("{:.1$e}", n, p - 1);
let parts: Vec<&str> = s_exp.split('e').collect();
let exponent: i32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
if exponent < -6 || exponent >= p as i32 {
Ok(Value::String(utf8_to_utf16(&s_exp)))
} else {
let digits_after_decimal = p as i32 - 1 - exponent;
let width = if digits_after_decimal < 0 {
0
} else {
digits_after_decimal as usize
};
Ok(Value::String(utf8_to_utf16(&format!("{:.1$}", n, width))))
}
}
None => Ok(Value::String(utf8_to_utf16(&n.to_string()))),
}
}
_ => Err(raise_eval_error!(format!("Number.prototype.{method} is not implemented"))),
}
}
pub fn handle_number_object_method(
obj_map: &JSObjectDataPtr,
method: &str,
args: &[Expr],
env: &JSObjectDataPtr,
) -> Result<Value, JSError> {
if let Some(value_val) = obj_get_key_value(obj_map, &"__value__".into())? {
if let Value::Number(n) = *value_val.borrow() {
handle_number_instance_method(&n, method, args, env)
} else {
Err(raise_eval_error!("Invalid __value__ for Number instance"))
}
} else {
Err(raise_eval_error!("__value__ not found on Number instance"))
}
}
pub fn box_number_and_get_property(n: f64, prop: &str, env: &JSObjectDataPtr) -> Result<Value, JSError> {
let number_obj = new_js_object_data();
obj_set_key_value(&number_obj, &"__value__".into(), Value::Number(n))?;
crate::core::set_internal_prototype_from_constructor(&number_obj, env, "Number")?;
if let Some(val) = obj_get_key_value(&number_obj, &prop.into())? {
Ok(val.borrow().clone())
} else {
Ok(Value::Undefined)
}
}