use crate::atom::Atom;
use crate::native::ProcessContext;
use crate::term::Term;
use crate::term::binary::{Binary, packed_word_count, write_binary};
use crate::term::boxed::{Float, Map, write_float};
pub fn bif_round(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [value] = args else {
return Err(badarg());
};
if value.as_small_int().is_some() {
return Ok(*value);
}
float_to_small_int(*value, f64::round)
}
pub fn bif_trunc(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [value] = args else {
return Err(badarg());
};
if value.as_small_int().is_some() {
return Ok(*value);
}
float_to_small_int(*value, f64::trunc)
}
pub fn bif_is_bitstring(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [value] = args else {
return Err(badarg());
};
Ok(bool_term(Binary::new(*value).is_some()))
}
pub fn bif_is_map_key(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [key, map_term] = args else {
return Err(badarg());
};
let map = Map::new(*map_term).ok_or_else(badarg)?;
Ok(bool_term(map.get(*key).is_some()))
}
pub fn bif_map_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [map_term] = args else {
return Err(badarg());
};
let map = Map::new(*map_term).ok_or_else(badarg)?;
i64::try_from(map.len())
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_binary_part(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term, offset_term, length_term] = args else {
return Err(badarg());
};
let binary = Binary::new(*binary_term).ok_or_else(badarg)?;
let offset = offset_term
.as_small_int()
.and_then(|value| usize::try_from(value).ok())
.ok_or_else(badarg)?;
let length = length_term
.as_small_int()
.and_then(|value| usize::try_from(value).ok())
.ok_or_else(badarg)?;
let end = offset.checked_add(length).ok_or_else(badarg)?;
let bytes = binary.as_bytes();
if end > bytes.len() {
return Err(badarg());
}
make_binary(&bytes[offset..end])
}
pub fn bif_bit_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term] = args else {
return Err(badarg());
};
let binary = Binary::new(*binary_term).ok_or_else(badarg)?;
let bits = binary.len().checked_mul(8).ok_or_else(badarg)?;
i64::try_from(bits)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_unary_minus(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [value] = args else {
return Err(badarg());
};
if let Some(integer) = value.as_small_int() {
return integer
.checked_neg()
.and_then(Term::try_small_int)
.ok_or_else(badarg);
}
let value = Float::new(*value).ok_or_else(badarg)?.value();
if !value.is_finite() {
return Err(badarg());
}
make_float(-value)
}
fn float_to_small_int(term: Term, operation: fn(f64) -> f64) -> Result<Term, Term> {
let value = Float::new(term).ok_or_else(badarg)?.value();
if !value.is_finite() {
return Err(badarg());
}
let value = operation(value);
if !value.is_finite()
|| value < Term::SMALL_INT_MIN as f64
|| value > Term::SMALL_INT_MAX as f64
{
return Err(badarg());
}
Term::try_small_int(value as i64).ok_or_else(badarg)
}
fn make_float(value: f64) -> Result<Term, Term> {
if !value.is_finite() {
return Err(badarg());
}
let heap = Box::leak(Box::new([0u64; 2]));
write_float(heap, value).ok_or_else(badarg)
}
fn make_binary(bytes: &[u8]) -> Result<Term, Term> {
let heap = Box::leak(vec![0u64; 2 + packed_word_count(bytes.len())].into_boxed_slice());
write_binary(heap, bytes).ok_or_else(badarg)
}
fn bool_term(value: bool) -> Term {
Term::atom(if value { Atom::TRUE } else { Atom::FALSE })
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}