mod additional;
mod registry_bifs;
mod type_conversion;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::atom::{Atom, AtomTable};
use crate::native::{BifRegistryImpl, NativeFn, NativeRegistrationError, ProcessContext};
use crate::term::Term;
use crate::term::binary::Binary;
use crate::term::boxed::{Cons, Tuple};
pub use additional::{
bif_binary_part, bif_bit_size, bif_is_bitstring, bif_is_map_key, bif_map_size, bif_round,
bif_trunc, bif_unary_minus,
};
pub use registry_bifs::{bif_register, bif_unregister, bif_whereis};
pub use type_conversion::{
bif_atom_to_binary, bif_binary_to_existing_atom, bif_binary_to_list, bif_list_to_binary,
bif_map_get,
};
type Gate3Bif = (&'static str, u8, NativeFn);
const GATE3_BIFS: &[Gate3Bif] = &[
("element", 2, bif_element),
("send", 2, bif_send),
("tuple_size", 1, bif_tuple_size),
("make_ref", 0, bif_make_ref),
("is_process_alive", 1, bif_is_process_alive),
("spawn", 1, bif_spawn_1),
("spawn_link", 1, bif_spawn_link_1),
("atom_to_binary", 2, bif_atom_to_binary),
("binary_to_existing_atom", 1, bif_binary_to_existing_atom),
("binary_to_list", 1, bif_binary_to_list),
("list_to_binary", 1, bif_list_to_binary),
("map_get", 2, bif_map_get),
("register", 2, bif_register),
("unregister", 1, bif_unregister),
("whereis", 1, bif_whereis),
("demonitor", 2, bif_demonitor_2),
("get", 0, bif_get),
("pid_to_list", 1, bif_pid_to_list),
("byte_size", 1, bif_byte_size),
("iolist_size", 1, bif_iolist_size),
("++", 2, bif_list_append),
("not", 1, bif_not),
("/=", 2, bif_not_equal),
("length", 1, bif_length),
("round", 1, bif_round),
("trunc", 1, bif_trunc),
("is_bitstring", 1, bif_is_bitstring),
("is_map_key", 2, bif_is_map_key),
("map_size", 1, bif_map_size),
("binary_part", 3, bif_binary_part),
("bit_size", 1, bif_bit_size),
("-", 1, bif_unary_minus),
];
static REF_COUNTER: AtomicU64 = AtomicU64::new(1);
pub fn register_gate3_bifs(
registry: &BifRegistryImpl,
atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
let erlang = atom_table.intern("erlang");
for &(function_name, arity, native_function) in GATE3_BIFS {
let function = atom_table.intern(function_name);
registry.register(erlang, function, arity, native_function)?;
}
Ok(())
}
pub fn bif_element(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [index_term, tuple_term] = args else {
return Err(badarg());
};
let index = index_term.as_small_int().ok_or_else(badarg)?;
if index < 1 {
return Err(badarg());
}
let tuple = Tuple::new(*tuple_term).ok_or_else(badarg)?;
let zero_based = (index - 1) as usize;
tuple.get(zero_based).ok_or_else(badarg)
}
pub fn bif_send(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term, message_term] = args else {
return Err(badarg());
};
pid_term.as_pid().ok_or_else(badarg)?;
Ok(*message_term)
}
pub fn bif_tuple_size(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [tuple_term] = args else {
return Err(badarg());
};
let tuple = Tuple::new(*tuple_term).ok_or_else(badarg)?;
let arity = tuple.arity();
i64::try_from(arity)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_make_ref(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let id = REF_COUNTER.fetch_add(1, Ordering::Relaxed);
i64::try_from(id)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_is_process_alive(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term] = args else {
return Err(badarg());
};
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
if let Some(caller_pid) = context.pid()
&& caller_pid == target_pid
{
return Ok(bool_term(true));
}
if let Some(facility) = context.supervision_facility() {
let caller_pid = context.pid().ok_or_else(badarg)?;
match facility.monitor(caller_pid, target_pid) {
Ok(result) => {
let _ = facility.demonitor(caller_pid, result.reference);
Ok(bool_term(true))
}
Err(_) => Ok(bool_term(false)),
}
} else {
Ok(bool_term(false))
}
}
pub fn bif_spawn_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_from_fun(args, context, false)
}
pub fn bif_spawn_link_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_from_fun(args, context, true)
}
fn spawn_from_fun(args: &[Term], context: &mut ProcessContext, link: bool) -> Result<Term, Term> {
let [fun_term] = args else {
return Err(badarg());
};
let closure = crate::term::boxed::Closure::new(*fun_term).ok_or_else(badarg)?;
if closure.arity() != 0 || closure.num_free() != 0 {
return Err(badarg());
}
let module = closure.module().ok_or_else(badarg)?;
let lambda_index = closure.function_index() as u32;
let link_to = if link {
Some(context.pid().ok_or_else(badarg)?)
} else {
None
};
let facility = context.spawn_facility().ok_or_else(badarg)?;
let new_pid = facility
.spawn_lambda(module, lambda_index, link_to)
.map_err(|_| badarg())?;
Term::try_pid(new_pid).ok_or_else(badarg)
}
pub fn bif_demonitor_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [ref_term, opts_term] = args else {
return Err(badarg());
};
let reference = ref_term.as_small_int().ok_or_else(badarg)?;
if reference < 0 {
return Err(badarg());
}
let mut opt_flush = false;
let mut opt_info = false;
let mut current = *opts_term;
loop {
if current.is_nil() {
break;
}
let cons = Cons::new(current).ok_or_else(badarg)?;
let opt = cons.head().as_atom().ok_or_else(badarg)?;
if opt == Atom::FLUSH {
opt_flush = true;
} else if opt == Atom::INFO {
opt_info = true;
} else {
return Err(badarg());
}
current = cons.tail();
}
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
let result = facility.demonitor(caller_pid, reference as u64);
let _ = opt_flush;
if opt_info {
Ok(bool_term(result.is_ok()))
} else {
result.map_err(|_| badarg())?;
Ok(bool_term(true))
}
}
pub fn bif_get(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
Ok(Term::NIL)
}
pub fn bif_pid_to_list(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term] = args else {
return Err(badarg());
};
let pid = pid_term.as_pid().ok_or_else(badarg)?;
let repr = format!("<0.{pid}.0>");
let bytes = repr.as_bytes();
let mut tail = Term::NIL;
for &byte in bytes.iter().rev() {
let int_term = Term::small_int(i64::from(byte));
let cell = Box::leak(Box::new([0u64; 2]));
tail = crate::term::boxed::write_cons(cell, int_term, tail).ok_or_else(badarg)?;
}
Ok(tail)
}
pub fn bif_byte_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term] = args else {
return Err(badarg());
};
binary_size(*binary_term)
}
pub fn bif_iolist_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term] = args else {
return Err(badarg());
};
binary_size(*binary_term)
}
fn binary_size(term: Term) -> Result<Term, Term> {
let binary = Binary::new(term).ok_or_else(badarg)?;
i64::try_from(binary.len())
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_list_append(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [list_a, list_b] = args else {
return Err(badarg());
};
if list_a.is_nil() {
return Ok(*list_b);
}
let mut elements = Vec::new();
let mut current = *list_a;
loop {
if current.is_nil() {
break;
}
let cons = Cons::new(current).ok_or_else(badarg)?;
elements.push(cons.head());
current = cons.tail();
}
let mut tail = *list_b;
for element in elements.into_iter().rev() {
let cell = Box::leak(Box::new([0u64; 2]));
tail = crate::term::boxed::write_cons(cell, element, tail).ok_or_else(badarg)?;
}
Ok(tail)
}
pub fn bif_not(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
let atom = term.as_atom().ok_or_else(badarg)?;
if atom == Atom::TRUE {
Ok(Term::atom(Atom::FALSE))
} else if atom == Atom::FALSE {
Ok(Term::atom(Atom::TRUE))
} else {
Err(badarg())
}
}
pub fn bif_not_equal(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [left, right] = args else {
return Err(badarg());
};
Ok(bool_term(left != right))
}
pub fn bif_length(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [list_term] = args else {
return Err(badarg());
};
let mut count: i64 = 0;
let mut current = *list_term;
loop {
if current.is_nil() {
return Term::try_small_int(count).ok_or_else(badarg);
}
let cons = Cons::new(current).ok_or_else(badarg)?;
count = count.checked_add(1).ok_or_else(badarg)?;
current = cons.tail();
}
}
fn bool_term(value: bool) -> Term {
Term::atom(if value { Atom::TRUE } else { Atom::FALSE })
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}
#[cfg(test)]
#[path = "additional_tests.rs"]
mod additional_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
#[path = "demonitor2_tests.rs"]
mod demonitor2_tests;