Skip to main content

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::collections::{BTreeSet, HashSet};
6use std::convert::From;
7use std::iter::FromIterator;
8
9#[derive(PartialEq, Clone)]
10pub struct Strings {
11    pub(crate) robj: Robj,
12}
13
14impl Default for Strings {
15    fn default() -> Self {
16        Strings::new(0)
17    }
18}
19
20impl Strings {
21    /// Create a new, empty list.
22    /// ```
23    /// use extendr_api::prelude::*;
24    /// test! {
25    ///     let strings = Strings::new(10);
26    ///     assert_eq!(strings.is_string(), true);
27    ///     assert_eq!(strings.len(), 10);
28    /// }
29    /// ```
30    pub fn new(size: usize) -> Strings {
31        let robj = Robj::alloc_vector(SEXPTYPE::STRSXP, size);
32        Self { robj }
33    }
34
35    /// Constructs a new vector of size `len` with `NA` values
36    pub fn new_with_na(len: usize) -> Strings {
37        let iter = (0..len).map(|_| Rstr::na());
38        Strings::from_values(iter)
39    }
40    /// Wrapper for creating string vector (STRSXP) objects.
41    /// ```
42    /// use extendr_api::prelude::*;
43    /// test! {
44    ///     let list = r!(Strings::from_values(&["x", "y", "z"]));
45    ///     assert_eq!(list.is_string(), true);
46    ///     assert_eq!(list.len(), 3);
47    /// }
48    /// ```
49    pub fn from_values<V>(values: V) -> Self
50    where
51        V: IntoIterator,
52        V::IntoIter: ExactSizeIterator,
53        V::Item: AsRef<str>,
54    {
55        single_threaded(|| unsafe {
56            let values = values.into_iter();
57            let maxlen = values.len();
58            let mut robj = Robj::alloc_vector(SEXPTYPE::STRSXP, maxlen);
59            let sexp = robj.get_mut();
60            for (i, v) in values.into_iter().take(maxlen).enumerate() {
61                let v = v.as_ref();
62                let ch = str_to_character(v);
63                SET_STRING_ELT(sexp, i as R_xlen_t, ch);
64            }
65            Self { robj }
66        })
67    }
68
69    /// This is a relatively expensive operation, so use a variable if using this in a loop.
70    pub fn as_slice<'a>(&self) -> &'a [Rstr] {
71        unsafe {
72            let data = STRING_PTR_RO(self.robj.get()) as *const Rstr;
73            let len = self.robj.len();
74            crate::robj::vector_slice(data, len)
75        }
76    }
77
78    /// Get an element in a string vector.
79    pub fn elt(&self, i: usize) -> Rstr {
80        if i >= self.len() {
81            Rstr::na()
82        } else {
83            unsafe {
84                Robj::from_sexp(STRING_ELT(self.get(), i as R_xlen_t))
85                    .try_into()
86                    .unwrap()
87            }
88        }
89    }
90
91    /// Set a single element of this string vector.
92    pub fn set_elt(&mut self, i: usize, e: Rstr) {
93        single_threaded(|| unsafe {
94            if i < self.len() {
95                SET_STRING_ELT(self.robj.get_mut(), i as isize, e.get());
96            }
97        });
98    }
99
100    /// Get an iterator for this string vector.
101    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Rstr> + Clone + DoubleEndedIterator {
102        self.as_slice().iter()
103    }
104
105    /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
106    pub fn is_sorted(&self) -> Rbool {
107        unsafe { STRING_IS_SORTED(self.get()).into() }
108    }
109
110    /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
111    pub fn no_na(&self) -> Rbool {
112        unsafe { STRING_NO_NA(self.get()).into() }
113    }
114}
115
116impl Attributes for Strings {}
117
118impl<T: AsRef<str>> FromIterator<T> for Strings {
119    /// Convert an iterator to a Strings object.
120    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
121        let iter = iter.into_iter();
122        let mut capacity = iter.size_hint().0;
123        let mut robj = Strings::alloc_vector(SEXPTYPE::STRSXP, capacity);
124        let mut n = 0;
125        unsafe {
126            for v in iter {
127                if n == capacity {
128                    capacity = (capacity * 2).max(1);
129                    robj = Robj::from_sexp(Rf_xlengthgets(robj.get(), capacity as R_xlen_t));
130                }
131                SET_STRING_ELT(robj.get_mut(), n as isize, str_to_character(v.as_ref()));
132                n += 1;
133            }
134            if n < capacity {
135                robj = Robj::from_sexp(Rf_xlengthgets(robj.get(), n as R_xlen_t));
136            }
137        }
138        Strings { robj }
139    }
140}
141
142impl<T> From<T> for Strings
143where
144    T: AsRef<str>,
145{
146    /// convert string-like objects into a Strings object.
147    fn from(value: T) -> Self {
148        Strings::from_values([value.as_ref()])
149    }
150}
151
152impl Deref for Strings {
153    type Target = [Rstr];
154
155    fn deref(&self) -> &Self::Target {
156        self.as_slice()
157    }
158}
159
160impl std::fmt::Debug for Strings {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        if self.len() == 1 {
163            write!(f, "{:?}", self.elt(0))
164        } else {
165            f.debug_list().entries(self.iter()).finish()
166        }
167    }
168}
169
170impl From<Option<Strings>> for Robj {
171    fn from(value: Option<Strings>) -> Self {
172        match value {
173            Some(value_strings) => value_strings.into(),
174            None => nil_value(),
175        }
176    }
177}
178
179impl TryFrom<&Robj> for HashSet<&str> {
180    type Error = Error;
181
182    fn try_from(robj: &Robj) -> Result<Self> {
183        robj.as_str_iter()
184            .ok_or_else(|| Error::ExpectedString(robj.clone()))?
185            .map(|s| {
186                if s.is_na() {
187                    Err(Error::MustNotBeNA(robj.clone()))
188                } else {
189                    Ok(s)
190                }
191            })
192            .collect()
193    }
194}
195
196impl TryFrom<&Robj> for BTreeSet<&str> {
197    type Error = Error;
198
199    fn try_from(robj: &Robj) -> Result<Self> {
200        robj.as_str_iter()
201            .ok_or_else(|| Error::ExpectedString(robj.clone()))?
202            .map(|s| {
203                if s.is_na() {
204                    Err(Error::MustNotBeNA(robj.clone()))
205                } else {
206                    Ok(s)
207                }
208            })
209            .collect()
210    }
211}
212
213impl TryFrom<&Robj> for HashSet<Option<&str>> {
214    type Error = Error;
215
216    fn try_from(robj: &Robj) -> Result<Self> {
217        robj.as_str_iter()
218            .ok_or_else(|| Error::ExpectedString(robj.clone()))
219            .map(|iter| iter.map(|s| (!s.is_na()).then_some(s)).collect())
220    }
221}
222
223impl TryFrom<&Robj> for BTreeSet<Option<&str>> {
224    type Error = Error;
225
226    fn try_from(robj: &Robj) -> Result<Self> {
227        robj.as_str_iter()
228            .ok_or_else(|| Error::ExpectedString(robj.clone()))
229            .map(|iter| iter.map(|s| (!s.is_na()).then_some(s)).collect())
230    }
231}
232
233impl TryFrom<&Strings> for HashSet<&str> {
234    type Error = Error;
235
236    fn try_from(value: &Strings) -> Result<Self> {
237        Self::try_from(&value.robj)
238    }
239}
240
241impl TryFrom<&Strings> for BTreeSet<&str> {
242    type Error = Error;
243
244    fn try_from(value: &Strings) -> Result<Self> {
245        Self::try_from(&value.robj)
246    }
247}
248
249impl TryFrom<&Strings> for HashSet<Option<&str>> {
250    type Error = Error;
251
252    fn try_from(value: &Strings) -> Result<Self> {
253        Self::try_from(&value.robj)
254    }
255}
256
257impl TryFrom<&Strings> for BTreeSet<Option<&str>> {
258    type Error = Error;
259
260    fn try_from(value: &Strings) -> Result<Self> {
261        Self::try_from(&value.robj)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268    use std::collections::{BTreeSet, HashSet};
269
270    #[test]
271    fn new() {
272        test! {
273            let vec = Strings::new(10);
274            assert_eq!(vec.is_string(), true);
275            assert_eq!(vec.len(), 10);
276        }
277    }
278
279    #[test]
280    fn iter_cycles_and_reverses() {
281        test! {
282            let vec = Strings::from_values(["a", "b"]);
283            let iter = vec.iter();
284            assert_eq!(iter.len(), 2);
285
286            let cycled: Vec<&str> = iter.clone().cycle().take(3).map(|s| s.as_ref()).collect();
287            assert_eq!(cycled, ["a", "b", "a"]);
288
289            let reversed: Vec<&str> = iter.rev().map(|s| s.as_ref()).collect();
290            assert_eq!(reversed, ["b", "a"]);
291        }
292    }
293
294    #[test]
295    fn new_with_na() {
296        use crate::na::CanBeNA;
297        test! {
298            let vec = Strings::new_with_na(10);
299            let manual_vec = (0..10).map(|_| Rstr::na()).collect::<Strings>();
300            assert_eq!(vec, manual_vec);
301            assert_eq!(vec.len(), manual_vec.len());
302        }
303    }
304
305    #[test]
306    fn try_from_strings_to_hashset() {
307        test! {
308            let strings = Strings::from_values(["alpha", "beta", "alpha"]);
309            let set = HashSet::<&str>::try_from(&strings).unwrap();
310
311            assert_eq!(set.len(), 2);
312            assert!(set.contains("alpha"));
313            assert!(set.contains("beta"));
314        }
315    }
316
317    #[test]
318    fn try_from_strings_to_btreeset() {
319        test! {
320            let strings = Strings::from_values(["charlie", "bravo", "alpha"]);
321            let set = BTreeSet::<&str>::try_from(&strings).unwrap();
322            let ordered: Vec<_> = set.into_iter().collect();
323
324            assert_eq!(ordered, vec!["alpha", "bravo", "charlie"]);
325        }
326    }
327
328    #[test]
329    fn try_from_strings_fails_on_na() {
330        test! {
331            let strings = Strings::from_values([Rstr::from("NA"), Rstr::na()]);
332
333            assert!(matches!(HashSet::<&str>::try_from(&strings), Err(Error::MustNotBeNA(_))));
334            assert!(matches!(BTreeSet::<&str>::try_from(&strings), Err(Error::MustNotBeNA(_))));
335        }
336    }
337
338    #[test]
339    fn try_from_strings_to_option_sets_keeps_na_distinct() {
340        test! {
341            let strings = Strings::from_values([Rstr::from("NA"), Rstr::na(), Rstr::na()]);
342
343            let set = HashSet::<Option<&str>>::try_from(&strings).unwrap();
344            assert_eq!(set, HashSet::from([Some("NA"), None]));
345
346            let set = BTreeSet::<Option<&str>>::try_from(&strings).unwrap();
347            assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![None, Some("NA")]);
348        }
349    }
350
351    #[test]
352    fn try_from_strings_fails_on_non_strings() {
353        test! {
354            let not_strings = Strings { robj: r!(1) };
355            let result = HashSet::<&str>::try_from(&not_strings);
356
357            assert!(matches!(result, Err(Error::ExpectedString(robj)) if robj == r!(1)));
358        }
359    }
360}