use crate::{
Context, JsArgs, JsResult, JsStr, JsString, JsValue,
builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject},
context::intrinsics::Intrinsics,
object::JsObject,
realm::Realm,
string::StaticJsStrings,
};
use boa_macros::js_str;
use boa_string::JsStrVariant;
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(string), radix) = (args.first(), args.get_or_undefined(1)) else {
return Ok(JsValue::nan());
};
if let Some(int) = string.as_i32()
&& radix.is_null_or_undefined()
{
return Ok(JsValue::new(int));
}
let input_string = string.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.as_str(), 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> {
let Some(string) = args.first() else {
return Ok(JsValue::nan());
};
if string.is_number() {
if string.is_negative_zero() {
return Ok(JsValue::new(0));
}
return Ok(string.clone());
}
let input_string = string.to_string(context)?;
let trimmed_string = input_string.trim_start();
let (positive, prefix) = match trimmed_string
.code_unit_at(0)
.and_then(|c| char::from_u32(u32::from(c)))
{
Some('+') => (
true,
trimmed_string
.get(1..)
.unwrap_or(StaticJsStrings::EMPTY_STRING),
),
Some('-') => (
false,
trimmed_string
.get(1..)
.unwrap_or(StaticJsStrings::EMPTY_STRING),
),
_ => (true, trimmed_string.clone()),
};
if prefix.starts_with(js_str!("Infinity")) {
if positive {
return Ok(JsValue::positive_infinity());
}
return Ok(JsValue::negative_infinity());
} else if let Some('I' | 'i') = prefix
.code_unit_at(0)
.and_then(|c| char::from_u32(u32::from(c)))
{
return Ok(JsValue::nan());
}
let value = match trimmed_string.variant() {
JsStrVariant::Latin1(s) => fast_float2::parse_partial::<f64, _>(s),
JsStrVariant::Utf16(s) => {
let s = String::from_utf16_lossy(s);
fast_float2::parse_partial::<f64, _>(s.as_bytes())
}
};
Ok(value.map_or_else(
|_| JsValue::nan(),
|(f, len)| {
if len > 0 {
JsValue::new(f)
} else {
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;
}