use std::{
ffi::CString,
os::raw::{c_int, c_void},
};
use savvy_ffi::{
R_NaString, R_NilValue, R_xlen_t, Rboolean, Rboolean_FALSE, Rboolean_TRUE, Rf_coerceVector,
Rf_duplicate, Rf_protect, Rf_unprotect, Rf_xlength, SET_STRING_ELT, SEXP, SEXPTYPE, STRING_ELT,
STRING_PTR_RO, STRSXP,
altrep::{
R_altrep_data2, R_make_altstring_class, R_set_altrep_Coerce_method,
R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method,
R_set_altrep_data2, R_set_altstring_Elt_method, R_set_altvec_Dataptr_or_null_method,
},
};
use crate::{IntoExtPtrSexp, StringSexp};
pub trait AltString: Sized + IntoExtPtrSexp {
const CLASS_NAME: &'static str;
const PACKAGE_NAME: &'static str;
fn length(&mut self) -> usize;
fn elt(&mut self, i: usize) -> &str;
fn inspect(&mut self, is_materialized: bool) {
crate::io::r_print(
&format!("{} (materialized: {is_materialized})\n", Self::CLASS_NAME),
false,
);
}
fn into_altrep(self) -> crate::Result<crate::Sexp> {
super::create_altrep_instance(self, Self::CLASS_NAME).map(crate::Sexp)
}
fn try_from_altrep_ref(x: &StringSexp) -> crate::Result<&Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
super::extract_ref_from_altrep(&x.0)
}
fn try_from_altrep_mut(x: &mut StringSexp, invalidate_cache: bool) -> crate::Result<&mut Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
if invalidate_cache {
unsafe { R_set_altrep_data2(x.0, R_NilValue) }
}
super::extract_mut_from_altrep(&mut x.0)
}
fn try_from_altrep(x: StringSexp) -> crate::Result<Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
super::extract_from_altrep(x.0)
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn register_altstring_class<T: AltString>(
dll_info: *mut crate::ffi::DllInfo,
) -> crate::error::Result<()> {
let class_name = CString::new(T::CLASS_NAME).unwrap_or_default();
let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default();
let class_t =
unsafe { R_make_altstring_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) };
#[allow(clippy::mut_from_ref)]
#[inline]
fn get_materialized_sexp<T: AltString>(x: &mut SEXP, allow_allocate: bool) -> Option<SEXP> {
let data = unsafe { R_altrep_data2(*x) };
if unsafe { data != R_NilValue } {
return Some(data);
}
if !allow_allocate {
return None;
}
let self_: &mut T = match super::extract_mut_from_altrep(x) {
Ok(self_) => self_,
Err(_) => return None,
};
let len = self_.length();
let new = crate::alloc_vector(STRSXP, len).unwrap();
unsafe { Rf_protect(new) };
for i in 0..len {
unsafe {
SET_STRING_ELT(
new,
i as _,
crate::sexp::utils::str_to_charsxp(self_.elt(i)).unwrap_or(R_NaString),
)
};
}
crate::log::debug!("A {} object is materialized", T::CLASS_NAME);
unsafe { R_set_altrep_data2(*x, new) };
unsafe { Rf_unprotect(1) };
Some(new)
}
unsafe extern "C" fn altrep_duplicate<T: AltString>(mut x: SEXP, _deep_copy: Rboolean) -> SEXP {
crate::log::trace!("A {} object is duplicated", T::CLASS_NAME);
let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
unsafe { Rf_duplicate(materialized) }
}
unsafe extern "C" fn altrep_coerce<T: AltString>(mut x: SEXP, sexp_type: SEXPTYPE) -> SEXP {
crate::log::trace!("A {} object is coerced", T::CLASS_NAME);
let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
unsafe { Rf_coerceVector(materialized, sexp_type) }
}
unsafe extern "C" fn altvec_dataptr_or_null<T: AltString>(mut x: SEXP) -> *const c_void {
crate::log::trace!("DATAPTR_OR_NULL({}) is called", T::CLASS_NAME);
match get_materialized_sexp::<T>(&mut x, false) {
Some(materialized) => unsafe { STRING_PTR_RO(materialized) as _ },
None => std::ptr::null(),
}
}
unsafe extern "C" fn altrep_length<T: AltString>(mut x: SEXP) -> R_xlen_t {
if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
unsafe { Rf_xlength(materialized) }
} else {
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => self_.length() as _,
Err(_) => 0,
}
}
}
unsafe extern "C" fn altrep_inspect<T: AltString>(
mut x: SEXP,
_: c_int,
_: c_int,
_: c_int,
_: Option<unsafe extern "C" fn(SEXP, c_int, c_int, c_int)>,
) -> Rboolean {
let is_materialized = unsafe { R_altrep_data2(x) != R_NilValue };
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => {
self_.inspect(is_materialized);
Rboolean_TRUE
}
Err(_) => Rboolean_FALSE,
}
}
unsafe extern "C" fn altstring_elt<T: AltString>(mut x: SEXP, i: R_xlen_t) -> SEXP {
crate::log::trace!("STRING_ELT({}, {i}) is called", T::CLASS_NAME);
if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
unsafe { STRING_ELT(materialized, i) }
} else {
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => unsafe {
crate::sexp::utils::str_to_charsxp(self_.elt(i as _)).unwrap_or(R_NaString)
},
Err(_) => unsafe { R_NaString },
}
}
}
unsafe {
R_set_altrep_Length_method(class_t, Some(altrep_length::<T>));
R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::<T>));
R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::<T>));
R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::<T>));
R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::<T>));
R_set_altstring_Elt_method(class_t, Some(altstring_elt::<T>));
}
super::register_altrep_class(T::CLASS_NAME, class_t)?;
Ok(())
}