use crate::{
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
object::JsObject,
property::Attribute,
realm::Realm,
symbol::JsSymbol,
value::{IntegerOrInfinity, PreferredType},
Context, JsArgs, JsBigInt, JsResult, JsValue,
};
use boa_profiler::Profiler;
use num_bigint::ToBigInt;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy)]
pub struct BigInt;
impl IntrinsicObject for BigInt {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.method(Self::to_string, "toString", 0)
.method(Self::value_of, "valueOf", 0)
.static_method(Self::as_int_n, "asIntN", 2)
.static_method(Self::as_uint_n, "asUintN", 2)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for BigInt {
const NAME: &'static str = "BigInt";
}
impl BuiltInConstructor for BigInt {
const LENGTH: usize = 1;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::bigint;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if !new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("BigInt is not a constructor")
.into());
}
let value = args.get_or_undefined(0);
let prim = value.to_primitive(context, PreferredType::Number)?;
if let Some(number) = prim.as_number() {
return Self::number_to_bigint(number);
}
Ok(prim.to_bigint(context)?.into())
}
}
impl BigInt {
fn number_to_bigint(number: f64) -> JsResult<JsValue> {
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return Err(JsNativeError::range()
.with_message(format!("cannot convert {number} to a BigInt"))
.into());
}
Ok(JsBigInt::from(number.to_bigint().expect("This conversion must be safe")).into())
}
fn this_bigint_value(value: &JsValue) -> JsResult<JsBigInt> {
value
.as_bigint()
.cloned()
.or_else(|| {
value
.as_object()
.and_then(|obj| obj.borrow().as_bigint().cloned())
})
.ok_or_else(|| {
JsNativeError::typ()
.with_message("'this' is not a BigInt")
.into()
})
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let x = Self::this_bigint_value(this)?;
let radix = args.get_or_undefined(0);
let radix_mv = if radix.is_undefined() {
return Ok(x.to_string().into());
} else {
radix.to_integer_or_infinity(context)?
};
let radix_mv = match radix_mv {
IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i,
_ => {
return Err(JsNativeError::range()
.with_message("radix must be an integer at least 2 and no greater than 36")
.into())
}
};
if radix_mv == 10 {
return Ok(x.to_string().into());
}
Ok(JsValue::new(x.to_string_radix(radix_mv as u32)))
}
pub(crate) fn value_of(
this: &JsValue,
_: &[JsValue],
_: &mut Context<'_>,
) -> JsResult<JsValue> {
Ok(JsValue::new(Self::this_bigint_value(this)?))
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_int_n(
_: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let (modulo, bits) = Self::calculate_as_uint_n(args, context)?;
if bits > 0
&& modulo >= JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits) - 1))?
{
Ok(JsValue::new(JsBigInt::sub(
&modulo,
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?,
)))
} else {
Ok(JsValue::new(modulo))
}
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_uint_n(
_: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let (modulo, _) = Self::calculate_as_uint_n(args, context)?;
Ok(JsValue::new(modulo))
}
fn calculate_as_uint_n(
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<(JsBigInt, u32)> {
let bits_arg = args.get_or_undefined(0);
let bigint_arg = args.get_or_undefined(1);
let bits = bits_arg.to_index(context)?;
let bits = u32::try_from(bits).unwrap_or(u32::MAX);
let bigint = bigint_arg.to_bigint(context)?;
Ok((
JsBigInt::mod_floor(
&bigint,
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?,
),
bits,
))
}
}