use std::ffi::{CStr, CString};
use savvy_ffi::{
R_NilValue, Rboolean_TRUE, Rf_getAttrib, Rf_isEnvironment, Rf_isFunction, Rf_isInteger,
Rf_isLogical, Rf_isNumeric, Rf_isReal, Rf_isString, Rf_type2char, Rf_xlength, EXTPTRSXP,
INTEGER, RAWSXP, SEXP, SEXPTYPE, TYPEOF, VECSXP,
};
use crate::{
EnvironmentSexp, ExternalPointerSexp, FunctionSexp, IntegerSexp, ListSexp, LogicalSexp,
NullSexp, ObjSexp, OwnedIntegerSexp, OwnedLogicalSexp, OwnedRawSexp, OwnedRealSexp,
OwnedStringSexp, RawSexp, RealSexp, StringSexp,
};
#[cfg(feature = "complex")]
use crate::{ComplexSexp, OwnedComplexSexp};
pub mod environment;
pub mod external_pointer;
pub mod function;
pub mod integer;
pub mod list;
pub mod logical;
pub mod na;
pub mod null;
pub mod obj;
pub mod raw;
pub mod real;
pub mod scalar;
pub mod string;
pub mod numeric;
pub mod utils;
#[cfg(feature = "complex")]
pub mod complex;
pub struct Sexp(pub SEXP);
impl Sexp {
pub fn is_null(&self) -> bool {
unsafe { self.0 == R_NilValue }
}
pub fn is_scalar_na(&self) -> bool {
unsafe { na::is_scalar_na(self.0) }
}
pub fn is_integer(&self) -> bool {
unsafe { Rf_isInteger(self.0) == Rboolean_TRUE }
}
pub fn is_real(&self) -> bool {
unsafe { Rf_isReal(self.0) == Rboolean_TRUE }
}
pub fn is_numeric(&self) -> bool {
unsafe { Rf_isNumeric(self.0) == Rboolean_TRUE && Rf_isLogical(self.0) != Rboolean_TRUE }
}
#[cfg(feature = "complex")]
pub fn is_complex(&self) -> bool {
unsafe { savvy_ffi::Rf_isComplex(self.0) == Rboolean_TRUE }
}
pub fn is_logical(&self) -> bool {
unsafe { Rf_isLogical(self.0) == Rboolean_TRUE }
}
pub fn is_raw(&self) -> bool {
unsafe { TYPEOF(self.0) as u32 == RAWSXP }
}
pub fn is_string(&self) -> bool {
unsafe { Rf_isString(self.0) == Rboolean_TRUE }
}
pub fn is_list(&self) -> bool {
unsafe { TYPEOF(self.0) as u32 == VECSXP }
}
pub fn is_external_pointer(&self) -> bool {
unsafe { TYPEOF(self.0) as u32 == EXTPTRSXP }
}
pub fn is_function(&self) -> bool {
unsafe { Rf_isFunction(self.0) == Rboolean_TRUE }
}
pub fn is_environment(&self) -> bool {
unsafe { Rf_isEnvironment(self.0) == Rboolean_TRUE }
}
pub fn is_obj(&self) -> bool {
unsafe { TYPEOF(self.0) == savvy_ffi::OBJSXP }
}
fn is_sexp_type(&self, sexptype: SEXPTYPE) -> bool {
match sexptype {
savvy_ffi::INTSXP => self.is_integer(),
savvy_ffi::REALSXP => self.is_real(),
#[cfg(feature = "complex")]
savvy_ffi::CPLXSXP => self.is_complex(),
savvy_ffi::LGLSXP => self.is_logical(),
savvy_ffi::RAWSXP => self.is_raw(),
savvy_ffi::STRSXP => self.is_string(),
savvy_ffi::VECSXP => self.is_list(),
savvy_ffi::EXTPTRSXP => self.is_external_pointer(),
savvy_ffi::CLOSXP | savvy_ffi::BUILTINSXP | savvy_ffi::SPECIALSXP => self.is_function(),
savvy_ffi::ENVSXP => self.is_environment(),
savvy_ffi::OBJSXP => self.is_obj(),
savvy_ffi::NILSXP => self.is_null(),
_ => false,
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn get_human_readable_type_name(&self) -> &'static str {
unsafe { get_human_readable_type_name(TYPEOF(self.0)) }
}
}
unsafe fn get_human_readable_type_name(sexptype: SEXPTYPE) -> &'static str {
if sexptype == savvy_ffi::OBJSXP {
return "S4/S7 object";
}
unsafe {
let c = Rf_type2char(sexptype);
CStr::from_ptr(c).to_str().unwrap()
}
}
macro_rules! impl_sexp_type_assert {
($self: ident, $sexptype: ident) => {
if $self.is_sexp_type(savvy_ffi::$sexptype) {
Ok(())
} else {
let expected =
unsafe { get_human_readable_type_name(savvy_ffi::$sexptype).to_string() };
let actual = $self.get_human_readable_type_name().to_string();
Err(crate::error::Error::UnexpectedType { expected, actual })
}
};
}
impl Sexp {
pub fn assert_null(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, NILSXP)
}
pub fn assert_integer(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, INTSXP)
}
pub fn assert_real(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, REALSXP)
}
#[cfg(feature = "complex")]
pub fn assert_complex(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, CPLXSXP)
}
pub fn assert_logical(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, LGLSXP)
}
pub fn assert_raw(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, RAWSXP)
}
pub fn assert_string(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, STRSXP)
}
pub fn assert_list(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, VECSXP)
}
pub fn assert_external_pointer(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, EXTPTRSXP)
}
pub fn assert_function(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, CLOSXP)
}
pub fn assert_environment(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, ENVSXP)
}
pub fn assert_obj(&self) -> crate::error::Result<()> {
impl_sexp_type_assert!(self, OBJSXP)
}
}
#[non_exhaustive]
pub enum TypedSexp {
Integer(IntegerSexp),
Real(RealSexp),
#[cfg(feature = "complex")]
Complex(ComplexSexp),
Logical(LogicalSexp),
Raw(RawSexp),
String(StringSexp),
List(ListSexp),
Null(NullSexp),
ExternalPointer(ExternalPointerSexp),
Function(FunctionSexp),
Environment(EnvironmentSexp),
Obj(ObjSexp),
Other(SEXP),
}
macro_rules! into_typed_sxp {
($ty: ty, $variant: ident) => {
impl From<$ty> for TypedSexp {
fn from(value: $ty) -> Self {
TypedSexp::$variant(value)
}
}
};
}
into_typed_sxp!(IntegerSexp, Integer);
into_typed_sxp!(RealSexp, Real);
#[cfg(feature = "complex")]
into_typed_sxp!(ComplexSexp, Complex);
into_typed_sxp!(LogicalSexp, Logical);
into_typed_sxp!(RawSexp, Raw);
into_typed_sxp!(StringSexp, String);
into_typed_sxp!(ListSexp, List);
into_typed_sxp!(ExternalPointerSexp, ExternalPointer);
into_typed_sxp!(FunctionSexp, Function);
into_typed_sxp!(EnvironmentSexp, Environment);
into_typed_sxp!(NullSexp, Null);
into_typed_sxp!(ObjSexp, Obj);
macro_rules! into_typed_sxp_owned {
($ty: ty, $variant: ident) => {
impl From<$ty> for TypedSexp {
fn from(value: $ty) -> Self {
TypedSexp::$variant(value.as_read_only())
}
}
};
}
into_typed_sxp_owned!(OwnedIntegerSexp, Integer);
into_typed_sxp_owned!(OwnedRealSexp, Real);
#[cfg(feature = "complex")]
into_typed_sxp_owned!(OwnedComplexSexp, Complex);
into_typed_sxp_owned!(OwnedLogicalSexp, Logical);
into_typed_sxp_owned!(OwnedRawSexp, Raw);
into_typed_sxp_owned!(OwnedStringSexp, String);
impl From<TypedSexp> for SEXP {
fn from(value: TypedSexp) -> Self {
match value {
TypedSexp::Null(_) => unsafe { savvy_ffi::R_NilValue },
TypedSexp::Integer(sxp) => sxp.inner(),
TypedSexp::Real(sxp) => sxp.inner(),
#[cfg(feature = "complex")]
TypedSexp::Complex(sxp) => sxp.inner(),
TypedSexp::Logical(sxp) => sxp.inner(),
TypedSexp::Raw(sxp) => sxp.inner(),
TypedSexp::String(sxp) => sxp.inner(),
TypedSexp::List(sxp) => sxp.inner(),
TypedSexp::ExternalPointer(sxp) => sxp.inner(),
TypedSexp::Function(sxp) => sxp.inner(),
TypedSexp::Environment(sxp) => sxp.inner(),
TypedSexp::Obj(sxp) => sxp.inner(),
TypedSexp::Other(sxp) => sxp,
}
}
}
impl Sexp {
pub fn into_typed(self) -> TypedSexp {
let ty = unsafe { TYPEOF(self.0) };
match ty {
savvy_ffi::INTSXP => TypedSexp::Integer(IntegerSexp(self.0)),
savvy_ffi::REALSXP => TypedSexp::Real(RealSexp(self.0)),
#[cfg(feature = "complex")]
savvy_ffi::CPLXSXP => TypedSexp::Complex(ComplexSexp(self.0)),
savvy_ffi::LGLSXP => TypedSexp::Logical(LogicalSexp(self.0)),
savvy_ffi::RAWSXP => TypedSexp::Raw(RawSexp(self.0)),
savvy_ffi::STRSXP => TypedSexp::String(StringSexp(self.0)),
savvy_ffi::VECSXP => TypedSexp::List(ListSexp(self.0)),
savvy_ffi::EXTPTRSXP => TypedSexp::ExternalPointer(ExternalPointerSexp(self.0)),
savvy_ffi::CLOSXP | savvy_ffi::BUILTINSXP | savvy_ffi::SPECIALSXP => {
TypedSexp::Function(FunctionSexp(self.0))
}
savvy_ffi::ENVSXP => TypedSexp::Environment(EnvironmentSexp(self.0)),
savvy_ffi::OBJSXP => TypedSexp::Obj(ObjSexp(self.0)),
savvy_ffi::NILSXP => TypedSexp::Null(NullSexp),
_ => TypedSexp::Other(self.0),
}
}
pub fn get_attrib(&self, attr: &str) -> crate::error::Result<Option<Sexp>> {
let attr_cstr = CString::new(attr)?;
let attr_sexp = unsafe {
crate::unwind_protect(|| {
savvy_ffi::Rf_getAttrib(self.0, savvy_ffi::Rf_install(attr_cstr.as_ptr()))
})?
};
if attr_sexp == unsafe { savvy_ffi::R_NilValue } {
Ok(None)
} else {
Ok(Some(Sexp(attr_sexp)))
}
}
unsafe fn get_string_attrib_by_symbol(&self, attr: SEXP) -> Option<Vec<&'static str>> {
let sexp = unsafe { savvy_ffi::Rf_getAttrib(self.0, attr) };
if sexp == unsafe { savvy_ffi::R_NilValue } {
None
} else {
Some(crate::StringSexp(sexp).iter().collect())
}
}
pub fn get_class(&self) -> Option<Vec<&'static str>> {
unsafe { self.get_string_attrib_by_symbol(savvy_ffi::R_ClassSymbol) }
}
pub fn get_names(&self) -> Option<Vec<&'static str>> {
unsafe { self.get_string_attrib_by_symbol(savvy_ffi::R_NamesSymbol) }
}
pub fn get_dim(&self) -> Option<&[i32]> {
unsafe { crate::sexp::get_dim_from_sexp(&self.0) }
}
pub fn set_attrib(&mut self, attr: &str, value: Sexp) -> crate::error::Result<()> {
let attr_cstr = CString::new(attr)?;
unsafe {
crate::unwind_protect(|| {
savvy_ffi::Rf_setAttrib(self.0, savvy_ffi::Rf_install(attr_cstr.as_ptr()), value.0)
})?
};
Ok(())
}
unsafe fn set_string_attrib_by_symbol<T, U>(
&mut self,
attr: SEXP,
values: T,
) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: AsRef<str>,
{
let values_sexp: OwnedStringSexp = values.as_ref().try_into()?;
unsafe {
crate::unwind_protect(|| savvy_ffi::Rf_setAttrib(self.0, attr, values_sexp.inner()))?
};
Ok(())
}
pub fn set_class<T, U>(&mut self, classes: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: AsRef<str>,
{
unsafe { self.set_string_attrib_by_symbol(savvy_ffi::R_ClassSymbol, classes) }
}
pub fn set_names<T, U>(&mut self, names: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: AsRef<str>,
{
unsafe { self.set_string_attrib_by_symbol(savvy_ffi::R_NamesSymbol, names) }
}
pub fn set_dim<T, U>(&mut self, dim: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: TryInto<i32> + Copy,
{
unsafe { crate::sexp::set_dim_to_sexp(self.0, dim) }
}
}
pub(crate) unsafe fn get_dim_from_sexp(value: &SEXP) -> Option<&[i32]> {
let dim_sexp = unsafe { Rf_getAttrib(*value, savvy_ffi::R_DimSymbol) };
if unsafe { TYPEOF(dim_sexp) != savvy_ffi::INTSXP } {
None
} else {
Some(unsafe {
std::slice::from_raw_parts(INTEGER(dim_sexp) as _, Rf_xlength(dim_sexp) as _)
})
}
}
pub(crate) unsafe fn set_dim_to_sexp<T, U>(value: SEXP, dim: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: TryInto<i32> + Copy,
{
let dim = dim.as_ref();
let mut dim_sexp = unsafe { OwnedIntegerSexp::new_without_init(dim.len())? };
dim.iter()
.enumerate()
.for_each(|(i, &v)| dim_sexp[i] = v.try_into().unwrap_or_default());
unsafe { savvy_ffi::Rf_setAttrib(value, savvy_ffi::R_DimSymbol, dim_sexp.inner()) };
Ok(())
}
macro_rules! impl_common_sexp_ops {
($ty: ty) => {
impl $ty {
#[inline]
pub fn inner(&self) -> savvy_ffi::SEXP {
self.0
}
#[inline]
pub(crate) fn inner_ref(&self) -> &savvy_ffi::SEXP {
&self.0
}
pub fn len(&self) -> usize {
unsafe { savvy_ffi::Rf_xlength(self.inner()) as _ }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get_attrib(&self, attr: &str) -> crate::error::Result<Option<Sexp>> {
crate::Sexp(self.inner()).get_attrib(attr)
}
pub fn get_names(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_names()
}
pub fn get_class(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_class()
}
pub fn get_dim(&self) -> Option<&[i32]> {
unsafe { crate::sexp::get_dim_from_sexp(self.inner_ref()) }
}
}
};
}
macro_rules! impl_common_sexp_ops_owned {
($ty: ty) => {
impl $ty {
#[inline]
pub fn inner(&self) -> SEXP {
self.inner
}
#[inline]
pub(crate) fn inner_ref(&self) -> &savvy_ffi::SEXP {
&self.inner
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn get_attrib(&self, attr: &str) -> crate::error::Result<Option<Sexp>> {
crate::Sexp(self.inner()).get_attrib(attr)
}
pub fn get_names(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_names()
}
pub fn get_class(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_class()
}
pub fn get_dim(&self) -> Option<&[i32]> {
unsafe { crate::sexp::get_dim_from_sexp(self.inner_ref()) }
}
pub fn set_attrib(&mut self, attr: &str, value: Sexp) -> crate::error::Result<()> {
crate::Sexp(self.inner()).set_attrib(attr, value)
}
pub fn set_class<T, U>(&mut self, classes: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: AsRef<str>,
{
crate::Sexp(self.inner()).set_class(classes)
}
pub fn set_names<T, U>(&mut self, names: T) -> crate::error::Result<()>
where
T: AsRef<[U]>,
U: AsRef<str>,
{
crate::Sexp(self.inner()).set_names(names)
}
pub fn set_dim<T: TryInto<i32> + Copy>(
&mut self,
dim: &[T],
) -> crate::error::Result<()> {
unsafe { crate::sexp::set_dim_to_sexp(self.inner(), dim) }
}
}
};
}
pub(crate) use impl_common_sexp_ops;
pub(crate) use impl_common_sexp_ops_owned;