#[cfg(feature = "nif_version_2_17")]
use crate::sys::enif_make_new_atom_len;
use crate::sys::{
c_char, c_uint, enif_get_atom, enif_get_atom_length, enif_make_existing_atom_len,
ErlNifCharEncoding, ErlNifEnv, ErlNifTerm,
};
#[cfg(not(feature = "nif_version_2_17"))]
use crate::sys::{enif_is_exception, enif_make_atom_len};
use crate::{Decoder, Encoder, Env, Error, NifResult, Term};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::mem::MaybeUninit;
#[derive(PartialEq, Eq, Clone, Copy)]
#[repr(transparent)]
pub struct Atom {
term: ErlNifTerm,
}
impl Atom {
pub fn as_c_arg(self) -> ErlNifTerm {
self.term
}
pub fn to_term(self, env: Env) -> Term {
unsafe { Term::new(env, self.term) }
}
unsafe fn from_nif_term(term: ErlNifTerm) -> Self {
Self { term }
}
pub fn from_term(term: Term) -> NifResult<Self> {
if term.is_atom() {
Ok(unsafe { Self::from_nif_term(term.as_c_arg()) })
} else {
Err(Error::BadArg)
}
}
pub fn from_bytes(env: Env, bytes: &[u8]) -> NifResult<Self> {
Self::from_encoded_bytes(env, bytes, ErlNifCharEncoding::ERL_NIF_LATIN1)
}
pub fn from_utf8_bytes(env: Env, bytes: &[u8]) -> NifResult<Self> {
use ErlNifCharEncoding::*;
#[cfg(feature = "nif_version_2_17")]
{
Self::from_encoded_bytes(env, bytes, ERL_NIF_UTF8)
}
#[cfg(not(feature = "nif_version_2_17"))]
{
let string = std::str::from_utf8(bytes).map_err(|_| Error::BadArg)?;
let mut latin1 = Vec::with_capacity(string.len());
for c in string.chars() {
if (c as u32) >= 256 {
return Err(Error::BadArg);
}
latin1.push(c as u8);
}
Self::from_encoded_bytes(env, &latin1, ERL_NIF_LATIN1)
}
}
fn from_encoded_bytes(env: Env, bytes: &[u8], encoding: ErlNifCharEncoding) -> NifResult<Self> {
unsafe { make_atom(env.as_c_arg(), bytes, encoding).map(|term| Self::from_nif_term(term)) }
}
pub fn existing_from_bytes(env: Env, bytes: &[u8]) -> NifResult<Self> {
Self::existing_from_encoded_bytes(env, bytes, ErlNifCharEncoding::ERL_NIF_LATIN1)
}
#[deprecated(since = "0.38.0", note = "Use existing_from_(utf8_)bytes")]
pub fn try_from_bytes(env: Env, bytes: &[u8]) -> NifResult<Option<Self>> {
Self::existing_from_bytes(env, bytes).map(Some)
}
pub fn existing_from_utf8_bytes(env: Env, bytes: &[u8]) -> NifResult<Self> {
use ErlNifCharEncoding::*;
#[cfg(feature = "nif_version_2_17")]
{
Self::existing_from_encoded_bytes(env, bytes, ERL_NIF_UTF8)
}
#[cfg(not(feature = "nif_version_2_17"))]
{
let string = std::str::from_utf8(bytes).map_err(|_| Error::BadArg)?;
let mut latin1 = Vec::with_capacity(string.len());
for c in string.chars() {
if (c as u32) >= 256 {
return Err(Error::BadArg);
}
latin1.push(c as u8);
}
Self::existing_from_encoded_bytes(env, &latin1, ERL_NIF_LATIN1)
}
}
fn existing_from_encoded_bytes(
env: Env,
bytes: &[u8],
encoding: ErlNifCharEncoding,
) -> NifResult<Self> {
unsafe {
make_existing_atom(env.as_c_arg(), bytes, encoding)
.map(|term| Self::from_nif_term(term))
}
}
pub fn from_str(env: Env, string: &str) -> NifResult<Self> {
Self::from_utf8_bytes(env, string.as_bytes())
}
pub fn existing_from_str(env: Env, string: &str) -> NifResult<Self> {
Self::existing_from_utf8_bytes(env, string.as_bytes())
}
#[deprecated(since = "0.38.0", note = "Use existing_from_str")]
pub fn try_from_str(env: Env, string: &str) -> NifResult<Option<Self>> {
Self::existing_from_str(env, string).map(Some)
}
}
impl fmt::Debug for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
crate::term::fmt(self.as_c_arg(), f)
}
}
impl Encoder for Atom {
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
self.to_term(env)
}
}
impl<'a> Decoder<'a> for Atom {
fn decode(term: Term<'a>) -> NifResult<Atom> {
Atom::from_term(term)
}
}
impl<'a> PartialEq<Term<'a>> for Atom {
fn eq(&self, other: &Term<'a>) -> bool {
self.as_c_arg() == other.as_c_arg()
}
}
impl Term<'_> {
pub fn atom_to_string(&self) -> NifResult<String> {
unsafe { get_atom(self.get_env().as_c_arg(), self.as_c_arg()) }
}
}
pub fn is_truthy(term: Term) -> bool {
!((term.as_c_arg() == false_().as_c_arg()) || (term.as_c_arg() == nil().as_c_arg()))
}
pub(in crate::types) fn decode_bool(term: Term) -> NifResult<bool> {
let as_c_arg = term.as_c_arg();
if as_c_arg == true_().as_c_arg() {
return Ok(true);
}
if as_c_arg == false_().as_c_arg() {
return Ok(false);
}
Err(Error::BadArg)
}
impl Hash for Atom {
fn hash<H: Hasher>(&self, state: &mut H) {
use crate::sys::{enif_hash, ErlNifHash};
let hash =
unsafe { enif_hash(ErlNifHash::ERL_NIF_INTERNAL_HASH, self.as_c_arg(), 0) as u32 };
state.write_u32(hash);
}
}
unsafe impl Sync for Atom {}
unsafe impl Send for Atom {}
#[macro_export]
macro_rules! atoms {
{
$(
$( #[$attr:meta] )*
$name:ident $( = $str:expr )?
),*$(,)?
} => {
#[allow(non_snake_case)]
struct RustlerAtoms {
$( $name : $crate::types::atom::Atom ),*
}
impl RustlerAtoms {
fn get() -> &'static Self {
use std::sync::OnceLock;
static RUSTLER_ATOMS: OnceLock<RustlerAtoms> = OnceLock::new();
RUSTLER_ATOMS.get_or_init(||
$crate::env::OwnedEnv::new().run(|env| {
RustlerAtoms {
$( $name: $crate::atoms!(@internal_make_atom(env, $name $( = $str)? )) ),*
}
})
)
}
}
$(
$( #[$attr] )*
pub fn $name() -> $crate::types::atom::Atom {
RustlerAtoms::get().$name
}
)*
};
{ @internal_make_atom($env:ident, $name:ident) } => {
$crate::atoms!(@internal_make_atom($env, $name = stringify!($name)))
};
{ @internal_make_atom($env:ident, $name:ident = $str:expr) } => {
$crate::types::atom::Atom::from_str($env, $str)
.expect("rustler::atoms!: bad atom string")
};
}
atoms! {
nif_panicked,
nil,
undefined,
ok,
error,
badarg,
false_ = "false",
true_ = "true",
__struct__,
first,
last,
step,
}
#[cfg(not(feature = "nif_version_2_17"))]
unsafe fn make_atom(
env: *mut ErlNifEnv,
name: &[u8],
_encoding: ErlNifCharEncoding,
) -> Result<ErlNifTerm, Error> {
let res = enif_make_atom_len(env, name.as_ptr() as *const c_char, name.len());
if enif_is_exception(env, res) != 0 {
Err(Error::BadArg)
} else {
Ok(res)
}
}
#[cfg(feature = "nif_version_2_17")]
unsafe fn make_atom(
env: *mut ErlNifEnv,
name: &[u8],
encoding: ErlNifCharEncoding,
) -> Result<ErlNifTerm, Error> {
let mut atom_out = MaybeUninit::uninit();
if enif_make_new_atom_len(
env,
name.as_ptr() as *const c_char,
name.len(),
atom_out.as_mut_ptr(),
encoding,
) != 0
{
Ok(atom_out.assume_init())
} else {
Err(Error::BadArg)
}
}
unsafe fn make_existing_atom(
env: *mut ErlNifEnv,
name: &[u8],
encoding: ErlNifCharEncoding,
) -> Result<ErlNifTerm, Error> {
let mut atom_out = MaybeUninit::uninit();
if enif_make_existing_atom_len(
env,
name.as_ptr() as *const c_char,
name.len(),
atom_out.as_mut_ptr(),
encoding,
) != 0
{
Ok(atom_out.assume_init())
} else {
Err(Error::BadArg)
}
}
#[cfg(feature = "nif_version_2_17")]
unsafe fn get_atom(env: *mut ErlNifEnv, term: ErlNifTerm) -> Result<String, Error> {
let mut len = 0;
let success = enif_get_atom_length(env, term, &mut len, ErlNifCharEncoding::ERL_NIF_UTF8);
if success == 0 {
return Err(Error::BadArg);
}
let mut string = String::with_capacity(len as usize + 1);
let bytes = string.as_mut_vec();
let nbytes = enif_get_atom(
env,
term,
bytes.as_mut_ptr() as *mut c_char,
len + 1,
ErlNifCharEncoding::ERL_NIF_UTF8,
);
assert!(nbytes as c_uint == len + 1);
bytes.set_len(len as usize);
Ok(string)
}
#[cfg(not(feature = "nif_version_2_17"))]
unsafe fn get_atom(env: *mut ErlNifEnv, term: ErlNifTerm) -> Result<String, Error> {
let mut len = 0;
let success = enif_get_atom_length(env, term, &mut len, ErlNifCharEncoding::ERL_NIF_LATIN1);
if success == 0 {
return Err(Error::BadArg);
}
let mut bytes: Vec<u8> = Vec::with_capacity(len as usize + 1);
let nbytes = enif_get_atom(
env,
term,
bytes.as_mut_ptr() as *mut c_char,
len + 1,
ErlNifCharEncoding::ERL_NIF_LATIN1,
);
assert!(nbytes as c_uint == len + 1);
bytes.set_len(len as usize);
let nonascii_count = bytes.iter().filter(|&&b| b >= 128).count();
if nonascii_count == 0 {
Ok(String::from_utf8_unchecked(bytes))
} else {
let mut out = String::with_capacity(bytes.len() + nonascii_count);
for b in bytes {
out.push(b as char);
}
Ok(out)
}
}