use core::marker::PhantomData;
#[cfg(feature = "bigint")]
use num_bigint::{BigInt, Sign};
use crate::types::sealed::Sealed;
use crate::types::{Env, Invariant, RawTerm, Term};
#[derive(Clone, Copy)]
pub struct Integer<'id> {
raw_term: RawTerm,
_id: Invariant<'id>,
}
impl<'id> Integer<'id> {
#[crate::raw]
pub(crate) fn from_raw(raw_term: RawTerm) -> Self {
Self { raw_term, _id: PhantomData }
}
pub fn from_i64(env: impl Env<'id>, val: i64) -> Self {
let raw_term = unsafe { enif_ffi::make_int64(env.raw_env(), val) };
Integer { raw_term, _id: PhantomData }
}
pub fn from_u64(env: impl Env<'id>, val: u64) -> Self {
let raw_term = unsafe { enif_ffi::make_uint64(env.raw_env(), val) };
Integer { raw_term, _id: PhantomData }
}
pub fn to_i64(self, env: impl Env<'id>) -> Option<i64> {
let mut val: i64 = 0;
(unsafe { enif_ffi::get_int64(env.raw_env(), self.raw_term, &mut val) } != 0).then_some(val)
}
pub fn to_u64(self, env: impl Env<'id>) -> Option<u64> {
let mut val: u64 = 0;
(unsafe { enif_ffi::get_uint64(env.raw_env(), self.raw_term, &mut val) } != 0).then_some(val)
}
#[cfg(feature = "bigint")]
#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
pub fn to_bigint(self, env: impl Env<'id>) -> BigInt {
let buf = crate::types::serialize(env, self)
.expect("enif_term_to_binary failed on an integer term");
etf_integer_to_bigint(buf.as_bytes())
}
#[cfg(feature = "bigint")]
#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
pub fn from_bigint(env: impl Env<'id>, val: &BigInt) -> Self {
if let Ok(i) = i64::try_from(val) {
return Self::from_i64(env, i);
}
if let Ok(u) = u64::try_from(val) {
return Self::from_u64(env, u);
}
let etf = bigint_to_etf(val);
let term = crate::types::deserialize(env, &etf, false)
.expect("enif_binary_to_term failed on otter-authored bignum ETF");
Integer::from_raw(term.raw_term())
}
}
#[cfg(feature = "bigint")]
const ETF_VERSION: u8 = 131;
#[cfg(feature = "bigint")]
const SMALL_INTEGER_EXT: u8 = 97; #[cfg(feature = "bigint")]
const INTEGER_EXT: u8 = 98; #[cfg(feature = "bigint")]
const SMALL_BIG_EXT: u8 = 110; #[cfg(feature = "bigint")]
const LARGE_BIG_EXT: u8 = 111;
#[cfg(feature = "bigint")]
fn etf_integer_to_bigint(bytes: &[u8]) -> BigInt {
assert!(
bytes.len() >= 2 && bytes[0] == ETF_VERSION,
"malformed ETF from enif_term_to_binary"
);
let body = &bytes[1..];
match body[0] {
SMALL_INTEGER_EXT => BigInt::from(body[1]),
INTEGER_EXT => BigInt::from(i32::from_be_bytes([body[1], body[2], body[3], body[4]])),
SMALL_BIG_EXT => {
let n = body[1] as usize;
BigInt::from_bytes_le(etf_sign(body[2]), &body[3..3 + n])
}
LARGE_BIG_EXT => {
let n = u32::from_be_bytes([body[1], body[2], body[3], body[4]]) as usize;
BigInt::from_bytes_le(etf_sign(body[5]), &body[6..6 + n])
}
tag => panic!("enif_term_to_binary of an integer produced unexpected ETF tag {tag}"),
}
}
#[cfg(feature = "bigint")]
fn etf_sign(byte: u8) -> Sign {
if byte == 0 { Sign::Plus } else { Sign::Minus }
}
#[cfg(feature = "bigint")]
fn bigint_to_etf(val: &BigInt) -> Vec<u8> {
let (sign, mag) = val.to_bytes_le();
let sign_byte = if sign == Sign::Minus { 1 } else { 0 };
let mut etf = Vec::with_capacity(mag.len() + 7);
etf.push(ETF_VERSION);
if mag.len() <= u8::MAX as usize {
etf.push(SMALL_BIG_EXT);
etf.push(mag.len() as u8);
etf.push(sign_byte);
} else {
etf.push(LARGE_BIG_EXT);
etf.extend_from_slice(&(mag.len() as u32).to_be_bytes());
etf.push(sign_byte);
}
etf.extend_from_slice(&mag);
etf
}
impl<'id> Sealed for Integer<'id> {}
impl<'id> Term<'id> for Integer<'id> {
fn raw_term(self) -> RawTerm {
self.raw_term
}
}
impl PartialEq for Integer<'_> {
fn eq(&self, other: &Self) -> bool {
unsafe { enif_ffi::is_identical(self.raw_term, other.raw_term) != 0 }
}
}
impl Eq for Integer<'_> {}
impl PartialOrd for Integer<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Integer<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let c = unsafe { enif_ffi::compare(self.raw_term, other.raw_term) };
c.cmp(&0)
}
}
impl std::fmt::Debug for Integer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Integer")
}
}