1#[doc(hidden)]
58use ndarray::prelude::*;
59use ndarray::{Data, ShapeBuilder};
60
61use crate::prelude::{c64, dim_symbol, Rcplx, Rfloat, Rint};
62use crate::*;
63
64macro_rules! make_array_view_1 {
65 ($type: ty, $error_fn: expr) => {
66 impl<'a> TryFrom<&'_ Robj> for ArrayView1<'a, $type> {
67 type Error = crate::Error;
68
69 fn try_from(robj: &Robj) -> Result<Self> {
70 if let Some(v) = robj.as_typed_slice() {
71 Ok(ArrayView1::<'a, $type>::from(v))
72 } else {
73 Err($error_fn(robj.clone()))
74 }
75 }
76 }
77
78 impl<'a> TryFrom<Robj> for ArrayView1<'a, $type> {
79 type Error = crate::Error;
80
81 fn try_from(robj: Robj) -> Result<Self> {
82 Self::try_from(&robj)
83 }
84 }
85 };
86}
87
88macro_rules! make_array_view_2 {
89 ($type: ty, $error_str: expr, $error_fn: expr) => {
90 impl<'a> TryFrom<&'_ Robj> for ArrayView2<'a, $type> {
91 type Error = crate::Error;
92 fn try_from(robj: &Robj) -> Result<Self> {
93 if robj.is_matrix() {
94 let nrows = robj.nrows();
95 let ncols = robj.ncols();
96 if let Some(v) = robj.as_typed_slice() {
97 let shape = (nrows, ncols).into_shape().f();
99 return ArrayView2::from_shape(shape, v)
100 .map_err(|err| Error::NDArrayShapeError(err));
101 } else {
102 return Err($error_fn(robj.clone()));
103 }
104 }
105 return Err(Error::ExpectedMatrix(robj.clone()));
106 }
107 }
108
109 impl<'a> TryFrom<Robj> for ArrayView2<'a, $type> {
110 type Error = crate::Error;
111 fn try_from(robj: Robj) -> Result<Self> {
112 Self::try_from(&robj)
113 }
114 }
115 };
116}
117make_array_view_1!(Rbool, Error::ExpectedLogical);
118make_array_view_1!(Rint, Error::ExpectedInteger);
119make_array_view_1!(i32, Error::ExpectedInteger);
120make_array_view_1!(Rfloat, Error::ExpectedReal);
121make_array_view_1!(f64, Error::ExpectedReal);
122make_array_view_1!(Rcplx, Error::ExpectedComplex);
123make_array_view_1!(c64, Error::ExpectedComplex);
124make_array_view_1!(Rstr, Error::ExpectedString);
125
126make_array_view_2!(Rbool, "Not a logical matrix.", Error::ExpectedLogical);
127make_array_view_2!(Rint, "Not an integer matrix.", Error::ExpectedInteger);
128make_array_view_2!(i32, "Not an integer matrix.", Error::ExpectedInteger);
129make_array_view_2!(Rfloat, "Not a floating point matrix.", Error::ExpectedReal);
130make_array_view_2!(f64, "Not a floating point matrix.", Error::ExpectedReal);
131make_array_view_2!(
132 Rcplx,
133 "Not a complex number matrix.",
134 Error::ExpectedComplex
135);
136make_array_view_2!(c64, "Not a complex number matrix.", Error::ExpectedComplex);
137make_array_view_2!(Rstr, "Not a string matrix.", Error::ExpectedString);
138
139impl<A, S, D> TryFrom<&ArrayBase<S, D>> for Robj
140where
141 S: Data<Elem = A>,
142 A: Copy + ToVectorValue,
143 D: Dimension,
144{
145 type Error = Error;
146
147 fn try_from(value: &ArrayBase<S, D>) -> Result<Self> {
150 let mut result = value
157 .t()
158 .iter()
159 .copied()
161 .collect_robj();
162 result.set_attrib(
163 dim_symbol(),
164 value
165 .shape()
166 .iter()
167 .map(|x| i32::try_from(*x))
168 .collect::<std::result::Result<Vec<i32>, <i32 as TryFrom<usize>>::Error>>()
169 .map_err(|_err| {
170 Error::Other(String::from(
171 "One or more array dimensions were too large to be handled by R.",
172 ))
173 })?,
174 )?;
175 Ok(result)
176 }
177}
178
179impl<A, S, D> TryFrom<ArrayBase<S, D>> for Robj
180where
181 S: Data<Elem = A>,
182 A: Copy + ToVectorValue,
183 D: Dimension,
184{
185 type Error = Error;
186
187 fn try_from(value: ArrayBase<S, D>) -> Result<Self> {
190 Robj::try_from(&value)
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use super::*;
197 use crate as extendr_api;
198 use ndarray::array;
199 use rstest::rstest;
200
201 #[rstest]
202 #[case(
204 "1.0",
205 ArrayView1::<f64>::from(&[1.][..])
206 )]
207 #[case(
208 "1L",
209 ArrayView1::<i32>::from(&[1][..])
210 )]
211 #[case(
212 "TRUE",
213 ArrayView1::<Rbool>::from(&[TRUE][..])
214 )]
215 #[case(
217 "matrix(c(1, 2, 3, 4, 5, 6, 7, 8), ncol=2, nrow=4)",
218 <Array2<f64>>::from_shape_vec((4, 2).f(), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]).unwrap()
219 )]
220 #[case(
221 "matrix(c(1, 2, 3, 4, 5, 6, 7, 8), ncol=2, nrow=4)[, 1]",
223 <Array2<f64>>::from_shape_vec((4, 2).f(), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]).unwrap().column(0).to_owned()
224 )]
225 #[case(
226 "matrix(c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L), ncol=2, nrow=4)",
227 <Array2<i32>>::from_shape_vec((4, 2).f(), vec![1, 2, 3, 4, 5, 6, 7, 8]).unwrap()
228 )]
229 #[case(
230 "matrix(c(T, T, T, T, F, F, F, F), ncol=2, nrow=4)",
231 <Array2<Rbool>>::from_shape_vec((4, 2).f(), vec![true.into(), true.into(), true.into(), true.into(), false.into(), false.into(), false.into(), false.into()]).unwrap()
232 )]
233 fn test_from_robj<DataType, DimType, Error>(
234 #[case] left: &'static str,
235 #[case] right: ArrayBase<DataType, DimType>,
236 ) where
237 DataType: Data,
238 Error: std::fmt::Debug,
239 for<'a> ArrayView<'a, <DataType as ndarray::RawData>::Elem, DimType>:
240 TryFrom<&'a Robj, Error = Error>,
241 DimType: Dimension,
242 <DataType as ndarray::RawData>::Elem: PartialEq + std::fmt::Debug,
243 Error: std::fmt::Debug,
244 {
245 test! {
247 let left_robj = eval_string(left).unwrap();
248 let left_array = <ArrayView<DataType::Elem, DimType>>::try_from(&left_robj).unwrap();
249 assert_eq!( left_array, right );
250 }
251 }
252
253 #[rstest]
254 #[case(
255 Array4::<i32>::zeros((0, 1, 2, 3).f()),
257 "array(integer(), c(0, 1, 2, 3))"
258 )]
259 #[case(
260 array![1., 2., 3.],
261 "array(c(1, 2, 3))"
262 )]
263 #[case(
264 Array::from_shape_vec((2, 3), vec![1., 2., 3., 4., 5., 6.]).unwrap(),
267 "matrix(c(1, 2, 3, 4, 5, 6), nrow=2, byrow=TRUE)"
268 )]
269 #[case(
270 Array::from_shape_vec((2, 3).f(), vec![1., 2., 3., 4., 5., 6.]).unwrap(),
273 "matrix(c(1, 2, 3, 4, 5, 6), nrow=2, byrow=FALSE)"
274 )]
275 #[case(
276 Array::from_shape_vec((1, 2, 3).f(), vec![1, 2, 3, 4, 5, 6]).unwrap(),
279 "array(1:6, c(1, 2, 3))"
280 )]
281 #[case(
282 array![[[1, 5], [3, 7]], [[2, 6], [4, 8]]],
285 "array(1:8, dim=c(2, 2, 2))"
286 )]
287 fn test_to_robj<ElementType, DimType>(
288 #[case] array: Array<ElementType, DimType>,
289 #[case] r_expr: &str,
290 ) where
291 Robj: TryFrom<Array<ElementType, DimType>>,
292 for<'a> Robj: TryFrom<&'a Array<ElementType, DimType>>,
293 <robj::Robj as TryFrom<Array<ElementType, DimType>>>::Error: std::fmt::Debug,
294 for<'a> <robj::Robj as TryFrom<&'a Array<ElementType, DimType>>>::Error: std::fmt::Debug,
295 {
296 test! {
299 assert_eq!(
301 &(Robj::try_from(&array).unwrap()),
302 &eval_string(r_expr).unwrap()
303 );
304 assert_eq!(
306 &(Robj::try_from(array).unwrap()),
307 &eval_string(r_expr).unwrap()
308 );
309 }
310 }
311
312 #[test]
313 fn test_round_trip() {
314 test! {
315 let rvals = [
316 R!("matrix(c(1L, 2L, 3L, 4L, 5L, 6L), nrow=2)"),
317 R!("array(1:8, c(4, 2))")
318 ];
319 for rval in rvals {
320 let rval = rval.unwrap();
321 let rust_arr= <ArrayView2<i32>>::try_from(&rval).unwrap();
322 let r_arr: Robj = (&rust_arr).try_into().unwrap();
323 assert_eq!(
324 rval,
325 r_arr
326 );
327 }
328 }
329 }
330}