use crate::host::HostFunction;
use crate::object::function::JSFunction;
use crate::runtime::context::JSContext;
use crate::runtime::vm::VM;
use crate::value::JSValue;
pub fn register_math_builtins(ctx: &mut JSContext) {
ctx.register_builtin("math_abs", HostFunction::new("abs", 1, math_abs));
ctx.register_builtin("math_floor", HostFunction::new("floor", 1, math_floor));
ctx.register_builtin("math_ceil", HostFunction::new("ceil", 1, math_ceil));
ctx.register_builtin("math_round", HostFunction::new("round", 1, math_round));
ctx.register_builtin("math_sqrt", HostFunction::new("sqrt", 1, math_sqrt));
ctx.register_builtin("math_max", HostFunction::new("max", 2, math_max));
ctx.register_builtin("math_min", HostFunction::new("min", 2, math_min));
ctx.register_builtin("math_pow", HostFunction::new("pow", 2, math_pow));
ctx.register_builtin("math_log", HostFunction::new("log", 1, math_log));
ctx.register_builtin("math_random", HostFunction::new("random", 0, math_random));
ctx.register_builtin("math_trunc", HostFunction::new("trunc", 1, math_trunc));
ctx.register_builtin("math_sign", HostFunction::new("sign", 1, math_sign));
ctx.register_builtin("math_cbrt", HostFunction::new("cbrt", 1, math_cbrt));
ctx.register_builtin("math_clz32", HostFunction::new("clz32", 1, math_clz32));
ctx.register_builtin("math_imul", HostFunction::new("imul", 2, math_imul));
ctx.register_builtin("math_fround", HostFunction::new("fround", 1, math_fround));
ctx.register_builtin("math_hypot", HostFunction::new("hypot", 2, math_hypot));
ctx.register_builtin("math_expm1", HostFunction::new("expm1", 1, math_expm1));
ctx.register_builtin("math_log1p", HostFunction::new("log1p", 1, math_log1p));
ctx.register_builtin("math_log10", HostFunction::new("log10", 1, math_log10));
ctx.register_builtin("math_log2", HostFunction::new("log2", 1, math_log2));
ctx.register_builtin("math_sin", HostFunction::new("sin", 1, math_sin));
ctx.register_builtin("math_cos", HostFunction::new("cos", 1, math_cos));
ctx.register_builtin("math_tan", HostFunction::new("tan", 1, math_tan));
ctx.register_builtin("math_exp", HostFunction::new("exp", 1, math_exp));
ctx.register_builtin("math_asin", HostFunction::new("asin", 1, math_asin));
ctx.register_builtin("math_acos", HostFunction::new("acos", 1, math_acos));
ctx.register_builtin("math_atan", HostFunction::new("atan", 1, math_atan));
ctx.register_builtin("math_atan2", HostFunction::new("atan2", 2, math_atan2));
ctx.register_builtin(
"math_f16round",
HostFunction::new("f16round", 1, math_f16round),
);
ctx.register_builtin(
"math_sumPrecise",
HostFunction::new("sumPrecise", 1, math_sum_precise),
);
ctx.register_builtin("math_sinh", HostFunction::new("sinh", 1, math_sinh));
ctx.register_builtin("math_cosh", HostFunction::new("cosh", 1, math_cosh));
ctx.register_builtin("math_tanh", HostFunction::new("tanh", 1, math_tanh));
ctx.register_builtin("math_asinh", HostFunction::new("asinh", 1, math_asinh));
ctx.register_builtin("math_acosh", HostFunction::new("acosh", 1, math_acosh));
ctx.register_builtin("math_atanh", HostFunction::new("atanh", 1, math_atanh));
}
fn call_js_function(
ctx: &mut JSContext,
func: JSValue,
this_value: JSValue,
args: &[JSValue],
) -> Result<JSValue, String> {
if !func.is_function() {
return Err("not a function".to_string());
}
let ptr = func.get_ptr();
let js_func = unsafe { &*(ptr as *const JSFunction) };
if js_func.is_builtin() {
if let Some(builtin_fn) = js_func.builtin_func {
return Ok(ctx.call_builtin_direct(builtin_fn, args));
} else if let Some(builtin_atom) = js_func.builtin_atom {
let builtin_name = ctx.get_atom_str(builtin_atom).to_string();
return Ok(ctx.call_builtin(&builtin_name, args));
}
return Ok(JSValue::undefined());
}
if let Some(ref _rb) = js_func.bytecode {
let vm_ptr = ctx
.get_register_vm_ptr()
.ok_or("No VM associated with context")?;
let vm = unsafe { &mut *(vm_ptr as *mut VM) };
vm.call_function_with_this(ctx, func, this_value, args)
} else {
Ok(JSValue::undefined())
}
}
fn to_number_with_valueof(ctx: &mut JSContext, value: &JSValue) -> f64 {
if value.is_int() {
return value.get_int() as f64;
}
if value.is_float() {
return value.get_float();
}
if value.is_bool() {
return if value.get_bool() { 1.0 } else { 0.0 };
}
if value.is_null() {
return 0.0;
}
if value.is_undefined() {
return f64::NAN;
}
if value.is_object() {
let obj = value.as_object();
let value_of_atom = ctx.common_atoms.value_of;
if let Some(func) = obj.get(value_of_atom) {
if func.is_function() {
let result = call_js_function(ctx, func, *value, &[]);
if let Ok(result) = result {
return to_number_with_valueof(ctx, &result);
}
}
}
let to_string_atom = ctx.common_atoms.to_string;
if let Some(func) = obj.get(to_string_atom) {
if func.is_function() {
let result = call_js_function(ctx, func, *value, &[]);
if let Ok(result) = result {
if result.is_string() {
let s = ctx.get_atom_str(result.get_atom());
if let Ok(n) = s.parse::<f64>() {
return n;
}
}
}
}
}
}
f64::NAN
}
fn math_abs(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let arg = if args.len() >= 2 {
&args[1]
} else if !args.is_empty() {
&args[0]
} else {
return JSValue::new_float(f64::NAN);
};
if arg.is_int() {
let v = arg.get_int();
JSValue::new_int(if v < 0 { -v } else { v })
} else {
JSValue::new_float(arg.get_float().abs())
}
}
fn math_floor(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let arg = &args[arg_idx];
if arg.is_int() {
return *arg;
}
if arg.is_bigint() {
return *arg;
}
JSValue::new_float(arg.get_float().floor())
}
fn math_ceil(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
JSValue::new_float(args[arg_idx].get_float().ceil())
}
fn math_round(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
JSValue::new_float(args[arg_idx].get_float().round())
}
fn math_sqrt(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
JSValue::new_float(args[arg_idx].get_float().sqrt())
}
fn math_max(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NEG_INFINITY);
}
let mut max = args[0].to_number();
for arg in args.iter().skip(1) {
let v = arg.to_number();
if v.is_nan() {
max = v;
} else if v > max {
max = v;
} else if v == 0.0 && max == 0.0 && !v.is_sign_negative() && max.is_sign_negative() {
max = v;
}
}
JSValue::new_float(max)
}
fn math_min(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::INFINITY);
}
let mut min = args[0].to_number();
for arg in args.iter().skip(1) {
let v = arg.to_number();
if v.is_nan() {
min = v;
} else if v < min {
min = v;
} else if v == 0.0 && min == 0.0 && v.is_sign_negative() && !min.is_sign_negative() {
min = v;
}
}
JSValue::new_float(min)
}
fn math_pow(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.len() < 2 {
return JSValue::new_float(f64::NAN);
}
let x = args[0].get_float();
let y = args[1].get_float();
if y.is_nan() {
return JSValue::new_float(f64::NAN);
}
if x.abs() == 1.0 && y.is_infinite() {
return JSValue::new_float(f64::NAN);
}
JSValue::new_float(x.powf(y))
}
fn math_random(_ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
JSValue::new_float(rand_simple())
}
fn math_trunc(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.trunc())
}
fn math_sign(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
if v.is_nan() || v == 0.0 {
JSValue::new_float(v)
} else if v > 0.0 {
JSValue::new_int(1)
} else {
JSValue::new_int(-1)
}
}
fn math_cbrt(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.cbrt())
}
fn math_clz32(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_int(32);
}
let uint32 = to_uint32(args[0].get_float());
let bits = uint32.leading_zeros();
JSValue::new_int(bits as i64)
}
fn to_uint32(f: f64) -> u32 {
if f.is_nan() || f.is_infinite() || f == 0.0 {
0
} else {
let int = f.trunc();
let int128 = int as i128;
let modulo = ((int128 % (1i128 << 32)) + (1i128 << 32)) % (1i128 << 32);
modulo as u32
}
}
fn to_int32(f: f64) -> i32 {
let uint32 = to_uint32(f);
if uint32 >= 0x80000000 {
(uint32 as i64 - 0x100000000i64) as i32
} else {
uint32 as i32
}
}
fn math_imul(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let a = if args.len() >= 1 {
to_int32(args[0].get_float())
} else {
0i32
};
let b = if args.len() >= 2 {
to_int32(args[1].get_float())
} else {
0i32
};
JSValue::new_int(a.wrapping_mul(b) as i64)
}
fn math_fround(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float() as f32;
JSValue::new_float(v as f64)
}
fn math_f16round(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
}
let x = args[0].to_number();
if x.is_nan() {
return JSValue::new_float(f64::NAN);
}
if x == 0.0 || x.is_infinite() {
return JSValue::new_float(x);
}
let f16 = f16_from_f64(x);
JSValue::new_float(f16)
}
fn f16_from_f64(x: f64) -> f64 {
let sign_bit = if x < 0.0 { 0x8000u16 } else { 0u16 };
let abs_x = x.abs();
if abs_x < 2.0f64.powi(-25) {
return if x < 0.0 { -0.0 } else { 0.0 };
}
if abs_x == 2.0f64.powi(-25) {
return if x < 0.0 { -0.0 } else { 0.0 };
}
if abs_x < 2.0f64.powi(-24) {
let result = 2.0f64.powi(-24);
return if x < 0.0 { -result } else { result };
}
if abs_x > 65520.0 {
return if x < 0.0 {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
let bits = abs_x.to_bits();
let exp64 = ((bits >> 52) & 0x7FF) as i32;
let mant64 = bits & 0x000F_FFFF_FFFF_FFFF;
let (exp_unbiased, full_mant): (i32, u64) = if exp64 == 0 {
let lz = mant64.leading_zeros() as i32;
let shift = lz - 11;
(-1021 - shift, mant64 << (shift + 1))
} else {
(exp64 - 1023, (1u64 << 52) | mant64)
};
let f16_exp_biased = exp_unbiased + 15;
if f16_exp_biased < 1 {
let sub_shift = (1 - f16_exp_biased) as u32;
if sub_shift > 10 {
return if x < 0.0 { -0.0 } else { 0.0 };
}
let shift = 42 + sub_shift;
let half_bit = 1u64 << (shift - 1);
let lost = full_mant & ((1u64 << shift) - 1);
let sig = full_mant >> shift;
let rounded = sig
+ if lost > half_bit || (lost == half_bit && sig & 1 != 0) {
1u64
} else {
0u64
};
let f16_bits = sign_bit | rounded as u16;
f16_to_f64(f16_bits)
} else {
let shift = 42u32;
let half_bit = 1u64 << (shift - 1);
let lost = full_mant & ((1u64 << shift) - 1);
let sig = (full_mant >> shift) as u16;
let rounded = sig
+ if lost > half_bit || (lost == half_bit && sig & 1 != 0) {
1u16
} else {
0u16
};
if rounded >= 0x800 {
let new_exp = f16_exp_biased + 1;
if new_exp >= 31 {
let f16_bits = sign_bit | 0x7C00;
return f16_to_f64(f16_bits);
}
let f16_bits = sign_bit | ((new_exp as u16) << 10);
f16_to_f64(f16_bits)
} else {
let f16_bits = sign_bit | ((f16_exp_biased as u16) << 10) | (rounded & 0x3FF);
f16_to_f64(f16_bits)
}
}
}
fn f16_to_f64(bits: u16) -> f64 {
let sign = (bits >> 15) & 1;
let exp = ((bits >> 10) & 0x1F) as i32;
let mant = (bits & 0x3FF) as f64;
let val = if exp == 0 {
if mant == 0.0 {
0.0
} else {
2.0f64.powi(-14) * mant / 1024.0
}
} else if exp == 31 {
if mant == 0.0 { f64::INFINITY } else { f64::NAN }
} else {
2.0f64.powi(exp - 15) * (1.0 + mant / 1024.0)
};
if sign != 0 { -val } else { val }
}
fn math_hypot(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(0.0);
}
let mut has_inf = false;
let mut sum_squares = 0.0;
for arg in args {
let v = match crate::builtins::global::js_to_number_value(ctx, arg) {
Ok(v) => v,
Err(()) => {
return JSValue::new_float(f64::NAN);
}
};
if v.is_infinite() {
has_inf = true;
}
sum_squares += v * v;
}
if has_inf {
return JSValue::new_float(f64::INFINITY);
}
JSValue::new_float(sum_squares.sqrt())
}
fn math_expm1(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.exp_m1())
}
fn math_log(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
}
let v = to_number_with_valueof(ctx, &args[0]);
JSValue::new_float(v.ln())
}
fn math_log1p(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.ln_1p())
}
fn math_log10(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.log10())
}
fn math_log2(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.log2())
}
fn math_sinh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.sinh())
}
fn math_cosh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.cosh())
}
fn math_tanh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.tanh())
}
fn math_asinh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.asinh())
}
fn math_acos(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.acos())
}
fn math_acosh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.acosh())
}
fn math_sin(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.sin())
}
fn math_exp(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
}
let v = args[0].get_float();
JSValue::new_float(v.exp())
}
fn math_cos(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.cos())
}
fn math_tan(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.tan())
}
fn math_asin(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.asin())
}
fn math_atan(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.atan())
}
fn math_atan2(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
let args = if !args.is_empty() && args[0].is_object() {
&args[1..]
} else {
args
};
if args.len() < 2 {
return JSValue::new_float(f64::NAN);
}
let y = args[0].get_float();
let x = args[1].get_float();
JSValue::new_float(y.atan2(x))
}
fn math_atanh(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
return JSValue::new_float(f64::NAN);
};
let arg_idx = 0;
let v = args[arg_idx].get_float();
JSValue::new_float(v.atanh())
}
fn rand_simple() -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.subsec_nanos();
(nanos as f64) / (u32::MAX as f64)
}
fn throw_type_error_math(ctx: &mut JSContext, msg: &str) -> JSValue {
let mut err =
crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
err.set(
ctx.common_atoms.name,
JSValue::new_string(ctx.intern("TypeError")),
);
err.set(
ctx.common_atoms.message,
JSValue::new_string(ctx.intern(msg)),
);
if let Some(proto) = ctx.get_type_error_prototype() {
err.prototype = Some(proto);
}
let ptr = Box::into_raw(Box::new(err)) as usize;
ctx.runtime_mut().gc_heap_mut().track(ptr);
ctx.pending_exception = Some(JSValue::new_object(ptr));
JSValue::undefined()
}
fn math_sum_precise(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
if args.is_empty() {
let mut err =
crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
err.set(
ctx.common_atoms.name,
JSValue::new_string(ctx.intern("TypeError")),
);
err.set(
ctx.common_atoms.message,
JSValue::new_string(ctx.intern("sumPrecise requires 1 argument")),
);
if let Some(proto) = ctx.get_type_error_prototype() {
err.prototype = Some(proto);
}
let ptr = Box::into_raw(Box::new(err)) as usize;
ctx.runtime_mut().gc_heap_mut().track(ptr);
ctx.pending_exception = Some(JSValue::new_object(ptr));
return JSValue::undefined();
}
let iterable = &args[0];
let mut values: Vec<f64> = Vec::new();
let sym_iter_val =
crate::builtins::symbol::get_or_create_well_known_symbol(ctx, "Symbol.iterator");
let has_iterator = iterable.is_object() && {
let sym_key = crate::runtime::atom::Atom(0x40000000 | sym_iter_val.get_symbol_id());
let obj = unsafe { &*(iterable.get_ptr() as *const crate::object::JSObject) };
obj.get(sym_key).map_or(false, |v| v.is_function())
};
if has_iterator {
let vm_ptr = match ctx.get_register_vm_ptr() {
Some(p) => p,
None => return JSValue::new_float(f64::NAN),
};
let vm = unsafe { &mut *(vm_ptr as *mut VM) };
let iter_fn = {
let sym_key = crate::runtime::atom::Atom(0x40000000 | sym_iter_val.get_symbol_id());
let obj = unsafe { &*(iterable.get_ptr() as *const crate::object::JSObject) };
obj.get(sym_key).unwrap()
};
let iterator = match vm.call_function_with_this(ctx, iter_fn, iterable.clone(), &[]) {
Ok(v) => v,
Err(_) => return JSValue::undefined(),
};
if ctx.pending_exception.is_some() {
return JSValue::undefined();
}
if !iterator.is_object() {
return throw_type_error_math(ctx, "iterator is not an object");
}
let next_atom = ctx.intern("next");
let next_fn = {
let mut next_val = None;
let mut current: Option<*const crate::object::JSObject> =
Some(iterator.get_ptr() as *const _);
while let Some(ptr) = current {
let obj = unsafe { &*ptr };
if let Some(v) = obj.get(next_atom) {
next_val = Some(v);
break;
}
current = obj.prototype.map(|p| p as *const _);
}
match next_val {
Some(v) if v.is_function() => v,
_ => return throw_type_error_math(ctx, "iterator has no next method"),
}
};
loop {
let result = match vm.call_function_with_this(ctx, next_fn, iterator.clone(), &[]) {
Ok(v) => v,
Err(_) => return JSValue::undefined(),
};
if ctx.pending_exception.is_some() {
return JSValue::undefined();
}
if !result.is_object() {
return throw_type_error_math(ctx, "iterator result is not an object");
}
let result_obj = unsafe { &*(result.get_ptr() as *const crate::object::JSObject) };
let done_atom = ctx.intern("done");
let value_atom = ctx.intern("value");
let done = result_obj
.get(done_atom)
.map_or(false, |v| v.is_bool() && v.get_bool());
if done {
break;
}
let value = match result_obj.get(value_atom) {
Some(v) => v,
None => JSValue::undefined(),
};
if value.is_object() || value.is_bigint() || value.is_symbol() || value.is_function() {
let return_atom = ctx.intern("return");
let iter_obj = unsafe { &*(iterator.get_ptr() as *const crate::object::JSObject) };
if let Some(ret_fn) = iter_obj.get(return_atom) {
if ret_fn.is_function() {
let _ = vm.call_function_with_this(ctx, ret_fn, iterator.clone(), &[]);
}
}
return throw_type_error_math(ctx, "element is not a number");
}
values.push(value.to_number());
}
} else if iterable.is_object() {
let obj = unsafe { &*(iterable.get_ptr() as *const crate::object::JSObject) };
let length_prop = obj.get(ctx.common_atoms.length);
let len = if let Some(v) = length_prop {
if v.is_int() {
v.get_int() as usize
} else if v.is_float() {
v.get_float() as usize
} else {
0
}
} else {
return throw_type_error_math(ctx, "argument is not iterable");
};
for i in 0..len {
let val = if obj.is_dense_array() {
use crate::object::array_obj::JSArrayObject;
let arr = unsafe { &*(iterable.get_ptr() as *const JSArrayObject) };
if i < arr.elements.len() {
arr.elements[i]
} else {
JSValue::undefined()
}
} else {
let atom = crate::runtime::atom::Atom(i as u32);
obj.get(atom).unwrap_or(JSValue::undefined())
};
if val.is_object() || val.is_bigint() {
return throw_type_error_math(ctx, "element is not a number");
}
values.push(val.to_number());
}
} else {
return throw_type_error_math(ctx, "argument is not iterable");
}
if values.is_empty() {
return JSValue::new_float(-0.0);
}
let mut has_nan = false;
let mut pos_inf = false;
let mut neg_inf = false;
let mut finite_values: Vec<f64> = Vec::new();
for &n in &values {
if n.is_nan() {
has_nan = true;
} else if n == f64::INFINITY {
pos_inf = true;
} else if n == f64::NEG_INFINITY {
neg_inf = true;
} else if n > 0.0 || (n == 0.0 && !n.is_sign_negative()) {
finite_values.push(n);
} else {
finite_values.push(n);
}
}
if has_nan {
return JSValue::new_float(f64::NAN);
}
if pos_inf && neg_inf {
return JSValue::new_float(f64::NAN);
}
if pos_inf {
return JSValue::new_float(f64::INFINITY);
}
if neg_inf {
return JSValue::new_float(f64::NEG_INFINITY);
}
if finite_values.is_empty() {
return JSValue::new_float(-0.0);
}
if finite_values.len() == 1 {
return JSValue::new_float(finite_values[0]);
}
let result = exact_float_sum(&finite_values);
if result == 0.0 {
let has_positive = finite_values
.iter()
.any(|&x| x > 0.0 || (x == 0.0 && !x.is_sign_negative()));
let has_negative = finite_values
.iter()
.any(|&x| x < 0.0 || (x == 0.0 && x.is_sign_negative()));
if has_positive && has_negative {
return JSValue::new_float(0.0);
}
if finite_values
.iter()
.all(|&x| x == 0.0 && x.is_sign_negative())
{
return JSValue::new_float(-0.0);
}
let has_pos_zero = finite_values
.iter()
.any(|&x| x == 0.0 && !x.is_sign_negative());
if has_pos_zero {
return JSValue::new_float(0.0);
}
return JSValue::new_float(-0.0);
}
JSValue::new_float(result)
}
fn decompose_float(f: f64) -> (i64, i32) {
let bits = f.to_bits();
let sign = if (bits >> 63) != 0 { -1i64 } else { 1i64 };
let exp_bits = ((bits >> 52) & 0x7FF) as i32;
let mant = (bits & 0xFFFFFFFFFFFFF) as i64;
if exp_bits == 0 {
(sign * mant, -1074)
} else {
(sign * (mant + (1i64 << 52)), exp_bits - 1023 - 52)
}
}
fn exact_float_sum(values: &[f64]) -> f64 {
let comps: Vec<(i64, i32)> = values.iter().map(|&v| decompose_float(v)).collect();
let min_exp = comps.iter().map(|&(_, e)| e).min().unwrap_or(0);
let max_exp = comps.iter().map(|&(_, e)| e).max().unwrap_or(0);
let total_shift = (max_exp - min_exp) as usize;
let limbs_needed = (total_shift + 63) / 64 + 2;
let mut pos_limbs = vec![0u64; limbs_needed];
let mut neg_limbs = vec![0u64; limbs_needed];
for &(mant, exp) in &comps {
let shift = (exp - min_exp) as usize;
let limb_shift = shift / 64;
let bit_shift = shift % 64;
let abs_mant = mant.unsigned_abs();
if mant >= 0 {
add_to_bigint(&mut pos_limbs, abs_mant, limb_shift, bit_shift);
} else {
add_to_bigint(&mut neg_limbs, abs_mant, limb_shift, bit_shift);
}
}
let (result_sign, result_limbs) = if cmp_bigint(&pos_limbs, &neg_limbs) >= 0 {
(false, sub_bigint(&pos_limbs, &neg_limbs))
} else {
(true, sub_bigint(&neg_limbs, &pos_limbs))
};
let first_nonzero = result_limbs.iter().rposition(|&l| l != 0).unwrap_or(0);
if first_nonzero == 0 && result_limbs[0] == 0 {
return if result_sign { -0.0 } else { 0.0 };
}
let total_bits =
first_nonzero * 64 + (64 - result_limbs[first_nonzero].leading_zeros() as usize);
let exp = min_exp + total_bits as i32 - 1;
let biased_exp = exp + 1023;
if biased_exp >= 0x7FF {
return if result_sign {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
if biased_exp <= 0 {
return if result_sign { -0.0 } else { 0.0 };
}
let top_limb = first_nonzero;
let bits_in_top = 64 - result_limbs[top_limb].leading_zeros() as usize;
let top_val = result_limbs[top_limb];
let next_val = if top_limb > 0 {
result_limbs[top_limb - 1]
} else {
0
};
let next_next_val = if top_limb > 1 {
result_limbs[top_limb - 2]
} else {
0
};
let result = if bits_in_top >= 53 {
let shift = bits_in_top - 53;
let mant = (top_val >> shift) as u64;
let round_bit = if shift > 0 {
(top_val >> (shift - 1)) & 1
} else {
0
};
let sticky = if shift > 1 {
(top_val & ((1u64 << (shift - 1)) - 1)) != 0
} else if shift == 1 {
false
} else {
(next_val >> 63) != 0
};
round_mantissa_to_float(mant, round_bit != 0, sticky, biased_exp, result_sign)
} else if bits_in_top > 0 && bits_in_top + 64 >= 53 {
let shift = 53 - bits_in_top;
let mut mant = (top_val << shift) as u128;
if shift <= 64 {
mant |= (next_val >> (64 - shift)) as u128;
}
let round_bit = if shift < 64 {
((next_val >> (64 - shift - 1)) & 1) != 0
} else {
false
};
let sticky = if shift < 63 {
(next_val & ((1u64 << (64 - shift - 1)) - 1)) != 0 || next_next_val != 0
} else {
next_next_val != 0
};
round_mantissa_to_float(mant as u64, round_bit, sticky, biased_exp, result_sign)
} else {
if result_sign { -0.0 } else { 0.0 }
};
result
}
fn add_to_bigint(limbs: &mut [u64], val: u64, limb_idx: usize, bit_shift: usize) {
if limb_idx >= limbs.len() || val == 0 {
return;
}
let (lo, hi) = if bit_shift == 0 {
(val, 0u64)
} else {
(val << bit_shift, val >> (64 - bit_shift))
};
let mut carry = {
let sum = limbs[limb_idx] as u128 + lo as u128;
limbs[limb_idx] = sum as u64;
(sum >> 64) as u64
};
if hi != 0 || carry != 0 {
let idx = limb_idx + 1;
if idx < limbs.len() {
let sum = limbs[idx] as u128 + hi as u128 + carry as u128;
limbs[idx] = sum as u64;
carry = (sum >> 64) as u64;
let mut cidx = idx + 1;
while carry != 0 && cidx < limbs.len() {
let sum = limbs[cidx] as u128 + carry as u128;
limbs[cidx] = sum as u64;
carry = (sum >> 64) as u64;
cidx += 1;
}
}
}
}
fn cmp_bigint(a: &[u64], b: &[u64]) -> i32 {
for i in (0..a.len()).rev() {
if a[i] > b[i] {
return 1;
}
if a[i] < b[i] {
return -1;
}
}
0
}
fn sub_bigint(a: &[u64], b: &[u64]) -> Vec<u64> {
let mut result = vec![0u64; a.len()];
let mut borrow = 0u64;
for i in 0..a.len() {
let (diff1, borrow1) = a[i].overflowing_sub(b[i]);
let (diff2, borrow2) = diff1.overflowing_sub(borrow);
result[i] = diff2;
borrow = borrow1 as u64 + borrow2 as u64;
}
result
}
fn round_mantissa_to_float(
mant: u64,
round_bit: bool,
sticky: bool,
mut biased_exp: i32,
negative: bool,
) -> f64 {
let mant_53_mask = (1u64 << 53) - 1;
let mut mant = mant & mant_53_mask;
if mant == 0 && !round_bit {
return if negative { -0.0 } else { 0.0 };
}
if round_bit && (sticky || (mant & 1) != 0) {
mant = mant.wrapping_add(1);
if mant > mant_53_mask {
mant >>= 1;
biased_exp += 1;
}
}
if biased_exp >= 0x7FF {
return if negative {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
if biased_exp <= 0 {
return if negative { -0.0 } else { 0.0 };
}
let mant_bits = mant & ((1u64 << 52) - 1);
let sign_bit = if negative { 1u64 << 63 } else { 0 };
let exp_bits = (biased_exp as u64) << 52;
f64::from_bits(sign_bit | exp_bits | mant_bits)
}