#![doc = include_str!("../README.md")]
use num_bigint::BigInt;
use rhai::{def_package, plugin::*};
#[export_module]
mod bigint_functions {
use num_bigint::{BigInt, Sign};
use num_traits::{FromPrimitive, ToPrimitive, Zero};
pub fn bigint(value: i64) -> BigInt {
value.into()
}
#[rhai_fn(name = "bigint", return_raw)]
pub fn bigint_from_float(value: rhai::FLOAT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
BigInt::from_f64(value)
.ok_or_else(|| format!("Cannot convert {value} to BigInt: value must be finite").into())
}
#[rhai_fn(name = "bigint", return_raw)]
pub fn bigint_from_str(value: String) -> Result<BigInt, Box<rhai::EvalAltResult>> {
value
.parse::<BigInt>()
.map_err(|e| format!("Failed to create BigInt from string: {e}").into())
}
#[rhai_fn(name = "+", pure)]
pub fn add(l: &mut BigInt, r: BigInt) -> BigInt {
l.clone() + r
}
#[rhai_fn(name = "-", pure)]
pub fn sub(l: &mut BigInt, r: BigInt) -> BigInt {
l.clone() - r
}
#[rhai_fn(name = "*", pure)]
pub fn mul(l: &mut BigInt, r: BigInt) -> BigInt {
l.clone() * r
}
#[rhai_fn(name = "/", pure, return_raw)]
pub fn div(l: &mut BigInt, r: BigInt) -> Result<BigInt, Box<rhai::EvalAltResult>> {
if r.is_zero() {
return Err("Division by zero".into());
}
Ok(l.clone() / r)
}
#[rhai_fn(name = "%", pure, return_raw)]
pub fn rem(l: &mut BigInt, r: BigInt) -> Result<BigInt, Box<rhai::EvalAltResult>> {
if r.is_zero() {
return Err("Modulo by zero".into());
}
Ok(l.clone() % r)
}
#[rhai_fn(name = "-", pure)]
pub fn neg(value: &mut BigInt) -> BigInt {
-value.clone()
}
#[rhai_fn(name = "==", pure)]
pub fn eq(l: &mut BigInt, r: BigInt) -> bool {
*l == r
}
#[rhai_fn(name = "!=", pure)]
pub fn ne(l: &mut BigInt, r: BigInt) -> bool {
*l != r
}
#[rhai_fn(name = "<", pure)]
pub fn lt(l: &mut BigInt, r: BigInt) -> bool {
*l < r
}
#[rhai_fn(name = "<=", pure)]
pub fn le(l: &mut BigInt, r: BigInt) -> bool {
*l <= r
}
#[rhai_fn(name = ">", pure)]
pub fn gt(l: &mut BigInt, r: BigInt) -> bool {
*l > r
}
#[rhai_fn(name = ">=", pure)]
pub fn ge(l: &mut BigInt, r: BigInt) -> bool {
*l >= r
}
#[rhai_fn(name = "to_string", pure)]
pub fn to_string(value: &mut BigInt) -> String {
value.to_string()
}
#[rhai_fn(name = "to_hex", pure)]
pub fn to_hex(value: &mut BigInt) -> String {
let hex = format!("{:x}", value.magnitude());
if value.sign() == Sign::Minus {
format!("-0x{hex}")
} else {
format!("0x{hex}")
}
}
#[rhai_fn(name = "to_float", pure, return_raw)]
pub fn to_float(value: &mut BigInt) -> Result<rhai::FLOAT, Box<rhai::EvalAltResult>> {
value
.to_f64()
.map(|f| f as rhai::FLOAT)
.filter(|f| f.is_finite())
.ok_or_else(|| "BigInt value is too large to represent as a finite float".into())
}
}
def_package! {
pub BigIntPackage(lib) {
lib.set_custom_type::<BigInt>("BigInt");
combine_with_exported_module!(lib, "bigint", bigint_functions);
}
}
#[cfg(test)]
mod tests {
use rhai::{Engine, packages::Package};
use super::*;
#[test]
fn test_rhai_integration() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: BigInt = engine.eval("bigint(42)").unwrap();
assert_eq!(result.to_string(), "42");
let result: BigInt = engine
.eval("bigint(\"123456789012345678901234567890\")")
.unwrap();
assert_eq!(result.to_string(), "123456789012345678901234567890");
let result: BigInt = engine.eval("bigint(42) + bigint(58)").unwrap();
assert_eq!(result.to_string(), "100");
let result: bool = engine.eval("bigint(50) > bigint(42)").unwrap();
assert!(result);
let result: bool = engine.eval("bigint(42) == bigint(42)").unwrap();
assert!(result);
let result: bool = engine.eval("bigint(42) != bigint(100)").unwrap();
assert!(result);
}
#[test]
fn test_core_functionality() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: BigInt = engine
.eval("bigint(1000000000000000000) + bigint(2000000000000000000)")
.unwrap();
assert_eq!(result.to_string(), "3000000000000000000");
let result: BigInt = engine
.eval("bigint(5000000000000000000) - bigint(1000000000000000000)")
.unwrap();
assert_eq!(result.to_string(), "4000000000000000000");
let result: BigInt = engine.eval("bigint(1000000) * bigint(1000000)").unwrap();
assert_eq!(result.to_string(), "1000000000000");
let result: BigInt = engine
.eval("bigint(1000000000000) / bigint(1000000)")
.unwrap();
assert_eq!(result.to_string(), "1000000");
let result: BigInt = engine.eval("bigint(10) % bigint(3)").unwrap();
assert_eq!(result.to_string(), "1");
let result: BigInt = engine.eval("-bigint(42)").unwrap();
assert_eq!(result.to_string(), "-42");
}
#[test]
fn test_error_handling() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result = engine.eval::<BigInt>("bigint(\"not_a_number\")");
assert!(result.is_err());
let result = engine.eval::<BigInt>("bigint(42) / bigint(0)");
assert!(result.is_err());
let result = engine.eval::<BigInt>("bigint(42) % bigint(0)");
assert!(result.is_err());
}
#[test]
fn test_bigint_from_f64() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: BigInt = engine.eval("bigint(1.5)").unwrap();
assert_eq!(result.to_string(), "1");
let result: BigInt = engine.eval("bigint(-2.9)").unwrap();
assert_eq!(result.to_string(), "-2");
let result: BigInt = engine.eval("bigint(42.0)").unwrap();
assert_eq!(result.to_string(), "42");
let result: BigInt = engine.eval("bigint(1e30)").unwrap();
assert_eq!(result.to_string(), "1000000000000000019884624838656");
}
#[test]
fn test_bigint_from_f64_errors() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result = engine.eval::<BigInt>("bigint(1.0 / 0.0)");
assert!(result.is_err(), "infinity should be rejected");
let result = engine.eval::<BigInt>("bigint(0.0 / 0.0)");
assert!(result.is_err(), "NaN should be rejected");
}
#[test]
fn test_to_string() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: String = engine.eval("bigint(42).to_string()").unwrap();
assert_eq!(result, "42");
let result: String = engine.eval("bigint(-99).to_string()").unwrap();
assert_eq!(result, "-99");
let result: String = engine
.eval("bigint(\"123456789012345678901234567890\").to_string()")
.unwrap();
assert_eq!(result, "123456789012345678901234567890");
}
#[test]
fn test_to_hex() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: String = engine.eval("bigint(255).to_hex()").unwrap();
assert_eq!(result, "0xff");
let result: String = engine.eval("bigint(0).to_hex()").unwrap();
assert_eq!(result, "0x0");
let result: String = engine.eval("bigint(-255).to_hex()").unwrap();
assert_eq!(result, "-0xff");
let result: String = engine.eval("bigint(256).to_hex()").unwrap();
assert_eq!(result, "0x100");
}
#[test]
fn test_to_float() {
let mut engine = Engine::new();
BigIntPackage::new().register_into_engine(&mut engine);
let result: rhai::FLOAT = engine.eval("bigint(42).to_float()").unwrap();
assert_eq!(result, 42.0);
let result: rhai::FLOAT = engine.eval("bigint(-7).to_float()").unwrap();
assert_eq!(result, -7.0);
let result = engine.eval::<rhai::FLOAT>(
"bigint(\"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\").to_float()"
);
assert!(result.is_err(), "overflow to infinity should be rejected");
}
}