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};
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,
}
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() {
context.alloc_tuple(&[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() {
context.alloc_tuple(&[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 repr = format!("{term:?}");
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 updated = context.alloc_tuple(&[new_count_term, Term::small_int(limit), period, events])?;
let status = if count < limit { Atom::OK } else { Atom::ERROR };
context.alloc_tuple(&[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 } => Ok(ContinuationStep::Done(
context.alloc_tuple(&[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 => Ok(ContinuationStep::Done(
context.alloc_tuple(&[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)
}