use std::ffi::{CStr, CString};
use std::fmt::{self, Display};
use std::ptr;
use crate::error::{InstanceError, PdError, StringConversionError};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Atom {
Float(f64),
Symbol(String),
}
impl Atom {
pub fn to_t_atom(&self) -> Result<libpd_sys::t_atom, PdError> {
match self {
Self::Float(value) => {
let mut t_atom = libpd_sys::t_atom {
a_type: libpd_sys::t_atomtype_A_FLOAT,
a_w: libpd_sys::word { w_float: 0.0 },
};
let p = &mut t_atom as *mut libpd_sys::t_atom;
unsafe {
libpd_sys::libpd_set_double(p, *value);
}
Ok(t_atom)
}
Self::Symbol(s) => {
if unsafe { libpd_sys::libpd_this_instance().is_null() } {
return Err(InstanceError::NoCurrentInstanceSet.into());
}
let c_str = CString::new(s.as_str()).map_err(StringConversionError::from)?;
let sym_ptr = unsafe { libpd_sys::gensym(c_str.as_ptr()) };
let t_atom = libpd_sys::t_atom {
a_type: libpd_sys::t_atomtype_A_SYMBOL,
a_w: libpd_sys::word { w_symbol: sym_ptr },
};
Ok(t_atom)
}
}
}
pub fn from_t_atom(t_atom: &libpd_sys::t_atom) -> Option<Self> {
match t_atom.a_type {
libpd_sys::t_atomtype_A_FLOAT => {
let p = ptr::from_ref::<libpd_sys::t_atom>(t_atom).cast_mut();
let value = unsafe { libpd_sys::libpd_get_double(p) };
Some(Self::Float(value))
}
libpd_sys::t_atomtype_A_SYMBOL => {
let p = ptr::from_ref::<libpd_sys::t_atom>(t_atom).cast_mut();
let sym_ptr = unsafe { libpd_sys::libpd_get_symbol(p) };
if sym_ptr.is_null() {
None
} else {
let c_str = unsafe { CStr::from_ptr(sym_ptr) };
c_str.to_str().ok().map(|s| Self::Symbol(s.to_owned()))
}
}
_ => None,
}
}
}
impl Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Float(value) => write!(f, "{value}"),
Self::Symbol(s) => write!(f, "{s}"),
}
}
}
impl From<String> for Atom {
fn from(s: String) -> Self {
Self::Symbol(s)
}
}
impl From<&String> for Atom {
fn from(s: &String) -> Self {
Self::Symbol(s.clone())
}
}
impl From<&str> for Atom {
fn from(s: &str) -> Self {
Self::Symbol(s.to_owned())
}
}
impl From<char> for Atom {
fn from(c: char) -> Self {
Self::Symbol(c.to_string())
}
}
impl From<&char> for Atom {
fn from(c: &char) -> Self {
Self::Symbol(c.to_string())
}
}
macro_rules! atom_from_number_type {
($type:ty) => {
impl From<$type> for Atom {
fn from(value: $type) -> Self {
Self::Float(value.into())
}
}
};
}
macro_rules! atom_from_reference_number_type {
($type:ty) => {
impl From<$type> for Atom {
fn from(value: $type) -> Self {
Self::Float((*value).into())
}
}
};
}
atom_from_number_type!(i8);
atom_from_number_type!(i16);
atom_from_number_type!(i32);
atom_from_number_type!(u8);
atom_from_number_type!(u16);
atom_from_number_type!(u32);
atom_from_number_type!(f32);
atom_from_number_type!(f64);
atom_from_reference_number_type!(&i8);
atom_from_reference_number_type!(&i16);
atom_from_reference_number_type!(&i32);
atom_from_reference_number_type!(&u8);
atom_from_reference_number_type!(&u16);
atom_from_reference_number_type!(&u32);
atom_from_reference_number_type!(&f32);
atom_from_reference_number_type!(&f64);
pub fn make_t_atom_list_from_atom_list(atoms: &[Atom]) -> Result<Vec<libpd_sys::t_atom>, PdError> {
atoms.iter().map(Atom::to_t_atom).collect()
}
pub fn make_atom_list_from_t_atom_list(t_atoms: &[libpd_sys::t_atom]) -> Vec<Atom> {
t_atoms.iter().filter_map(Atom::from_t_atom).collect()
}
#[cfg(test)]
mod tests {
#![allow(clippy::restriction, clippy::nursery, clippy::all, clippy::pedantic)]
use crate::instance::PdInstance;
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn test_float_conversion() {
let original_atom = Atom::Float(3.14);
let t_atom = original_atom
.to_t_atom()
.expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(original_atom, converted_atom);
}
#[test]
#[serial]
fn test_symbol_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let original_atom = Atom::Symbol("test_symbol".to_string());
let t_atom = original_atom
.to_t_atom()
.expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(original_atom, converted_atom);
}
#[test]
#[serial]
fn test_atom_list_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let original_atoms = vec![
Atom::Float(1.23),
Atom::Symbol("hello".to_string()),
Atom::Float(4.56),
Atom::Symbol("world".to_string()),
];
let t_atoms = make_t_atom_list_from_atom_list(&original_atoms)
.expect("Conversion to t_atom list failed");
let converted_atoms = make_atom_list_from_t_atom_list(&t_atoms);
assert_eq!(original_atoms, converted_atoms);
}
#[test]
#[serial]
fn test_empty_atom_list_conversion() {
let original_atoms: Vec<Atom> = Vec::new();
let t_atoms = make_t_atom_list_from_atom_list(&original_atoms)
.expect("Conversion to t_atom list failed");
let converted_atoms = make_atom_list_from_t_atom_list(&t_atoms);
assert_eq!(original_atoms, converted_atoms);
}
#[test]
#[serial]
fn test_float_reference_conversion() {
let value = 2.718;
let atom = Atom::from(&value);
assert_eq!(atom, Atom::Float(2.718));
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
#[test]
#[serial]
fn test_symbol_reference_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let value = "reference_symbol";
let atom = Atom::from(value);
assert_eq!(atom, Atom::Symbol("reference_symbol".to_string()));
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
#[test]
#[serial]
fn test_char_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let value = 'a';
let atom = Atom::from(value);
assert_eq!(atom, Atom::Symbol("a".to_string()));
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
#[test]
#[serial]
fn test_number_type_conversions() {
let int_value: i32 = -42;
let atom = Atom::from(int_value);
assert_eq!(atom, Atom::Float(-42.0));
let uint_value: u32 = 42;
let atom = Atom::from(uint_value);
assert_eq!(atom, Atom::Float(42.0));
let float_value: f32 = 3.14;
let atom = Atom::from(float_value);
if let Atom::Float(value) = atom {
assert!(
(value - 3.14).abs() < 1e-6,
"Expected value close to 3.14, got {}",
value
);
} else {
panic!("Expected Atom::Float");
}
let double_value: f64 = 2.71828;
let atom = Atom::from(double_value);
if let Atom::Float(value) = atom {
assert!(
(value - 2.71828).abs() < 1e-10,
"Expected value close to 2.71828, got {}",
value
);
} else {
panic!("Expected Atom::Float");
}
}
#[test]
#[serial]
fn test_symbol_with_null_byte() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let symbol_with_null = "null\0byte";
let atom_result = Atom::from(symbol_with_null).to_t_atom();
assert!(atom_result.is_err());
}
#[test]
#[serial]
fn test_large_float_conversion() {
let value = 1e308; let atom = Atom::Float(value);
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
#[test]
#[serial]
fn test_unicode_symbol_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let symbol = "こんにちは"; let atom = Atom::Symbol(symbol.to_string());
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
#[test]
#[serial]
fn test_empty_symbol_conversion() {
let main_instance = PdInstance::new().expect("Failed to create Pd instance");
main_instance.set_as_current();
let atom = Atom::Symbol(String::new());
let t_atom = atom.to_t_atom().expect("Conversion to t_atom failed");
let converted_atom = Atom::from_t_atom(&t_atom).expect("Conversion from t_atom failed");
assert_eq!(atom, converted_atom);
}
}