use crate::atom::{Atom, AtomTable};
use crate::native::links::LinkError;
use crate::native::{BifRegistryImpl, NativeFn, NativeRegistrationError, ProcessContext};
use crate::process::ExitReason;
use crate::term::Term;
use crate::term::boxed::Cons;
type Gate2Bif = (&'static str, u8, NativeFn);
const GATE2_BIFS: &[Gate2Bif] = &[
("self", 0, bif_self),
("spawn", 3, bif_spawn),
("spawn_link", 3, bif_spawn_link),
("link", 1, bif_link),
("unlink", 1, bif_unlink),
("process_flag", 2, bif_process_flag),
("monitor", 2, bif_monitor),
("demonitor", 1, bif_demonitor),
("exit", 2, bif_exit),
];
pub fn register_gate2_bifs(
registry: &mut BifRegistryImpl,
atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
let erlang = atom_table.intern("erlang");
for &(function_name, arity, native_function) in GATE2_BIFS {
let function = atom_table.intern(function_name);
registry.register(erlang, function, arity, native_function)?;
}
Ok(())
}
pub fn bif_self(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let pid = context.pid().ok_or_else(badarg)?;
Term::try_pid(pid).ok_or_else(badarg)
}
pub fn bif_spawn(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_impl(args, context, false)
}
pub fn bif_spawn_link(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_impl(args, context, true)
}
pub fn bif_link(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [target_term] = args else {
return Err(badarg());
};
let target_pid = target_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
if caller_pid == target_pid {
return Ok(Term::atom(Atom::TRUE));
}
let facility = context.link_facility().ok_or_else(badarg)?;
match facility.link(caller_pid, target_pid) {
Ok(()) => Ok(Term::atom(Atom::TRUE)),
Err(LinkError::NoProc) => Err(Term::atom(Atom::NOPROC)),
Err(LinkError::NoCaller) => Err(badarg()),
}
}
pub fn bif_unlink(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [target_term] = args else {
return Err(badarg());
};
let target_pid = target_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
if caller_pid == target_pid {
return Ok(Term::atom(Atom::TRUE));
}
let facility = context.link_facility().ok_or_else(badarg)?;
facility
.unlink(caller_pid, target_pid)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
pub fn bif_process_flag(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [flag_term, value_term] = args else {
return Err(badarg());
};
let flag = flag_term.as_atom().ok_or_else(badarg)?;
if flag == Atom::TRAP_EXIT {
let new_value = atom_to_bool(*value_term).ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.link_facility().ok_or_else(badarg)?;
let old_value = facility
.set_trap_exit(caller_pid, new_value)
.map_err(|_| badarg())?;
Ok(bool_to_atom(old_value))
} else {
Err(badarg())
}
}
pub fn bif_monitor(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [type_term, pid_term] = args else {
return Err(badarg());
};
let type_atom = type_term.as_atom().ok_or_else(badarg)?;
if type_atom != Atom::PROCESS {
return Err(badarg());
}
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
let result = facility
.monitor(caller_pid, target_pid)
.map_err(|_| badarg())?;
Term::try_small_int(result.reference as i64).ok_or_else(badarg)
}
pub fn bif_demonitor(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [ref_term] = args else {
return Err(badarg());
};
let reference = ref_term.as_small_int().ok_or_else(badarg)?;
if reference < 0 {
return Err(badarg());
}
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
facility
.demonitor(caller_pid, reference as u64)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
pub fn bif_exit(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term, reason_term] = args else {
return Err(badarg());
};
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let reason = exit_reason_from_term(*reason_term)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
facility
.exit_signal(caller_pid, target_pid, reason)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
fn exit_reason_from_term(term: Term) -> Result<ExitReason, Term> {
let atom = term.as_atom().ok_or_else(badarg)?;
match atom {
Atom::NORMAL => Ok(ExitReason::Normal),
Atom::KILL => Ok(ExitReason::Kill),
Atom::KILLED => Ok(ExitReason::Killed),
Atom::ERROR => Ok(ExitReason::Error),
_ => Err(badarg()),
}
}
fn spawn_impl(args: &[Term], context: &mut ProcessContext, link: bool) -> Result<Term, Term> {
let [module_term, function_term, args_term] = args else {
return Err(badarg());
};
let module = module_term.as_atom().ok_or_else(badarg)?;
let function = function_term.as_atom().ok_or_else(badarg)?;
let spawn_args = list_to_vec(*args_term)?;
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(module, function, spawn_args, link_to)
.map_err(|_| badarg())?;
Term::try_pid(new_pid).ok_or_else(badarg)
}
fn list_to_vec(term: Term) -> Result<Vec<Term>, Term> {
let mut elements = Vec::new();
let mut current = term;
loop {
if current.is_nil() {
return Ok(elements);
}
let cons = Cons::new(current).ok_or_else(badarg)?;
elements.push(cons.head());
current = cons.tail();
}
}
fn atom_to_bool(term: Term) -> Option<bool> {
let atom = term.as_atom()?;
if atom == Atom::TRUE {
Some(true)
} else if atom == Atom::FALSE {
Some(false)
} else {
None
}
}
const fn bool_to_atom(value: bool) -> Term {
if value {
Term::atom(Atom::TRUE)
} else {
Term::atom(Atom::FALSE)
}
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}
#[cfg(test)]
mod tests;