extern crate alloc;
use core::fmt;
use core::str;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AtomIndex(pub u32);
impl AtomIndex {
pub const INVALID: AtomIndex = AtomIndex(0);
pub fn new(index: u32) -> Self {
AtomIndex(index)
}
pub fn get(self) -> u32 {
self.0
}
pub fn is_valid(self) -> bool {
self.0 != 0
}
}
#[repr(u32)]
pub enum AtomCopyOpt {
Reference = 0,
Copy = 1,
AlreadyExisting = 2,
}
#[repr(u32)]
pub enum EnsureAtomsOpt {
Standard = 0,
LongEncoding = 1,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtomError {
NotFound,
AllocationFailed,
InvalidLength,
InvalidAtomData,
NullPointer,
InvalidIndex,
}
impl fmt::Display for AtomError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AtomError::NotFound => write!(f, "atom not found in table"),
AtomError::AllocationFailed => write!(f, "memory allocation failed"),
AtomError::InvalidLength => write!(f, "invalid atom length"),
AtomError::InvalidAtomData => write!(f, "invalid atom data or encoding"),
AtomError::NullPointer => write!(f, "unexpected null pointer from atom table"),
AtomError::InvalidIndex => write!(f, "invalid atom index"),
}
}
}
#[derive(Debug)]
pub struct AtomRef<'a> {
data: &'a [u8],
index: AtomIndex,
}
impl<'a> AtomRef<'a> {
pub fn new(data: &'a [u8], index: AtomIndex) -> Self {
Self { data, index }
}
pub fn index(&self) -> AtomIndex {
self.index
}
pub fn as_bytes(&self) -> &[u8] {
self.data
}
pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.data)
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl<'a> AsRef<[u8]> for AtomRef<'a> {
fn as_ref(&self) -> &[u8] {
self.data
}
}
impl<'a> PartialEq<[u8]> for AtomRef<'a> {
fn eq(&self, other: &[u8]) -> bool {
self.data == other
}
}
impl<'a> PartialEq<&[u8]> for AtomRef<'a> {
fn eq(&self, other: &&[u8]) -> bool {
self.data == *other
}
}
impl<'a> PartialEq<str> for AtomRef<'a> {
fn eq(&self, other: &str) -> bool {
self.data == other.as_bytes()
}
}
pub trait AtomTableOps {
fn count(&self) -> usize;
fn get_atom_string(&self, index: AtomIndex) -> Result<AtomRef<'_>, AtomError>;
fn ensure_atom(&self, atom_data: &[u8]) -> Result<AtomIndex, AtomError>;
fn find_atom(&self, atom_data: &[u8]) -> Result<AtomIndex, AtomError>;
fn ensure_atom_str(&self, atom_str: &str) -> Result<AtomIndex, AtomError> {
self.ensure_atom(atom_str.as_bytes())
}
fn find_atom_str(&self, atom_str: &str) -> Result<AtomIndex, AtomError> {
self.find_atom(atom_str.as_bytes())
}
fn atom_equals(&self, atom_index: AtomIndex, data: &[u8]) -> bool;
fn atom_equals_str(&self, atom_index: AtomIndex, s: &str) -> bool {
self.atom_equals(atom_index, s.as_bytes())
}
fn compare_atoms(&self, atom1: AtomIndex, atom2: AtomIndex) -> i32;
fn ensure_atoms_bulk(
&self,
atoms_data: &[u8],
count: usize,
encoding: EnsureAtomsOpt,
) -> Result<Vec<AtomIndex>, AtomError>;
}
use core::ffi::c_void;
use core::slice;
#[repr(transparent)]
pub struct AtomTable(*mut c_void);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AtomTableResult {
Ok,
NotFound,
AllocationFailed,
InvalidLength,
}
extern "C" {
fn atom_table_get_atom_string(
table: *mut c_void,
index: u32, out_size: *mut usize,
) -> *const u8;
fn atom_table_ensure_atom(
table: *mut c_void,
atom_data: *const u8,
atom_len: usize,
opts: u32,
result: *mut u32, ) -> u32;
fn atom_table_ensure_atoms(
table: *mut c_void,
atoms: *const c_void,
count: usize,
translate_table: *mut u32, opt: u32,
) -> u32;
fn atom_table_count(table: *mut c_void) -> usize;
fn atom_table_is_equal_to_atom_string(
table: *mut c_void,
atom_index: u32, string_data: *const u8,
string_len: usize,
) -> bool;
fn atom_table_cmp_using_atom_index(
table: *mut c_void,
atom1: u32, atom2: u32, ) -> i32;
fn atomvm_get_global_atom_table() -> *mut c_void;
}
fn result_from_c(result: u32) -> AtomTableResult {
match result {
0 => AtomTableResult::Ok,
1 => AtomTableResult::NotFound,
2 => AtomTableResult::AllocationFailed,
3 => AtomTableResult::InvalidLength,
_ => AtomTableResult::AllocationFailed,
}
}
impl AtomTable {
pub unsafe fn from_raw(ptr: *mut c_void) -> Self {
AtomTable(ptr)
}
pub fn from_global() -> Self {
let ptr = unsafe { atomvm_get_global_atom_table() };
AtomTable(ptr)
}
pub unsafe fn as_raw(&self) -> *mut c_void {
self.0
}
}
impl AtomTableOps for AtomTable {
fn count(&self) -> usize {
unsafe { atom_table_count(self.0) }
}
fn get_atom_string(&self, index: AtomIndex) -> Result<AtomRef<'_>, AtomError> {
let mut size: usize = 0;
let ptr = unsafe { atom_table_get_atom_string(self.0, index.0, &mut size) };
if ptr.is_null() {
return Err(AtomError::InvalidIndex);
}
let data = unsafe { slice::from_raw_parts(ptr, size) };
Ok(AtomRef::new(data, index))
}
fn ensure_atom(&self, atom_data: &[u8]) -> Result<AtomIndex, AtomError> {
let mut result: u32 = 0; let status = unsafe {
atom_table_ensure_atom(
self.0,
atom_data.as_ptr(),
atom_data.len(),
AtomCopyOpt::Copy as u32,
&mut result,
)
};
match result_from_c(status) {
AtomTableResult::Ok => Ok(AtomIndex(result)),
AtomTableResult::NotFound => Err(AtomError::NotFound),
AtomTableResult::AllocationFailed => Err(AtomError::AllocationFailed),
AtomTableResult::InvalidLength => Err(AtomError::InvalidLength),
}
}
fn find_atom(&self, atom_data: &[u8]) -> Result<AtomIndex, AtomError> {
let mut result: u32 = 0; let status = unsafe {
atom_table_ensure_atom(
self.0,
atom_data.as_ptr(),
atom_data.len(),
AtomCopyOpt::AlreadyExisting as u32,
&mut result,
)
};
match result_from_c(status) {
AtomTableResult::Ok => Ok(AtomIndex(result)),
AtomTableResult::NotFound => Err(AtomError::NotFound),
AtomTableResult::AllocationFailed => Err(AtomError::AllocationFailed),
AtomTableResult::InvalidLength => Err(AtomError::InvalidLength),
}
}
fn atom_equals(&self, atom_index: AtomIndex, data: &[u8]) -> bool {
unsafe {
atom_table_is_equal_to_atom_string(
self.0,
atom_index.0, data.as_ptr(),
data.len(),
)
}
}
fn compare_atoms(&self, atom1: AtomIndex, atom2: AtomIndex) -> i32 {
unsafe { atom_table_cmp_using_atom_index(self.0, atom1.0, atom2.0) }
}
fn ensure_atoms_bulk(
&self,
atoms_data: &[u8],
count: usize,
encoding: EnsureAtomsOpt,
) -> Result<Vec<AtomIndex>, AtomError> {
let mut translate_table: Vec<u32> = Vec::with_capacity(count); translate_table.resize(count, 0u32);
let status = unsafe {
atom_table_ensure_atoms(
self.0,
atoms_data.as_ptr() as *const c_void,
count,
translate_table.as_mut_ptr(),
encoding as u32,
)
};
match result_from_c(status) {
AtomTableResult::Ok => {
let result: Vec<AtomIndex> = translate_table.into_iter().map(AtomIndex).collect();
Ok(result)
}
AtomTableResult::NotFound => Err(AtomError::NotFound),
AtomTableResult::AllocationFailed => Err(AtomError::AllocationFailed),
AtomTableResult::InvalidLength => Err(AtomError::InvalidLength),
}
}
}
unsafe impl Send for AtomTable {}
unsafe impl Sync for AtomTable {}
pub mod atoms {
use super::*;
pub fn ensure_common_atoms<T: AtomTableOps>(table: &T) -> Result<(), AtomError> {
let common_atoms = [
"ok", "error", "true", "false", "undefined", "badarg", "nil",
"atom", "binary", "bitstring", "boolean", "float", "function",
"integer", "list", "map", "pid", "port", "reference", "tuple"
];
for atom_name in &common_atoms {
table.ensure_atom_str(atom_name)?;
}
Ok(())
}
pub fn ok<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("ok")
}
pub fn error<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("error")
}
pub fn true_atom<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("true")
}
pub fn false_atom<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("false")
}
pub fn nil<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("nil")
}
pub fn undefined<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("undefined")
}
pub fn badarg<T: AtomTableOps>(table: &T) -> Result<AtomIndex, AtomError> {
table.ensure_atom_str("badarg")
}
}