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