use crate::{
Context, JsArgs, JsResult, JsStr, JsString, JsValue,
builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject, string::is_trimmable_whitespace},
context::intrinsics::Intrinsics,
object::JsObject,
realm::Realm,
string::StaticJsStrings,
};
use boa_macros::js_str;
use cow_utils::CowUtils;
fn is_finite(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let Some(value) = args.first() {
let number = value.to_number(context)?;
Ok(number.is_finite().into())
} else {
Ok(false.into())
}
}
pub(crate) struct IsFinite;
impl IntrinsicObject for IsFinite {
fn init(realm: &Realm) {
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, is_finite)
.name(Self::NAME)
.length(1)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().is_finite().into()
}
}
impl BuiltInObject for IsFinite {
const NAME: JsString = StaticJsStrings::IS_FINITE;
}
pub(crate) fn is_nan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let Some(value) = args.first() {
let number = value.to_number(context)?;
Ok(number.is_nan().into())
} else {
Ok(true.into())
}
}
pub(crate) struct IsNaN;
impl IntrinsicObject for IsNaN {
fn init(realm: &Realm) {
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, is_nan)
.name(Self::NAME)
.length(1)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().is_nan().into()
}
}
impl BuiltInObject for IsNaN {
const NAME: JsString = StaticJsStrings::IS_NAN;
}
fn from_js_str_radix(src: JsStr<'_>, radix: u8) -> Option<f64> {
fn can_not_overflow(radix: u8, digits_len: usize) -> bool {
usize::from(radix) <= 16 && digits_len <= size_of::<u64>() * 2
}
const fn to_digit(input: u8, radix: u8) -> Option<u8> {
let mut digit = input.wrapping_sub(b'0');
if radix > 10 {
debug_assert!(radix <= 36, "to_digit: radix is too high (maximum 36)");
if digit < 10 {
return Some(digit);
}
digit = (input | 0b10_0000).wrapping_sub(b'a').saturating_add(10);
}
if digit < radix { Some(digit) } else { None }
}
let src = src
.iter()
.map(|x| u8::try_from(x).expect("should be ascii string"));
let result = if can_not_overflow(radix, src.len()) {
let mut result = 0;
for c in src {
result = result * u64::from(radix) + u64::from(to_digit(c, radix)?);
}
result as f64
} else {
let mut result = 0f64;
for c in src {
result = result * f64::from(radix) + f64::from(to_digit(c, radix)?);
}
result
};
Some(result)
}
pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) else {
return Ok(JsValue::nan());
};
let input_string = val.to_string(context)?;
let mut s = input_string.trim_start();
let sign = if !s.is_empty() && s.starts_with(js_str!("-")) {
-1
} else {
1
};
if !s.is_empty() && (s.starts_with(js_str!("+")) || s.starts_with(js_str!("-"))) {
s = s.get(1..).expect("already checked that it's not empty");
}
let r = radix.to_i32(context)?;
let mut strip_prefix = true;
#[allow(clippy::if_not_else)]
let mut r = if r != 0 {
if !(2..=36).contains(&r) {
return Ok(JsValue::nan());
}
if r != 16 {
strip_prefix = false;
}
r as u8
} else {
10
};
if strip_prefix
&& s.len() >= 2
&& (s.starts_with(js_str!("0x")) || s.starts_with(js_str!("0X")))
{
s = s
.get(2..)
.expect("already checked that it contains at least two chars");
r = 16;
}
let end = char::decode_utf16(s.iter())
.position(|code| !code.is_ok_and(|c| c.is_digit(u32::from(r))))
.unwrap_or(s.len());
let z = s.get(..end).expect("should be in range");
if z.is_empty() {
return Ok(JsValue::nan());
}
let math_int = from_js_str_radix(z, r).expect("Already checked");
if math_int == 0_f64 {
if sign == -1 {
return Ok(JsValue::new(-0_f64));
}
return Ok(JsValue::new(0));
}
Ok(JsValue::new(f64::from(sign) * math_int))
}
pub(crate) struct ParseInt;
impl IntrinsicObject for ParseInt {
fn init(realm: &Realm) {
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, parse_int)
.name(Self::NAME)
.length(2)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().parse_int().into()
}
}
impl BuiltInObject for ParseInt {
const NAME: JsString = StaticJsStrings::PARSE_INT;
}
pub(crate) fn parse_float(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if let Some(val) = args.first() {
let input_string = val.to_string(context)?.to_std_string_escaped();
let s = input_string.trim_start_matches(is_trimmable_whitespace);
let s_prefix = s.chars().take(4).collect::<String>();
let s_prefix_lower = s_prefix.cow_to_ascii_lowercase();
if s.starts_with("Infinity") || s.starts_with("+Infinity") {
Ok(JsValue::new(f64::INFINITY))
} else if s.starts_with("-Infinity") {
Ok(JsValue::new(f64::NEG_INFINITY))
} else if s_prefix_lower.starts_with("inf")
|| s_prefix_lower.starts_with("+inf")
|| s_prefix_lower.starts_with("-inf")
{
Ok(JsValue::nan())
} else {
Ok(fast_float2::parse_partial::<f64, _>(s).map_or_else(
|_| JsValue::nan(),
|(f, len)| {
if len > 0 {
JsValue::new(f)
} else {
JsValue::nan()
}
},
))
}
} else {
Ok(JsValue::nan())
}
}
pub(crate) struct ParseFloat;
impl IntrinsicObject for ParseFloat {
fn init(realm: &Realm) {
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, parse_float)
.name(Self::NAME)
.length(1)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().parse_float().into()
}
}
impl BuiltInObject for ParseFloat {
const NAME: JsString = StaticJsStrings::PARSE_FLOAT;
}