use beamr::term::{Term, binary, boxed, boxed::write_bigint};
use crate::{NifContext, TermError};
pub fn binary_term(heap: &mut [u64], bytes: &[u8]) -> Result<Term, TermError> {
binary::write_binary(heap, bytes).ok_or(TermError::HeapAllocation { shape: "binary" })
}
#[must_use]
pub const fn binary_word_len(byte_len: usize) -> usize {
2 + binary::packed_word_count(byte_len)
}
pub fn owned_binary_term(ctx: &mut NifContext<'_, '_>, bytes: &[u8]) -> Result<Term, TermError> {
ctx.retain_heap(binary_word_len(bytes.len()), |heap| {
binary_term(heap, bytes)
})
}
#[must_use]
pub const fn tuple_word_len(arity: usize) -> usize {
1 + arity
}
pub fn tuple_term(heap: &mut [u64], elements: &[Term]) -> Result<Term, TermError> {
boxed::write_tuple(heap, elements).ok_or(TermError::HeapAllocation { shape: "tuple" })
}
pub fn owned_tuple_term(
ctx: &mut NifContext<'_, '_>,
elements: &[Term],
) -> Result<Term, TermError> {
ctx.retain_heap(tuple_word_len(elements.len()), |heap| {
tuple_term(heap, elements)
})
}
#[must_use]
pub const fn float_word_len() -> usize {
2
}
pub fn float_term(heap: &mut [u64], value: f64) -> Result<Term, TermError> {
boxed::write_float(heap, value).ok_or(TermError::HeapAllocation { shape: "float" })
}
pub fn owned_float_term(ctx: &mut NifContext<'_, '_>, value: f64) -> Result<Term, TermError> {
ctx.retain_heap(float_word_len(), |heap| float_term(heap, value))
}
#[must_use]
pub const fn bigint_word_len(limb_count: usize) -> usize {
3 + limb_count
}
pub fn bigint_term(heap: &mut [u64], negative: bool, limbs: &[u64]) -> Result<Term, TermError> {
write_bigint(heap, negative, limbs).ok_or(TermError::HeapAllocation { shape: "bigint" })
}
pub fn owned_bigint_term(
ctx: &mut NifContext<'_, '_>,
negative: bool,
limbs: &[u64],
) -> Result<Term, TermError> {
let len = limbs
.iter()
.rposition(|limb| *limb != 0)
.map_or(0, |index| index + 1);
let normalized = &limbs[..len];
ctx.retain_heap(bigint_word_len(normalized.len()), |heap| {
bigint_term(heap, negative, normalized)
})
}
#[must_use]
pub const fn cons_word_len() -> usize {
2
}
pub fn cons_term(words: &mut [u64], head: Term, tail: Term) -> Result<Term, TermError> {
boxed::write_cons(words, head, tail).ok_or(TermError::HeapAllocation { shape: "cons" })
}
pub fn owned_cons_term(
ctx: &mut NifContext<'_, '_>,
head: Term,
tail: Term,
) -> Result<Term, TermError> {
ctx.retain_heap(cons_word_len(), |heap| cons_term(heap, head, tail))
}
#[must_use]
pub const fn list_word_len(len: usize) -> usize {
cons_word_len() * len
}
pub fn list_term(heap: &mut [u64], elements: &[Term]) -> Result<Term, TermError> {
if heap.len() < list_word_len(elements.len()) {
return Err(TermError::HeapAllocation { shape: "list" });
}
elements
.iter()
.rev()
.zip(heap.chunks_exact_mut(cons_word_len()).rev())
.try_fold(Term::NIL, |tail, (head, cell_heap)| {
cons_term(cell_heap, *head, tail)
})
}
pub fn owned_list_term(ctx: &mut NifContext<'_, '_>, elements: &[Term]) -> Result<Term, TermError> {
ctx.retain_heap(list_word_len(elements.len()), |heap| {
list_term(heap, elements)
})
}
#[must_use]
pub const fn map_word_len(len: usize) -> usize {
2 + (len * 2)
}
pub fn map_term(heap: &mut [u64], keys: &[Term], values: &[Term]) -> Result<Term, TermError> {
boxed::write_map(heap, keys, values).ok_or(TermError::HeapAllocation { shape: "map" })
}
pub fn owned_map_term(
ctx: &mut NifContext<'_, '_>,
keys: &[Term],
values: &[Term],
) -> Result<Term, TermError> {
ctx.retain_heap(map_word_len(keys.len()), |heap| {
map_term(heap, keys, values)
})
}
#[cfg(test)]
mod tests {
use beamr::term::{
Term,
binary::Binary,
boxed::{BigInt, Cons, Float, Map, Tuple},
};
use super::{
bigint_term, bigint_word_len, binary_term, binary_word_len, float_term, float_word_len,
list_term, list_word_len, map_term, map_word_len, tuple_term, tuple_word_len,
};
#[test]
fn tuple_wrapper_round_trips_elements() -> Result<(), Box<dyn std::error::Error>> {
let elements = [Term::small_int(1), Term::small_int(2)];
let mut heap = vec![0_u64; tuple_word_len(elements.len())];
let term = tuple_term(&mut heap, &elements)?;
let tuple = Tuple::new(term).ok_or("tuple accessor should accept tuple term")?;
assert_eq!(tuple.arity(), 2);
assert_eq!(tuple.get(0), Some(Term::small_int(1)));
assert_eq!(tuple.get(1), Some(Term::small_int(2)));
Ok(())
}
#[test]
fn list_wrapper_round_trips_cons_cells() -> Result<(), Box<dyn std::error::Error>> {
let mut heap = vec![0_u64; list_word_len(2)];
let term = list_term(&mut heap, &[Term::small_int(10), Term::small_int(20)])?;
let first = Cons::new(term).ok_or("first cons cell should exist")?;
let second = Cons::new(first.tail()).ok_or("second cons cell should exist")?;
assert_eq!(first.head(), Term::small_int(10));
assert_eq!(second.head(), Term::small_int(20));
assert_eq!(second.tail(), Term::NIL);
Ok(())
}
#[test]
fn binary_wrapper_round_trips_bytes() -> Result<(), Box<dyn std::error::Error>> {
let bytes = b"ok";
let mut heap = vec![0_u64; binary_word_len(bytes.len())];
let term = binary_term(&mut heap, bytes)?;
let binary = Binary::new(term).ok_or("binary accessor should accept binary term")?;
assert_eq!(binary.as_bytes(), bytes);
Ok(())
}
#[test]
fn float_wrapper_round_trips_value() -> Result<(), Box<dyn std::error::Error>> {
let mut heap = vec![0_u64; float_word_len()];
let term = float_term(&mut heap, 3.5)?;
let float = Float::new(term).ok_or("float accessor should accept float term")?;
assert_eq!(float.value().to_bits(), 3.5_f64.to_bits());
Ok(())
}
#[test]
fn bigint_wrapper_round_trips_limbs() -> Result<(), Box<dyn std::error::Error>> {
let limbs = [1_u64, 2_u64];
let mut heap = vec![0_u64; bigint_word_len(limbs.len())];
let term = bigint_term(&mut heap, true, &limbs)?;
let bigint = BigInt::new(term).ok_or("bigint accessor should accept bigint term")?;
assert!(bigint.is_negative());
assert_eq!(bigint.limbs(), limbs);
Ok(())
}
#[test]
fn map_wrapper_round_trips_key_values() -> Result<(), Box<dyn std::error::Error>> {
let keys = [Term::small_int(1), Term::small_int(2)];
let values = [Term::small_int(10), Term::small_int(20)];
let mut heap = vec![0_u64; map_word_len(keys.len())];
let term = map_term(&mut heap, &keys, &values)?;
let map = Map::new(term).ok_or("map accessor should accept map term")?;
assert_eq!(map.len(), 2);
assert_eq!(map.get(Term::small_int(1)), Some(Term::small_int(10)));
assert_eq!(map.get(Term::small_int(2)), Some(Term::small_int(20)));
Ok(())
}
}