use crate::atom::{Atom, AtomTable};
use crate::native::stdlib_stubs::maps_bifs::ContinuationStep;
use crate::native::{NativeContinuation, ProcessContext};
use crate::term::Term;
use crate::term::boxed::{Closure, Tuple};
use crate::term::format::format_term;
static NONE_ATOM: std::sync::OnceLock<Atom> = std::sync::OnceLock::new();
static SOME_ATOM: std::sync::OnceLock<Atom> = std::sync::OnceLock::new();
#[derive(Clone, Debug)]
pub enum GleamOptionState {
Map { some_tag: Atom },
}
#[derive(Clone, Debug)]
pub enum GleamResultState {
MapError,
Then,
}
impl GleamOptionState {
pub(crate) fn for_each_term(&self, _f: &mut dyn FnMut(Term)) {
match self {
Self::Map { .. } => {}
}
}
pub(crate) fn for_each_term_mut(&mut self, _f: &mut dyn FnMut(&mut Term)) {
match self {
Self::Map { .. } => {}
}
}
}
impl GleamResultState {
pub(crate) fn for_each_term(&self, _f: &mut dyn FnMut(Term)) {
match self {
Self::MapError | Self::Then => {}
}
}
pub(crate) fn for_each_term_mut(&mut self, _f: &mut dyn FnMut(&mut Term)) {
match self {
Self::MapError | Self::Then => {}
}
}
}
pub fn init_gleam_atoms(atom_table: &AtomTable) {
let _ = NONE_ATOM.set(atom_table.intern("None"));
let _ = SOME_ATOM.set(atom_table.intern("Some"));
}
fn none_atom_index() -> Option<u32> {
NONE_ATOM.get().map(|atom| atom.index())
}
fn some_atom_index() -> Option<u32> {
SOME_ATOM.get().map(|atom| atom.index())
}
pub fn bif_dynamic_classify(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
let description = if term.as_small_int().is_some() {
"Int"
} else if term.as_atom().is_some() {
"Atom"
} else if term.is_nil() || term.is_list() {
"List"
} else if term.is_pid() {
"Pid"
} else {
"Other"
};
context
.alloc_binary(description.as_bytes())
.or_else(|_| context.alloc_tuple(&[Term::atom(Atom::OK), Term::atom(Atom::NIL)]))
}
pub fn bif_dynamic_int(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
if term.as_small_int().is_some() {
{
let process = context.process_mut().ok_or_else(badarg)?;
process.set_x_reg(0, *term);
}
context.ensure_heap_space(3)?;
let term = context.process_mut().ok_or_else(badarg)?.x_reg(0);
context.alloc_tuple_prereserved(&[Term::atom(Atom::OK), term])
} else {
context.alloc_tuple(&[Term::atom(Atom::ERROR), Term::NIL])
}
}
pub fn bif_dynamic_string(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
if crate::term::binary::Binary::new(*term).is_some() {
{
let process = context.process_mut().ok_or_else(badarg)?;
process.set_x_reg(0, *term);
}
context.ensure_heap_space(3)?;
let term = context.process_mut().ok_or_else(badarg)?.x_reg(0);
context.alloc_tuple_prereserved(&[Term::atom(Atom::OK), term])
} else {
context.alloc_tuple(&[Term::atom(Atom::ERROR), Term::NIL])
}
}
pub fn bif_string_inspect(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
let fallback = AtomTable::with_common_atoms();
let atom_table = context.atom_table().unwrap_or(&fallback);
let repr = format_term(*term, atom_table);
context.alloc_binary(repr.as_bytes())
}
pub fn bif_string_append(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [first, second] = args else {
return Err(badarg());
};
let a_bytes = crate::term::binary::Binary::new(*first)
.map(|b| b.as_bytes().to_vec())
.unwrap_or_default();
let b_bytes = crate::term::binary::Binary::new(*second)
.map(|b| b.as_bytes().to_vec())
.unwrap_or_default();
let mut combined = a_bytes;
combined.extend_from_slice(&b_bytes);
context.alloc_binary(&combined)
}
pub fn bif_option_map(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [option, fun] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 1)?;
if is_option_none(*option) {
return Ok(*option);
}
let (some_tag, value) = option_some(*option, context)?;
context.set_continuation_trampoline(
*fun,
vec![value],
NativeContinuation::GleamOption(GleamOptionState::Map { some_tag }),
);
Ok(Term::NIL)
}
pub fn bif_option_unwrap(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [option, default] = args else {
return Err(badarg());
};
if is_option_none(*option) {
return Ok(*default);
}
if let Ok((_, value)) = option_some(*option, context) {
return Ok(value);
}
Ok(*option)
}
pub fn bif_result_map_error(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [result, fun] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 1)?;
let (tag, value) = result_tuple(*result)?;
if tag == Atom::OK {
return Ok(*result);
}
if tag != Atom::ERROR {
return Err(badarg());
}
context.set_continuation_trampoline(
*fun,
vec![value],
NativeContinuation::GleamResult(GleamResultState::MapError),
);
Ok(Term::NIL)
}
pub fn bif_result_then(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [result, fun] = args else {
return Err(badarg());
};
ensure_fun_arity(*fun, 1)?;
let (tag, value) = result_tuple(*result)?;
if tag == Atom::ERROR {
return Ok(*result);
}
if tag != Atom::OK {
return Err(badarg());
}
context.set_continuation_trampoline(
*fun,
vec![value],
NativeContinuation::GleamResult(GleamResultState::Then),
);
Ok(Term::NIL)
}
pub fn bif_intensity_tracker_new(
args: &[Term],
context: &mut ProcessContext,
) -> Result<Term, Term> {
let [limit, period] = args else {
return Err(badarg());
};
context.alloc_tuple(&[Term::small_int(0), *limit, *period, Term::NIL])
}
pub fn bif_intensity_tracker_add_event(
args: &[Term],
context: &mut ProcessContext,
) -> Result<Term, Term> {
let [tracker] = args else {
return Err(badarg());
};
let tuple = Tuple::new(*tracker).ok_or_else(badarg)?;
if tuple.arity() != 4 {
return Err(badarg());
}
let count = tuple
.get(0)
.and_then(Term::as_small_int)
.ok_or_else(badarg)?;
let limit = tuple
.get(1)
.and_then(Term::as_small_int)
.ok_or_else(badarg)?;
let _period = tuple.get(2).ok_or_else(badarg)?;
let _events = tuple.get(3).ok_or_else(badarg)?;
let new_count = count.checked_add(1).ok_or_else(badarg)?;
let new_count_term = Term::try_small_int(new_count).ok_or_else(badarg)?;
{
let process = context.process_mut().ok_or_else(badarg)?;
process.set_x_reg(0, *tracker);
}
context.ensure_heap_space(5 + 3)?;
let tracker = context.process_mut().ok_or_else(badarg)?.x_reg(0);
let tuple = Tuple::new(tracker).ok_or_else(badarg)?;
let period = tuple.get(2).ok_or_else(badarg)?;
let events = tuple.get(3).ok_or_else(badarg)?;
let updated = context.alloc_tuple_prereserved(&[
new_count_term,
Term::small_int(limit),
period,
events,
])?;
let status = if count < limit { Atom::OK } else { Atom::ERROR };
context.alloc_tuple_prereserved(&[Term::atom(status), updated])
}
pub fn resume_gleam_option_continuation(
state: GleamOptionState,
closure_result: Term,
context: &mut ProcessContext,
) -> Result<ContinuationStep, Term> {
match state {
GleamOptionState::Map { some_tag } => {
context.ensure_heap_space(3)?;
Ok(ContinuationStep::Done(context.alloc_tuple_prereserved(
&[Term::atom(some_tag), closure_result],
)?))
}
}
}
pub fn resume_gleam_result_continuation(
state: GleamResultState,
closure_result: Term,
context: &mut ProcessContext,
) -> Result<ContinuationStep, Term> {
match state {
GleamResultState::MapError => {
context.ensure_heap_space(3)?;
Ok(ContinuationStep::Done(context.alloc_tuple_prereserved(
&[Term::atom(Atom::ERROR), closure_result],
)?))
}
GleamResultState::Then => Ok(ContinuationStep::Done(closure_result)),
}
}
fn ensure_fun_arity(fun: Term, arity: u8) -> Result<(), Term> {
let closure = Closure::new(fun).ok_or_else(badarg)?;
if closure.arity() == arity {
Ok(())
} else {
Err(Term::atom(Atom::BADARITY))
}
}
fn is_option_none(option: Term) -> bool {
option.as_atom().is_some_and(|atom| {
atom == Atom::NIL || none_atom_index().is_some_and(|none_index| atom.index() == none_index)
})
}
fn option_some(option: Term, context: &ProcessContext) -> Result<(Atom, Term), Term> {
let tuple = Tuple::new(option).ok_or_else(badarg)?;
if tuple.arity() != 2 {
return Err(badarg());
}
let tag = tuple.get(0).and_then(Term::as_atom).ok_or_else(badarg)?;
if is_some_tag(tag, context) {
Ok((tag, tuple.get(1).ok_or_else(badarg)?))
} else {
Err(badarg())
}
}
fn is_some_tag(tag: Atom, context: &ProcessContext) -> bool {
tag == Atom::OK
|| some_atom_index().is_some_and(|some_index| tag.index() == some_index)
|| context.atom_table().is_some_and(|atom_table| {
atom_table
.lookup("Some")
.or_else(|| atom_table.lookup("some"))
.is_some_and(|some| some == tag)
})
}
fn result_tuple(result: Term) -> Result<(Atom, Term), Term> {
let tuple = Tuple::new(result).ok_or_else(badarg)?;
if tuple.arity() != 2 {
return Err(badarg());
}
let tag = tuple.get(0).and_then(Term::as_atom).ok_or_else(badarg)?;
let value = tuple.get(1).ok_or_else(badarg)?;
Ok((tag, value))
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}