extendr_api/wrapper/
strings.rs1use 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 pub fn new(size: usize) -> Strings {
31 let robj = Robj::alloc_vector(SEXPTYPE::STRSXP, size);
32 Self { robj }
33 }
34
35 pub fn new_with_na(len: usize) -> Strings {
37 let iter = (0..len).map(|_| Rstr::na());
38 Strings::from_values(iter)
39 }
40 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 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 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 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 pub fn iter(&self) -> impl ExactSizeIterator<Item = &Rstr> + Clone + DoubleEndedIterator {
102 self.as_slice().iter()
103 }
104
105 pub fn is_sorted(&self) -> Rbool {
107 unsafe { STRING_IS_SORTED(self.get()).into() }
108 }
109
110 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 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 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(¬_strings);
356
357 assert!(matches!(result, Err(Error::ExpectedString(robj)) if robj == r!(1)));
358 }
359 }
360}