use std::ffi::{c_char, c_uint};
use std::sync::OnceLock;
use crate::types::sealed::Sealed;
use crate::types::{Env, FreeTerm, RawTerm, Term};
#[derive(Clone, Copy)]
pub struct Atom {
pub(crate) term: RawTerm,
}
impl Atom {
pub fn intern<'id>(env: impl Env<'id>, name: &str) -> Result<Atom, AtomError> {
let mut term: RawTerm = 0;
let ok = unsafe {
enif_ffi::make_new_atom_len(
env.raw_env(),
name.as_ptr() as *const c_char,
name.len(),
&mut term,
enif_ffi::CharEncoding::Utf8,
)
};
if ok != 0 { Ok(Atom { term }) } else { Err(AtomError::NameTooLong) }
}
pub fn try_existing<'id>(env: impl Env<'id>, name: &str) -> Option<Atom> {
let mut term: RawTerm = 0;
let ok = unsafe {
enif_ffi::make_existing_atom_len(
env.raw_env(),
name.as_ptr() as *const c_char,
name.len(),
&mut term,
enif_ffi::CharEncoding::Utf8,
)
};
(ok != 0).then_some(Atom { term })
}
#[crate::raw]
pub(crate) fn from_raw(term: RawTerm) -> Atom {
Atom { term }
}
pub fn is_atom<'id>(env: impl Env<'id>, term: impl Term<'id>) -> bool {
unsafe { enif_ffi::is_atom(env.raw_env(), term.raw_term()) != 0 }
}
pub fn name<'id>(self, env: impl Env<'id>) -> String {
let mut len: c_uint = 0;
let ok = unsafe {
enif_ffi::get_atom_length(env.raw_env(), self.term, &mut len, enif_ffi::CharEncoding::Utf8)
};
assert!(ok != 0, "enif_get_atom_length failed for a validated Atom");
let mut buf = vec![0u8; len as usize + 1];
let written = unsafe {
enif_ffi::get_atom(
env.raw_env(),
self.term,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_uint,
enif_ffi::CharEncoding::Utf8,
)
};
assert!(written > 0, "enif_get_atom failed for a validated Atom");
buf.truncate((written - 1) as usize); unsafe { String::from_utf8_unchecked(buf) }
}
}
impl PartialEq for Atom {
fn eq(&self, other: &Self) -> bool {
unsafe { enif_ffi::is_identical(self.term, other.term) != 0 }
}
}
impl Eq for Atom {}
impl PartialOrd for Atom {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Atom {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let c = unsafe { enif_ffi::compare(self.term, other.term) };
c.cmp(&0)
}
}
impl std::fmt::Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Atom")
}
}
impl Sealed for Atom {}
impl<'id> Term<'id> for Atom {
fn raw_term(self) -> RawTerm {
self.term
}
}
impl FreeTerm for Atom {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtomError {
NameTooLong,
}
impl std::fmt::Display for AtomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AtomError::NameTooLong => write!(f, "atom name exceeds 255 characters"),
}
}
}
impl std::error::Error for AtomError {}
pub struct StaticAtom {
name: &'static str,
atom: OnceLock<Atom>,
}
impl StaticAtom {
pub const fn new(name: &'static str) -> Self {
Self { name, atom: OnceLock::new() }
}
pub fn init<'id>(&self, env: impl Env<'id>) -> Result<(), AtomError> {
let atom = Atom::intern(env, self.name)?;
let _ = self.atom.set(atom);
Ok(())
}
#[inline]
pub fn get(&self) -> Atom {
*self.atom.get().expect("StaticAtom::get called before init")
}
}