Skip to main content

miniextendr_api/from_r/
na_vectors.rs

1//! NA-aware vector conversions (`Vec<Option<T>>`, `Box<[Option<T>]>`).
2//!
3//! Maps R's NA values to `None` and non-NA values to `Some(v)`.
4//! Covers native types (i32, f64, u8), logical (bool, Rboolean, RLogical),
5//! string (`Option<String>`), complex (`Option<Rcomplex>`), and coerced
6//! numeric types (`Option<i64>`, `Option<u64>`, etc.).
7
8use crate::coerce::TryCoerce;
9use crate::ffi::{RLogical, Rboolean, SEXP, SEXPTYPE, SexpExt};
10use crate::from_r::{
11    SexpError, SexpNaError, SexpTypeError, TryFromSexp, charsxp_to_str, coerce_value, is_na_real,
12    r_slice,
13};
14
15/// Macro for NA-aware `R vector → Vec<Option<T>>` conversions.
16macro_rules! impl_vec_option_try_from_sexp {
17    ($t:ty, $sexptype:ident, $dataptr:ident, $is_na:expr) => {
18        impl TryFromSexp for Vec<Option<$t>> {
19            type Error = SexpError;
20
21            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
22                let actual = sexp.type_of();
23                if actual != SEXPTYPE::$sexptype {
24                    return Err(SexpTypeError {
25                        expected: SEXPTYPE::$sexptype,
26                        actual,
27                    }
28                    .into());
29                }
30
31                let len = sexp.len();
32                let ptr = unsafe { crate::ffi::$dataptr(sexp) };
33                let slice = unsafe { r_slice(ptr, len) };
34
35                Ok(slice
36                    .iter()
37                    .map(|&v| if $is_na(v) { None } else { Some(v) })
38                    .collect())
39            }
40        }
41    };
42}
43
44impl_vec_option_try_from_sexp!(f64, REALSXP, REAL, is_na_real);
45impl_vec_option_try_from_sexp!(i32, INTSXP, INTEGER, |v: i32| v == i32::MIN);
46
47/// Macro for NA-aware `R vector → Box<[Option<T>]>` conversions.
48macro_rules! impl_boxed_slice_option_try_from_sexp {
49    ($t:ty, $sexptype:ident, $dataptr:ident, $is_na:expr) => {
50        impl TryFromSexp for Box<[Option<$t>]> {
51            type Error = SexpError;
52
53            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
54                let actual = sexp.type_of();
55                if actual != SEXPTYPE::$sexptype {
56                    return Err(SexpTypeError {
57                        expected: SEXPTYPE::$sexptype,
58                        actual,
59                    }
60                    .into());
61                }
62
63                let len = sexp.len();
64                let ptr = unsafe { crate::ffi::$dataptr(sexp) };
65                let slice = unsafe { r_slice(ptr, len) };
66
67                Ok(slice
68                    .iter()
69                    .map(|&v| if $is_na(v) { None } else { Some(v) })
70                    .collect())
71            }
72        }
73    };
74}
75
76impl_boxed_slice_option_try_from_sexp!(f64, REALSXP, REAL, is_na_real);
77impl_boxed_slice_option_try_from_sexp!(i32, INTSXP, INTEGER, |v: i32| v == i32::MIN);
78
79/// Convert R logical vector (LGLSXP) to `Vec<Option<bool>>` with NA support.
80impl TryFromSexp for Vec<Option<bool>> {
81    type Error = SexpError;
82
83    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
84        let actual = sexp.type_of();
85        if actual != SEXPTYPE::LGLSXP {
86            return Err(SexpTypeError {
87                expected: SEXPTYPE::LGLSXP,
88                actual,
89            }
90            .into());
91        }
92
93        let slice: &[RLogical] = unsafe { sexp.as_slice() };
94
95        Ok(slice.iter().map(|v| v.to_option_bool()).collect())
96    }
97
98    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
99        let actual = sexp.type_of();
100        if actual != SEXPTYPE::LGLSXP {
101            return Err(SexpTypeError {
102                expected: SEXPTYPE::LGLSXP,
103                actual,
104            }
105            .into());
106        }
107
108        let slice: &[RLogical] = unsafe { sexp.as_slice_unchecked() };
109
110        Ok(slice.iter().map(|v| v.to_option_bool()).collect())
111    }
112}
113
114/// Convert R logical vector (LGLSXP) to `Box<[Option<bool>]>` with NA support.
115impl TryFromSexp for Box<[Option<bool>]> {
116    type Error = SexpError;
117
118    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
119        let vec: Vec<Option<bool>> = TryFromSexp::try_from_sexp(sexp)?;
120        Ok(vec.into_boxed_slice())
121    }
122}
123
124/// Convert R logical vector (LGLSXP) to `Vec<Rboolean>` (errors on NA).
125impl TryFromSexp for Vec<Rboolean> {
126    type Error = SexpError;
127
128    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
129        let actual = sexp.type_of();
130        if actual != SEXPTYPE::LGLSXP {
131            return Err(SexpTypeError {
132                expected: SEXPTYPE::LGLSXP,
133                actual,
134            }
135            .into());
136        }
137
138        let slice: &[RLogical] = unsafe { sexp.as_slice() };
139
140        slice
141            .iter()
142            .map(|v| match v.to_option_bool() {
143                Some(false) => Ok(Rboolean::FALSE),
144                Some(true) => Ok(Rboolean::TRUE),
145                None => Err(SexpNaError {
146                    sexp_type: SEXPTYPE::LGLSXP,
147                }
148                .into()),
149            })
150            .collect()
151    }
152}
153
154/// Convert R logical vector (LGLSXP) to `Vec<Option<Rboolean>>` with NA support.
155impl TryFromSexp for Vec<Option<Rboolean>> {
156    type Error = SexpError;
157
158    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
159        let actual = sexp.type_of();
160        if actual != SEXPTYPE::LGLSXP {
161            return Err(SexpTypeError {
162                expected: SEXPTYPE::LGLSXP,
163                actual,
164            }
165            .into());
166        }
167
168        let slice: &[RLogical] = unsafe { sexp.as_slice() };
169
170        Ok(slice
171            .iter()
172            .map(|v| match v.to_option_bool() {
173                Some(false) => Some(Rboolean::FALSE),
174                Some(true) => Some(Rboolean::TRUE),
175                None => None,
176            })
177            .collect())
178    }
179}
180
181/// Convert R logical vector (LGLSXP) to `Vec<Logical>` (ALTREP-compatible).
182///
183/// This converts R's logical vector to a vector of [`Logical`](crate::altrep_data::Logical) values,
184/// which is the native representation used by ALTREP logical vectors.
185/// Unlike `Vec<bool>`, this preserves NA values.
186impl TryFromSexp for Vec<crate::altrep_data::Logical> {
187    type Error = SexpError;
188
189    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
190        let actual = sexp.type_of();
191        if actual != SEXPTYPE::LGLSXP {
192            return Err(SexpTypeError {
193                expected: SEXPTYPE::LGLSXP,
194                actual,
195            }
196            .into());
197        }
198
199        let slice: &[RLogical] = unsafe { sexp.as_slice() };
200
201        Ok(slice
202            .iter()
203            .map(|&v| crate::altrep_data::Logical::from(v))
204            .collect())
205    }
206}
207
208/// Convert R logical vector (LGLSXP) to `Vec<Option<RLogical>>` with NA support.
209impl TryFromSexp for Vec<Option<RLogical>> {
210    type Error = SexpError;
211
212    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
213        let actual = sexp.type_of();
214        if actual != SEXPTYPE::LGLSXP {
215            return Err(SexpTypeError {
216                expected: SEXPTYPE::LGLSXP,
217                actual,
218            }
219            .into());
220        }
221
222        let slice: &[RLogical] = unsafe { sexp.as_slice() };
223
224        Ok(slice
225            .iter()
226            .map(|v| if v.is_na() { None } else { Some(*v) })
227            .collect())
228    }
229}
230
231/// Convert R character vector (STRSXP) to `Vec<Option<String>>` with NA support.
232///
233/// `NA_character_` elements are converted to `None`.
234impl TryFromSexp for Vec<Option<String>> {
235    type Error = SexpError;
236
237    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
238        let actual = sexp.type_of();
239        if actual != SEXPTYPE::STRSXP {
240            return Err(SexpTypeError {
241                expected: SEXPTYPE::STRSXP,
242                actual,
243            }
244            .into());
245        }
246
247        let len = sexp.len();
248        let mut result = Vec::with_capacity(len);
249
250        for i in 0..len {
251            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
252
253            if charsxp == SEXP::na_string() {
254                result.push(None);
255            } else {
256                result.push(Some(unsafe { charsxp_to_str(charsxp) }.to_owned()));
257            }
258        }
259
260        Ok(result)
261    }
262}
263
264/// Convert R character vector to `Box<[Option<String>]>` with NA support.
265impl TryFromSexp for Box<[Option<String>]> {
266    type Error = SexpError;
267
268    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
269        let vec: Vec<Option<String>> = TryFromSexp::try_from_sexp(sexp)?;
270        Ok(vec.into_boxed_slice())
271    }
272}
273
274/// Convert R raw vector (RAWSXP) to `Vec<Option<u8>>`.
275impl TryFromSexp for Vec<Option<u8>> {
276    type Error = SexpError;
277
278    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
279        let actual = sexp.type_of();
280        if actual != SEXPTYPE::RAWSXP {
281            return Err(SexpTypeError {
282                expected: SEXPTYPE::RAWSXP,
283                actual,
284            }
285            .into());
286        }
287
288        let slice: &[u8] = unsafe { sexp.as_slice() };
289
290        Ok(slice.iter().map(|&v| Some(v)).collect())
291    }
292}
293
294#[inline]
295fn try_from_sexp_numeric_option_vec<T>(sexp: SEXP) -> Result<Vec<Option<T>>, SexpError>
296where
297    i32: TryCoerce<T>,
298    f64: TryCoerce<T>,
299    u8: TryCoerce<T>,
300    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
301    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
302    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
303{
304    let actual = sexp.type_of();
305    match actual {
306        SEXPTYPE::INTSXP => {
307            let slice: &[i32] = unsafe { sexp.as_slice() };
308            slice
309                .iter()
310                .map(|&v| {
311                    if v == crate::altrep_traits::NA_INTEGER {
312                        Ok(None)
313                    } else {
314                        coerce_value(v).map(Some)
315                    }
316                })
317                .collect()
318        }
319        SEXPTYPE::REALSXP => {
320            let slice: &[f64] = unsafe { sexp.as_slice() };
321            slice
322                .iter()
323                .map(|&v| {
324                    if is_na_real(v) {
325                        Ok(None)
326                    } else {
327                        coerce_value(v).map(Some)
328                    }
329                })
330                .collect()
331        }
332        SEXPTYPE::RAWSXP => {
333            let slice: &[u8] = unsafe { sexp.as_slice() };
334            slice.iter().map(|&v| coerce_value(v).map(Some)).collect()
335        }
336        SEXPTYPE::LGLSXP => {
337            let slice: &[RLogical] = unsafe { sexp.as_slice() };
338            slice
339                .iter()
340                .map(|&v| {
341                    if v.is_na() {
342                        Ok(None)
343                    } else {
344                        coerce_value(v.to_i32()).map(Some)
345                    }
346                })
347                .collect()
348        }
349        _ => Err(SexpError::InvalidValue(format!(
350            "expected integer, numeric, logical, or raw; got {:?}",
351            actual
352        ))),
353    }
354}
355
356macro_rules! impl_vec_option_try_from_sexp_numeric {
357    ($t:ty) => {
358        impl TryFromSexp for Vec<Option<$t>> {
359            type Error = SexpError;
360
361            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
362                try_from_sexp_numeric_option_vec(sexp)
363            }
364
365            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
366                try_from_sexp_numeric_option_vec(sexp)
367            }
368        }
369    };
370}
371
372impl_vec_option_try_from_sexp_numeric!(i8);
373impl_vec_option_try_from_sexp_numeric!(i16);
374impl_vec_option_try_from_sexp_numeric!(u16);
375impl_vec_option_try_from_sexp_numeric!(u32);
376impl_vec_option_try_from_sexp_numeric!(i64);
377impl_vec_option_try_from_sexp_numeric!(u64);
378impl_vec_option_try_from_sexp_numeric!(isize);
379impl_vec_option_try_from_sexp_numeric!(usize);
380impl_vec_option_try_from_sexp_numeric!(f32);
381// endregion