extendr_api/wrapper/
strings.rs

1use super::*;
2use extendr_ffi::{
3    R_xlen_t, SET_STRING_ELT, STRING_ELT, STRING_IS_SORTED, STRING_NO_NA, STRING_PTR_RO,
4};
5use std::convert::From;
6use std::iter::FromIterator;
7
8#[derive(PartialEq, Clone)]
9pub struct Strings {
10    pub(crate) robj: Robj,
11}
12
13impl Default for Strings {
14    fn default() -> Self {
15        Strings::new(0)
16    }
17}
18
19impl Strings {
20    /// Create a new, empty list.
21    /// ```
22    /// use extendr_api::prelude::*;
23    /// test! {
24    ///     let strings = Strings::new(10);
25    ///     assert_eq!(strings.is_string(), true);
26    ///     assert_eq!(strings.len(), 10);
27    /// }
28    /// ```
29    pub fn new(size: usize) -> Strings {
30        let robj = Robj::alloc_vector(SEXPTYPE::STRSXP, size);
31        Self { robj }
32    }
33
34    /// Constructs a new vector of size `len` with `NA` values
35    pub fn new_with_na(len: usize) -> Strings {
36        let iter = (0..len).map(|_| Rstr::na());
37        Strings::from_values(iter)
38    }
39    /// Wrapper for creating string vector (STRSXP) objects.
40    /// ```
41    /// use extendr_api::prelude::*;
42    /// test! {
43    ///     let list = r!(Strings::from_values(&["x", "y", "z"]));
44    ///     assert_eq!(list.is_string(), true);
45    ///     assert_eq!(list.len(), 3);
46    /// }
47    /// ```
48    pub fn from_values<V>(values: V) -> Self
49    where
50        V: IntoIterator,
51        V::IntoIter: ExactSizeIterator,
52        V::Item: AsRef<str>,
53    {
54        single_threaded(|| unsafe {
55            let values = values.into_iter();
56            let maxlen = values.len();
57            let mut robj = Robj::alloc_vector(SEXPTYPE::STRSXP, maxlen);
58            let sexp = robj.get_mut();
59            for (i, v) in values.into_iter().take(maxlen).enumerate() {
60                let v = v.as_ref();
61                let ch = str_to_character(v);
62                SET_STRING_ELT(sexp, i as R_xlen_t, ch);
63            }
64            Self { robj }
65        })
66    }
67
68    /// This is a relatively expensive operation, so use a variable if using this in a loop.
69    pub fn as_slice<'a>(&self) -> &'a [Rstr] {
70        unsafe {
71            let data = STRING_PTR_RO(self.robj.get()) as *const Rstr;
72            let len = self.robj.len();
73            std::slice::from_raw_parts(data, len)
74        }
75    }
76
77    /// Get an element in a string vector.
78    pub fn elt(&self, i: usize) -> Rstr {
79        if i >= self.len() {
80            Rstr::na()
81        } else {
82            unsafe {
83                Robj::from_sexp(STRING_ELT(self.get(), i as R_xlen_t))
84                    .try_into()
85                    .unwrap()
86            }
87        }
88    }
89
90    /// Set a single element of this string vector.
91    pub fn set_elt(&mut self, i: usize, e: Rstr) {
92        single_threaded(|| unsafe {
93            if i < self.len() {
94                SET_STRING_ELT(self.robj.get_mut(), i as isize, e.get());
95            }
96        });
97    }
98
99    /// Get an iterator for this string vector.
100    pub fn iter(&self) -> impl Iterator<Item = &Rstr> {
101        self.as_slice().iter()
102    }
103
104    /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
105    pub fn is_sorted(&self) -> Rbool {
106        unsafe { STRING_IS_SORTED(self.get()).into() }
107    }
108
109    /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
110    pub fn no_na(&self) -> Rbool {
111        unsafe { STRING_NO_NA(self.get()).into() }
112    }
113}
114
115impl Attributes for Strings {}
116
117impl<T: AsRef<str>> FromIterator<T> for Strings {
118    /// Convert an iterator to a Strings object.
119    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
120        let iter_collect: Vec<_> = iter.into_iter().collect();
121        let len = iter_collect.len();
122
123        let mut robj = Strings::alloc_vector(SEXPTYPE::STRSXP, len);
124        crate::single_threaded(|| unsafe {
125            for (i, v) in iter_collect.into_iter().enumerate() {
126                SET_STRING_ELT(robj.get_mut(), i as isize, str_to_character(v.as_ref()));
127            }
128            Strings { robj }
129        })
130    }
131}
132
133impl<T> From<T> for Strings
134where
135    T: AsRef<str>,
136{
137    /// convert string-like objects into a Strings object.
138    fn from(value: T) -> Self {
139        Strings::from_values([value.as_ref()])
140    }
141}
142
143impl Deref for Strings {
144    type Target = [Rstr];
145
146    fn deref(&self) -> &Self::Target {
147        self.as_slice()
148    }
149}
150
151impl std::fmt::Debug for Strings {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        if self.len() == 1 {
154            write!(f, "{:?}", self.elt(0))
155        } else {
156            f.debug_list().entries(self.iter()).finish()
157        }
158    }
159}
160
161impl From<Option<Strings>> for Robj {
162    fn from(value: Option<Strings>) -> Self {
163        match value {
164            Some(value_strings) => value_strings.into(),
165            None => nil_value(),
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use crate as extendr_api;
174
175    #[test]
176    fn new() {
177        test! {
178            let vec = Strings::new(10);
179            assert_eq!(vec.is_string(), true);
180            assert_eq!(vec.len(), 10);
181        }
182    }
183
184    #[test]
185    fn new_with_na() {
186        use crate::na::CanBeNA;
187        test! {
188            let vec = Strings::new_with_na(10);
189            let manual_vec = (0..10).into_iter().map(|_| Rstr::na()).collect::<Strings>();
190            assert_eq!(vec, manual_vec);
191            assert_eq!(vec.len(), manual_vec.len());
192        }
193    }
194}