1use std::{
2 ffi::CString,
3 os::raw::{c_int, c_void},
4};
5
6use savvy_ffi::{
7 altrep::{
8 R_altrep_data2, R_make_altlist_class, R_set_altlist_Elt_method, R_set_altrep_Coerce_method,
9 R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method,
10 R_set_altrep_data2, R_set_altvec_Dataptr_or_null_method,
11 },
12 R_NilValue, R_xlen_t, Rboolean, Rboolean_FALSE, Rboolean_TRUE, Rf_coerceVector, Rf_duplicate,
13 Rf_protect, Rf_unprotect, Rf_xlength, DATAPTR_RO, SET_VECTOR_ELT, SEXP, SEXPTYPE, VECSXP,
14 VECTOR_ELT,
15};
16
17use crate::{IntoExtPtrSexp, ListSexp};
18
19pub trait AltList: Sized + IntoExtPtrSexp {
20 const CLASS_NAME: &'static str;
22
23 const PACKAGE_NAME: &'static str;
25
26 fn length(&mut self) -> usize;
28
29 fn elt(&mut self, i: usize) -> crate::Sexp;
32
33 fn inspect(&mut self, is_materialized: bool) {
35 crate::io::r_print(
36 &format!("{} (materialized: {is_materialized})\n", Self::CLASS_NAME),
37 false,
38 );
39 }
40
41 fn into_altrep(self) -> crate::Result<crate::Sexp> {
43 match super::create_altrep_instance(self, Self::CLASS_NAME) {
44 Ok(x) => Ok(crate::Sexp(x)),
45 Err(e) => Err(e),
46 }
47 }
48
49 fn try_from_altrep_ref(x: &ListSexp) -> crate::Result<&Self> {
51 super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
52 super::extract_ref_from_altrep(&x.0)
53 }
54
55 fn try_from_altrep_mut(x: &mut ListSexp, invalidate_cache: bool) -> crate::Result<&mut Self> {
57 super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
58 if invalidate_cache {
59 unsafe { R_set_altrep_data2(x.0, R_NilValue) }
60 }
61 super::extract_mut_from_altrep(&mut x.0)
62 }
63
64 fn try_from_altrep(x: ListSexp) -> crate::Result<Self> {
67 super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
68 super::extract_from_altrep(x.0)
69 }
70}
71
72#[allow(clippy::not_unsafe_ptr_arg_deref)]
73pub fn register_altlist_class<T: AltList>(
74 dll_info: *mut crate::ffi::DllInfo,
75) -> crate::error::Result<()> {
76 let class_name = CString::new(T::CLASS_NAME).unwrap_or_default();
77 let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default();
78 let class_t =
79 unsafe { R_make_altlist_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) };
80
81 #[allow(clippy::mut_from_ref)]
82 #[inline]
83 fn get_materialized_sexp<T: AltList>(x: &mut SEXP, allow_allocate: bool) -> Option<SEXP> {
84 let data = unsafe { R_altrep_data2(*x) };
85 if unsafe { data != R_NilValue } {
86 return Some(data);
87 }
88
89 if !allow_allocate {
91 return None;
92 }
93
94 let self_: &mut T = match super::extract_mut_from_altrep(x) {
95 Ok(self_) => self_,
96 Err(_) => return None,
97 };
98
99 let len = self_.length();
100
101 let new = crate::alloc_vector(VECSXP, len).unwrap();
102 unsafe { Rf_protect(new) };
103
104 for i in 0..len {
105 unsafe { SET_VECTOR_ELT(new, i as _, self_.elt(i).0) };
106 }
107
108 crate::log::debug!("A {} object is materialized", T::CLASS_NAME);
109
110 unsafe { R_set_altrep_data2(*x, new) };
112
113 unsafe { Rf_unprotect(1) };
115
116 Some(new)
117 }
118
119 unsafe extern "C" fn altrep_duplicate<T: AltList>(mut x: SEXP, _deep_copy: Rboolean) -> SEXP {
120 crate::log::trace!("A {} object is duplicated", T::CLASS_NAME);
121
122 let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
123 unsafe { Rf_duplicate(materialized) }
124 }
125
126 unsafe extern "C" fn altrep_coerce<T: AltList>(mut x: SEXP, sexp_type: SEXPTYPE) -> SEXP {
127 crate::log::trace!("A {} object is coerced", T::CLASS_NAME);
128
129 let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
130 unsafe { Rf_coerceVector(materialized, sexp_type) }
131 }
132
133 unsafe extern "C" fn altvec_dataptr_or_null<T: AltList>(mut x: SEXP) -> *const c_void {
142 crate::log::trace!("DATAPTR_OR_NULL({}) is called", T::CLASS_NAME);
143
144 match get_materialized_sexp::<T>(&mut x, false) {
145 Some(materialized) => unsafe { DATAPTR_RO(materialized) as _ },
146 None => std::ptr::null_mut(),
148 }
149 }
150
151 unsafe extern "C" fn altrep_length<T: AltList>(mut x: SEXP) -> R_xlen_t {
152 if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
153 unsafe { Rf_xlength(materialized) }
154 } else {
155 match super::extract_mut_from_altrep::<T>(&mut x) {
156 Ok(self_) => self_.length() as _,
157 Err(_) => 0,
158 }
159 }
160 }
161
162 unsafe extern "C" fn altrep_inspect<T: AltList>(
163 mut x: SEXP,
164 _: c_int,
165 _: c_int,
166 _: c_int,
167 _: Option<unsafe extern "C" fn(SEXP, c_int, c_int, c_int)>,
168 ) -> Rboolean {
169 let is_materialized = unsafe { R_altrep_data2(x) != R_NilValue };
170 match super::extract_mut_from_altrep::<T>(&mut x) {
171 Ok(self_) => {
172 self_.inspect(is_materialized);
173 Rboolean_TRUE
174 }
175 Err(_) => Rboolean_FALSE,
176 }
177 }
178
179 unsafe extern "C" fn altlist_elt<T: AltList>(mut x: SEXP, i: R_xlen_t) -> SEXP {
180 crate::log::trace!("VECTOR_ELT({}, {i}) is called", T::CLASS_NAME);
181
182 if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
183 unsafe { VECTOR_ELT(materialized, i) }
184 } else {
185 match super::extract_mut_from_altrep::<T>(&mut x) {
186 Ok(self_) => self_.elt(i as _).0,
187 Err(_) => unsafe { R_NilValue },
188 }
189 }
190 }
191
192 unsafe {
193 R_set_altrep_Length_method(class_t, Some(altrep_length::<T>));
194 R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::<T>));
195 R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::<T>));
196 R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::<T>));
197 R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::<T>));
199 R_set_altlist_Elt_method(class_t, Some(altlist_elt::<T>));
200
201 }
205
206 super::register_altrep_class(T::CLASS_NAME, class_t)?;
207 Ok(())
208}