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