1#[cfg(feature = "linalg")]
6use ndarray::{Array2, ShapeBuilder};
7
8#[cfg(feature = "linalg")]
9use crate::interpreter::builtins;
10use crate::interpreter::coerce::f64_to_i64;
11use crate::interpreter::environment::Environment;
12use crate::interpreter::value::*;
13use crate::interpreter::Interpreter;
14use crate::parser::ast::{BinaryOp, SpecialOp, UnaryOp};
15
16fn op_symbol(op: &BinaryOp) -> String {
18 match op {
19 BinaryOp::Add => "+".to_string(),
20 BinaryOp::Sub => "-".to_string(),
21 BinaryOp::Mul => "*".to_string(),
22 BinaryOp::Div => "/".to_string(),
23 BinaryOp::Pow => "^".to_string(),
24 BinaryOp::Mod => "%%".to_string(),
25 BinaryOp::IntDiv => "%/%".to_string(),
26 BinaryOp::Eq => "==".to_string(),
27 BinaryOp::Ne => "!=".to_string(),
28 BinaryOp::Lt => "<".to_string(),
29 BinaryOp::Gt => ">".to_string(),
30 BinaryOp::Le => "<=".to_string(),
31 BinaryOp::Ge => ">=".to_string(),
32 BinaryOp::And => "&".to_string(),
33 BinaryOp::AndScalar => "&&".to_string(),
34 BinaryOp::Or => "|".to_string(),
35 BinaryOp::OrScalar => "||".to_string(),
36 _ => String::new(),
37 }
38}
39
40fn list_to_character(list: &RList) -> Vector {
43 let chars: Vec<Option<String>> = list
44 .values
45 .iter()
46 .map(|(_, val)| match val {
47 RValue::Vector(rv) => rv.inner.as_character_scalar(),
48 RValue::Null => Some("NULL".to_string()),
49 _ => Some(val.type_name().to_string()),
50 })
51 .collect();
52 Vector::Character(chars.into())
53}
54
55fn propagate_attrs(result: RValue, lv: &RVector, rv: &RVector) -> RValue {
59 let donor = if lv.inner.len() >= rv.inner.len() {
60 lv
61 } else {
62 rv
63 };
64 let Some(attrs) = &donor.attrs else {
65 return result;
66 };
67 match result {
68 RValue::Vector(mut rv_out) => {
69 for key in &["dim", "dimnames", "names"] {
71 if let Some(val) = attrs.get(*key) {
72 rv_out.set_attr(key.to_string(), val.clone());
73 }
74 }
75 RValue::Vector(rv_out)
76 }
77 other => other,
78 }
79}
80
81impl Interpreter {
84 pub(super) fn eval_unary(&self, op: UnaryOp, val: &RValue) -> Result<RValue, RFlow> {
85 eval_unary(op, val)
86 }
87
88 pub(super) fn eval_binary(
89 &self,
90 op: BinaryOp,
91 left: &RValue,
92 right: &RValue,
93 env: &Environment,
94 ) -> Result<RValue, RFlow> {
95 let op_name = op_symbol(&op);
98 if let Some(result) = self.try_s3_binary_dispatch(&op_name, left, right, env)? {
99 return Ok(result);
100 }
101 eval_binary(op, left, right)
102 }
103
104 fn try_s3_binary_dispatch(
107 &self,
108 op_name: &str,
109 left: &RValue,
110 right: &RValue,
111 env: &Environment,
112 ) -> Result<Option<RValue>, RFlow> {
113 for obj in [left, right] {
115 let classes = self.s3_classes_for(obj);
116 for class in &classes {
117 let method_name = format!("{}.{}", op_name, class);
118 if let Some(method) = env
121 .get(&method_name)
122 .or_else(|| self.global_env.get(&method_name))
123 {
124 let result =
125 self.call_function(&method, &[left.clone(), right.clone()], &[], env)?;
126 return Ok(Some(result));
127 }
128 if let Some(method) = self.lookup_s3_method(op_name, class) {
129 let result =
130 self.call_function(&method, &[left.clone(), right.clone()], &[], env)?;
131 return Ok(Some(result));
132 }
133 }
134 }
135 Ok(None)
136 }
137}
138
139fn eval_unary(op: UnaryOp, val: &RValue) -> Result<RValue, RFlow> {
144 match op {
145 UnaryOp::Neg => match val {
146 RValue::Vector(v) => {
147 let result = match &v.inner {
148 Vector::Double(vals) => Vector::Double(
149 vals.iter()
150 .map(|x| x.map(|f| -f))
151 .collect::<Vec<_>>()
152 .into(),
153 ),
154 Vector::Integer(vals) => Vector::Integer(
155 vals.iter()
156 .map(|x| x.map(|i| -i))
157 .collect::<Vec<_>>()
158 .into(),
159 ),
160 Vector::Logical(vals) => Vector::Integer(
161 vals.iter()
162 .map(|x| x.map(|b| if b { -1 } else { 0 }))
163 .collect::<Vec<_>>()
164 .into(),
165 ),
166 _ => {
167 return Err(RError::new(
168 RErrorKind::Type,
169 "invalid argument to unary operator",
170 )
171 .into())
172 }
173 };
174 Ok(RValue::vec(result))
175 }
176 _ => Err(RError::new(RErrorKind::Type, "invalid argument to unary operator").into()),
177 },
178 UnaryOp::Pos => match val {
179 RValue::Vector(v) if matches!(v.inner, Vector::Raw(_)) => {
180 Err(RError::new(RErrorKind::Type, "non-numeric argument to unary operator").into())
181 }
182 _ => Ok(val.clone()),
183 },
184 UnaryOp::Not => match val {
185 RValue::Vector(v) if matches!(v.inner, Vector::Raw(_)) => {
187 let bytes = v.inner.to_raw();
188 let result: Vec<u8> = bytes.iter().map(|b| !b).collect();
189 Ok(RValue::vec(Vector::Raw(result)))
190 }
191 RValue::Vector(v) => {
192 let logicals = v.to_logicals();
193 let result: Vec<Option<bool>> = logicals.iter().map(|x| x.map(|b| !b)).collect();
194 Ok(RValue::vec(Vector::Logical(result.into())))
195 }
196 _ => Err(RError::new(RErrorKind::Type, "invalid argument type").into()),
197 },
198 UnaryOp::Formula => Ok(RValue::Null), }
200}
201
202fn eval_binary(op: BinaryOp, left: &RValue, right: &RValue) -> Result<RValue, RFlow> {
207 match op {
208 BinaryOp::Range => return eval_range(left, right),
209 BinaryOp::Special(SpecialOp::In) => return eval_in_op(left, right),
210 BinaryOp::Special(SpecialOp::MatMul) => return eval_matmul(left, right),
211 BinaryOp::Special(SpecialOp::Kronecker) => {
212 return crate::interpreter::builtins::math::eval_kronecker(left, right)
213 .map_err(RFlow::from)
214 }
215 _ => {}
216 };
217
218 let lv_owned;
222 let lv = match left {
223 RValue::Vector(v) => v,
224 RValue::Null => return Ok(RValue::Null),
225 RValue::List(list) if op.is_comparison() => {
226 lv_owned = RVector::from(list_to_character(list));
227 &lv_owned
228 }
229 _ => {
230 return Err(RError::new(
231 RErrorKind::Type,
232 "non-numeric argument to binary operator".to_string(),
233 )
234 .into())
235 }
236 };
237 let rv_owned;
238 let rv = match right {
239 RValue::Vector(v) => v,
240 RValue::Null => return Ok(RValue::Null),
241 RValue::List(list) if op.is_comparison() => {
242 rv_owned = RVector::from(list_to_character(list));
243 &rv_owned
244 }
245 _ => {
246 return Err(RError::new(
247 RErrorKind::Type,
248 "non-numeric argument to binary operator".to_string(),
249 )
250 .into())
251 }
252 };
253
254 match op {
255 BinaryOp::Range => eval_range(left, right),
256 BinaryOp::Special(SpecialOp::In) => eval_in_op(left, right),
257 BinaryOp::Special(SpecialOp::MatMul) => eval_matmul(left, right),
258 BinaryOp::Special(SpecialOp::Kronecker) => {
259 crate::interpreter::builtins::math::eval_kronecker(left, right).map_err(RFlow::from)
260 }
261 BinaryOp::Special(_) => Ok(RValue::Null),
262
263 BinaryOp::Pipe | BinaryOp::AssignPipe | BinaryOp::TeePipe | BinaryOp::ExpoPipe => {
265 Ok(RValue::Null)
266 }
267
268 BinaryOp::Add
270 | BinaryOp::Sub
271 | BinaryOp::Mul
272 | BinaryOp::Div
273 | BinaryOp::Pow
274 | BinaryOp::Mod
275 | BinaryOp::IntDiv => {
276 if matches!(lv.inner, Vector::Raw(_)) || matches!(rv.inner, Vector::Raw(_)) {
277 return Err(RError::new(
278 RErrorKind::Type,
279 "non-numeric argument to binary operator",
280 )
281 .into());
282 }
283 let result = eval_arith(op, &lv.inner, &rv.inner)?;
284 Ok(propagate_attrs(result, lv, rv))
285 }
286
287 BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
289 eval_compare(op, lv, rv)
290 }
291
292 BinaryOp::And | BinaryOp::Or => {
294 if matches!(lv.inner, Vector::Raw(_)) || matches!(rv.inner, Vector::Raw(_)) {
295 return eval_raw_bitwise(op, &lv.inner, &rv.inner);
296 }
297 eval_logical_vec(op, &lv.inner, &rv.inner)
298 }
299
300 BinaryOp::AndScalar => {
302 let a = lv.as_logical_scalar();
303 let b = rv.as_logical_scalar();
304 match (a, b) {
305 (Some(false), _) | (_, Some(false)) => {
306 Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
307 }
308 (Some(true), Some(true)) => {
309 Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
310 }
311 _ => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
312 }
313 }
314 BinaryOp::OrScalar => {
315 let a = lv.as_logical_scalar();
316 let b = rv.as_logical_scalar();
317 match (a, b) {
318 (Some(true), _) | (_, Some(true)) => {
319 Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
320 }
321 (Some(false), Some(false)) => {
322 Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
323 }
324 _ => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
325 }
326 }
327
328 BinaryOp::Tilde | BinaryOp::DoubleTilde => Ok(RValue::Null), }
330}
331
332fn eval_arith(op: BinaryOp, lv: &Vector, rv: &Vector) -> Result<RValue, RFlow> {
337 let use_integer = matches!(
339 (&lv, &rv, &op),
340 (
341 Vector::Integer(_),
342 Vector::Integer(_),
343 BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::IntDiv | BinaryOp::Mod
344 )
345 );
346
347 if use_integer {
348 let li = lv.to_integers();
349 let ri = rv.to_integers();
350 let len = li.len().max(ri.len());
351 if len == 0 || li.is_empty() || ri.is_empty() {
352 return Ok(RValue::vec(Vector::Integer(vec![].into())));
353 }
354 let result: Vec<Option<i64>> = (0..len)
355 .map(|i| {
356 let a = li[i % li.len()];
357 let b = ri[i % ri.len()];
358 match (a, b) {
359 (Some(a), Some(b)) => match op {
360 BinaryOp::Add => Some(a.wrapping_add(b)),
361 BinaryOp::Sub => Some(a.wrapping_sub(b)),
362 BinaryOp::Mul => Some(a.wrapping_mul(b)),
363 BinaryOp::IntDiv => {
364 if b != 0 {
365 Some(a / b)
366 } else {
367 None
368 }
369 }
370 BinaryOp::Mod => {
371 if b != 0 {
372 Some(a % b)
373 } else {
374 None
375 }
376 }
377 _ => None,
378 },
379 _ => None,
380 }
381 })
382 .collect();
383 return Ok(RValue::vec(Vector::Integer(result.into())));
384 }
385
386 let use_complex = matches!(lv, Vector::Complex(_)) || matches!(rv, Vector::Complex(_));
388
389 if use_complex {
390 let lc = lv.to_complex();
391 let rc = rv.to_complex();
392 let len = lc.len().max(rc.len());
393 if len == 0 {
394 return Ok(RValue::vec(Vector::Complex(vec![].into())));
395 }
396 if matches!(op, BinaryOp::Mod | BinaryOp::IntDiv) {
397 return Err(RError::new(
398 RErrorKind::Type,
399 "unimplemented complex operation".to_string(),
400 )
401 .into());
402 }
403 let result: Vec<Option<num_complex::Complex64>> = (0..len)
404 .map(|i| {
405 let a = lc[i % lc.len()];
406 let b = rc[i % rc.len()];
407 match (a, b) {
408 (Some(a), Some(b)) => Some(match op {
409 BinaryOp::Add => a + b,
410 BinaryOp::Sub => a - b,
411 BinaryOp::Mul => a * b,
412 BinaryOp::Div => a / b,
413 BinaryOp::Pow => a.powc(b),
414 _ => unreachable!(),
415 }),
416 _ => None,
417 }
418 })
419 .collect();
420 return Ok(RValue::vec(Vector::Complex(result.into())));
421 }
422
423 let ld = lv.to_doubles();
424 let rd = rv.to_doubles();
425 let len = ld.len().max(rd.len());
426 if len == 0 || ld.is_empty() || rd.is_empty() {
427 return Ok(RValue::vec(Vector::Double(vec![].into())));
428 }
429
430 let arith_element = |i: usize| -> Option<f64> {
431 let a = ld[i % ld.len()];
432 let b = rd[i % rd.len()];
433 match (a, b) {
434 (Some(a), Some(b)) => Some(match op {
435 BinaryOp::Add => a + b,
436 BinaryOp::Sub => a - b,
437 BinaryOp::Mul => a * b,
438 BinaryOp::Div => a / b,
439 BinaryOp::Pow => a.powf(b),
440 BinaryOp::Mod => a % b,
441 BinaryOp::IntDiv => (a / b).floor(),
442 _ => unreachable!(),
443 }),
444 _ => None,
445 }
446 };
447
448 #[cfg(feature = "parallel")]
450 if len >= 10_000 {
451 use rayon::prelude::*;
452 let result: Vec<Option<f64>> = (0..len).into_par_iter().map(arith_element).collect();
453 return Ok(RValue::vec(Vector::Double(result.into())));
454 }
455
456 let result: Vec<Option<f64>> = (0..len).map(arith_element).collect();
457 Ok(RValue::vec(Vector::Double(result.into())))
458}
459
460fn eval_compare(op: BinaryOp, lv: &RVector, rv: &RVector) -> Result<RValue, RFlow> {
465 if matches!(lv.inner, Vector::Raw(_)) || matches!(rv.inner, Vector::Raw(_)) {
467 let lb = lv.to_raw();
468 let rb = rv.to_raw();
469 let len = lb.len().max(rb.len());
470 if len == 0 {
471 return Ok(RValue::vec(Vector::Logical(vec![].into())));
472 }
473 let result: Vec<Option<bool>> = (0..len)
474 .map(|i| {
475 let a = lb[i % lb.len()];
476 let b = rb[i % rb.len()];
477 Some(match op {
478 BinaryOp::Eq => a == b,
479 BinaryOp::Ne => a != b,
480 BinaryOp::Lt => a < b,
481 BinaryOp::Gt => a > b,
482 BinaryOp::Le => a <= b,
483 BinaryOp::Ge => a >= b,
484 _ => unreachable!(),
485 })
486 })
487 .collect();
488 return Ok(RValue::vec(Vector::Logical(result.into())));
489 }
490
491 if matches!(lv.inner, Vector::Complex(_)) || matches!(rv.inner, Vector::Complex(_)) {
493 if !matches!(op, BinaryOp::Eq | BinaryOp::Ne) {
494 return Err(RError::new(
495 RErrorKind::Type,
496 "invalid comparison with complex values".to_string(),
497 )
498 .into());
499 }
500 let lc = lv.to_complex();
501 let rc = rv.to_complex();
502 let len = lc.len().max(rc.len());
503 let result: Vec<Option<bool>> = (0..len)
504 .map(|i| {
505 let a = lc[i % lc.len()];
506 let b = rc[i % rc.len()];
507 match (a, b) {
508 (Some(a), Some(b)) => Some(match op {
509 BinaryOp::Eq => a == b,
510 BinaryOp::Ne => a != b,
511 _ => unreachable!(),
512 }),
513 _ => None,
514 }
515 })
516 .collect();
517 return Ok(RValue::vec(Vector::Logical(result.into())));
518 }
519
520 if matches!(lv.inner, Vector::Character(_)) || matches!(rv.inner, Vector::Character(_)) {
522 let lc = lv.to_characters();
523 let rc = rv.to_characters();
524 if lc.is_empty() || rc.is_empty() {
525 return Ok(RValue::vec(Vector::Logical(vec![].into())));
526 }
527 let len = lc.len().max(rc.len());
528 let result: Vec<Option<bool>> = (0..len)
529 .map(|i| {
530 let a = &lc[i % lc.len()];
531 let b = &rc[i % rc.len()];
532 match (a, b) {
533 (Some(a), Some(b)) => Some(match op {
534 BinaryOp::Eq => a == b,
535 BinaryOp::Ne => a != b,
536 BinaryOp::Lt => a < b,
537 BinaryOp::Gt => a > b,
538 BinaryOp::Le => a <= b,
539 BinaryOp::Ge => a >= b,
540 _ => unreachable!(),
541 }),
542 _ => None,
543 }
544 })
545 .collect();
546 return Ok(RValue::vec(Vector::Logical(result.into())));
547 }
548
549 let ld = lv.to_doubles();
550 let rd = rv.to_doubles();
551 let len = ld.len().max(rd.len());
552 if len == 0 {
553 return Ok(RValue::vec(Vector::Logical(vec![].into())));
554 }
555
556 if ld.is_empty() || rd.is_empty() {
557 return Ok(RValue::vec(Vector::Logical(vec![].into())));
558 }
559 let result: Vec<Option<bool>> = (0..len)
560 .map(|i| {
561 let a = ld[i % ld.len()];
562 let b = rd[i % rd.len()];
563 match (a, b) {
564 (Some(a), Some(b)) => Some(match op {
565 BinaryOp::Eq => a == b,
566 BinaryOp::Ne => a != b,
567 BinaryOp::Lt => a < b,
568 BinaryOp::Gt => a > b,
569 BinaryOp::Le => a <= b,
570 BinaryOp::Ge => a >= b,
571 _ => unreachable!(),
572 }),
573 _ => None,
574 }
575 })
576 .collect();
577 Ok(RValue::vec(Vector::Logical(result.into())))
578}
579
580fn eval_logical_vec(op: BinaryOp, lv: &Vector, rv: &Vector) -> Result<RValue, RFlow> {
585 let ll = lv.to_logicals();
586 let rl = rv.to_logicals();
587 let len = ll.len().max(rl.len());
588
589 let result: Vec<Option<bool>> = (0..len)
590 .map(|i| {
591 let a = ll[i % ll.len()];
592 let b = rl[i % rl.len()];
593 match op {
594 BinaryOp::And => match (a, b) {
595 (Some(false), _) | (_, Some(false)) => Some(false),
596 (Some(true), Some(true)) => Some(true),
597 _ => None,
598 },
599 BinaryOp::Or => match (a, b) {
600 (Some(true), _) | (_, Some(true)) => Some(true),
601 (Some(false), Some(false)) => Some(false),
602 _ => None,
603 },
604 _ => unreachable!(),
605 }
606 })
607 .collect();
608 Ok(RValue::vec(Vector::Logical(result.into())))
609}
610
611fn eval_raw_bitwise(op: BinaryOp, lv: &Vector, rv: &Vector) -> Result<RValue, RFlow> {
613 let lb = lv.to_raw();
614 let rb = rv.to_raw();
615 let len = lb.len().max(rb.len());
616 if len == 0 {
617 return Ok(RValue::vec(Vector::Raw(vec![])));
618 }
619 let result: Vec<u8> = (0..len)
620 .map(|i| {
621 let a = lb[i % lb.len()];
622 let b = rb[i % rb.len()];
623 match op {
624 BinaryOp::And => a & b,
625 BinaryOp::Or => a | b,
626 _ => unreachable!(),
627 }
628 })
629 .collect();
630 Ok(RValue::vec(Vector::Raw(result)))
631}
632
633fn eval_range(left: &RValue, right: &RValue) -> Result<RValue, RFlow> {
638 let from = match left {
639 RValue::Vector(v) => f64_to_i64(v.as_double_scalar().unwrap_or(0.0))?,
640 _ => 0,
641 };
642 let to = match right {
643 RValue::Vector(v) => f64_to_i64(v.as_double_scalar().unwrap_or(0.0))?,
644 _ => 0,
645 };
646
647 let result: Vec<Option<i64>> = if from <= to {
648 (from..=to).map(Some).collect()
649 } else {
650 (to..=from).rev().map(Some).collect()
651 };
652 Ok(RValue::vec(Vector::Integer(result.into())))
653}
654
655fn eval_in_op(left: &RValue, right: &RValue) -> Result<RValue, RFlow> {
656 match (left, right) {
657 (RValue::Vector(lv), RValue::Vector(rv)) => {
658 if matches!(lv.inner, Vector::Character(_)) || matches!(rv.inner, Vector::Character(_))
660 {
661 let table = rv.to_characters();
662 let vals = lv.to_characters();
663 let result: Vec<Option<bool>> =
664 vals.iter().map(|x| Some(table.contains(x))).collect();
665 return Ok(RValue::vec(Vector::Logical(result.into())));
666 }
667 let table = rv.to_doubles();
669 let vals = lv.to_doubles();
670 let result: Vec<Option<bool>> = vals
671 .iter()
672 .map(|x| match x {
673 Some(v) => Some(table.iter().any(|t| match t {
674 Some(t) => (*t == *v) || (t.is_nan() && v.is_nan()),
675 None => false,
676 })),
677 None => Some(table.iter().any(|t| t.is_none())),
678 })
679 .collect();
680 Ok(RValue::vec(Vector::Logical(result.into())))
681 }
682 _ => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
683 }
684}
685
686#[cfg(feature = "linalg")]
692fn eval_matmul(left: &RValue, right: &RValue) -> Result<RValue, RFlow> {
693 fn to_matrix(val: &RValue) -> Result<(Array2<f64>, usize, usize), RError> {
694 let (data, dim_attr) = match val {
695 RValue::Vector(rv) => (rv.to_doubles(), rv.get_attr("dim")),
696 _ => {
697 return Err(RError::new(
698 RErrorKind::Type,
699 "requires numeric/complex matrix/vector arguments".to_string(),
700 ))
701 }
702 };
703 let (nrow, ncol) = match dim_attr {
704 Some(RValue::Vector(rv)) => match &rv.inner {
705 Vector::Integer(d) if d.len() >= 2 => (
706 usize::try_from(d.get_opt(0).unwrap_or(0))?,
707 usize::try_from(d.get_opt(1).unwrap_or(0))?,
708 ),
709 _ => (data.len(), 1), },
711 _ => (data.len(), 1), };
713 let flat: Vec<f64> = data.iter().map(|x| x.unwrap_or(f64::NAN)).collect();
714 let arr = Array2::from_shape_vec((nrow, ncol).f(), flat)
716 .map_err(|source| -> RError { builtins::math::MathError::Shape { source }.into() })?;
717 Ok((arr, nrow, ncol))
718 }
719
720 let (a, _arows, acols) = to_matrix(left)?;
721 let (b, brows, bcols) = to_matrix(right)?;
722
723 if acols != brows {
724 return Err(RError::other(format!(
725 "non-conformable arguments: {}x{} vs {}x{}",
726 a.nrows(),
727 acols,
728 brows,
729 bcols
730 ))
731 .into());
732 }
733
734 let c = a.dot(&b);
735 let (rrows, rcols) = (c.nrows(), c.ncols());
736
737 let mut result = Vec::with_capacity(rrows * rcols);
739 for j in 0..rcols {
740 for i in 0..rrows {
741 result.push(Some(c[[i, j]]));
742 }
743 }
744
745 let mut rv = RVector::from(Vector::Double(result.into()));
746 rv.set_attr(
747 "dim".to_string(),
748 RValue::vec(Vector::Integer(
749 vec![Some(i64::try_from(rrows)?), Some(i64::try_from(rcols)?)].into(),
750 )),
751 );
752 Ok(RValue::Vector(rv))
753}
754
755#[cfg(not(feature = "linalg"))]
756fn eval_matmul(_left: &RValue, _right: &RValue) -> Result<RValue, RFlow> {
757 Err(RError::other("matrix multiplication requires the 'linalg' feature").into())
758}
759
760