use std::time::Duration;
use crate::atom::{Atom, AtomTable};
use crate::native::{
BifRegistryImpl, Capability, NativeFn, NativeRegistrationError, ProcessContext,
};
use crate::scheduler::dirty::DirtySchedulerKind;
use crate::term::Term;
const GLEAM_NIL: Term = Term::atom(Atom::NIL);
type GleamBif = (
&'static str,
u8,
Capability,
Option<DirtySchedulerKind>,
NativeFn,
);
const GLEAM_PROCESS_BIFS: &[GleamBif] = &[
("trap_exits", 1, Capability::Pure, None, bif_trap_exits),
("link", 1, Capability::Pure, None, bif_gleam_link),
("demonitor", 1, Capability::Pure, None, bif_gleam_demonitor),
(
"sleep",
1,
Capability::Clock,
Some(DirtySchedulerKind::Io),
bif_sleep,
),
(
"sleep_forever",
0,
Capability::Clock,
None,
bif_sleep_forever,
),
(
"flush_messages",
0,
Capability::Pure,
None,
bif_flush_messages,
),
(
"register_process",
2,
Capability::Pure,
None,
bif_register_process,
),
(
"unregister_process",
1,
Capability::Pure,
None,
bif_unregister_process,
),
(
"process_named",
1,
Capability::Pure,
None,
bif_process_named,
),
(
"pid_from_dynamic",
1,
Capability::Pure,
None,
bif_pid_from_dynamic,
),
];
pub fn register_gleam_ffi_bifs(
registry: &BifRegistryImpl,
atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
let module = atom_table.intern("gleam_erlang_ffi");
for &(function_name, arity, capability, dirty_kind, native_function) in GLEAM_PROCESS_BIFS {
let function = atom_table.intern(function_name);
if let Some(kind) = dirty_kind {
registry.register_dirty(module, function, arity, native_function, kind, capability)?;
} else {
registry.register(module, function, arity, native_function, capability)?;
}
}
Ok(())
}
pub fn bif_trap_exits(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [bool_term] = args else {
return Err(badarg());
};
let value = atom_to_bool(*bool_term).ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.link_facility().ok_or_else(badarg)?;
facility
.set_trap_exit(caller_pid, value)
.map_err(|_| badarg())?;
Ok(GLEAM_NIL)
}
pub fn bif_gleam_link(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)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.link_facility().ok_or_else(badarg)?;
facility
.link(caller_pid, target_pid)
.map_err(|_| badarg())?;
Ok(GLEAM_NIL)
}
pub fn bif_gleam_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(GLEAM_NIL)
}
pub fn bif_sleep(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [ms_term] = args else {
return Err(badarg());
};
let ms = ms_term.as_small_int().ok_or_else(badarg)?;
if ms < 0 {
return Err(badarg());
}
std::thread::sleep(Duration::from_millis(ms as u64));
Ok(GLEAM_NIL)
}
pub fn bif_sleep_forever(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
loop {
std::thread::sleep(Duration::MAX);
}
}
pub fn bif_flush_messages(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
Ok(GLEAM_NIL)
}
pub fn bif_register_process(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [name_term, pid_term] = args else {
return Err(badarg());
};
let name = name_term.as_atom().ok_or_else(badarg)?;
let pid = pid_term.as_pid().ok_or_else(badarg)?;
let facility = context.registry_facility().ok_or_else(badarg)?;
facility.register(name, pid).map_err(|_| badarg())?;
Ok(GLEAM_NIL)
}
pub fn bif_unregister_process(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [name_term] = args else {
return Err(badarg());
};
let name = name_term.as_atom().ok_or_else(badarg)?;
let facility = context.registry_facility().ok_or_else(badarg)?;
facility.unregister(name).map_err(|_| badarg())?;
Ok(GLEAM_NIL)
}
pub fn bif_process_named(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [name_term] = args else {
return Err(badarg());
};
let name = name_term.as_atom().ok_or_else(badarg)?;
let facility = context.registry_facility().ok_or_else(badarg)?;
match facility.whereis(name) {
Some(pid) => {
let pid_term = Term::try_pid(pid).ok_or_else(badarg)?;
context.alloc_tuple(&[Term::atom(Atom::OK), pid_term])
}
None => context.alloc_tuple(&[Term::atom(Atom::ERROR), GLEAM_NIL]),
}
}
pub fn bif_pid_from_dynamic(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
if term.is_pid() {
context.alloc_tuple(&[Term::atom(Atom::OK), *term])
} else {
context.alloc_tuple(&[Term::atom(Atom::ERROR), GLEAM_NIL])
}
}
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
}
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}
#[cfg(test)]
#[path = "gleam_ffi_tests.rs"]
mod tests;