Skip to main content

r/interpreter/
ops.rs

1//! Vectorized arithmetic, comparison, logical, range, membership, and matrix
2//! multiplication operators. All functions are pure — they operate on values
3//! without needing interpreter state.
4
5#[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
16/// Map a BinaryOp to its R symbol name for S3 dispatch.
17fn 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
40/// Coerce a list to a character vector by extracting each element's string
41/// representation. Used for comparison operators on lists (e.g. `lapply(x, class) == "NULL"`).
42fn 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
55/// Copy attributes (dim, dimnames, names, class) from the longer operand
56/// to the arithmetic result. R's rule: attrs come from the first operand
57/// if lengths are equal, otherwise from the longer one.
58fn 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            // Copy dim, dimnames, names — skip class (arithmetic strips S3 class for base types)
70            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
81// region: Interpreter delegation
82
83impl 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        // Try S3 dispatch on the Ops group generic: check if either operand
96        // has a class with a method for this operator (e.g. `|.root_criterion`).
97        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    /// Try S3 dispatch for a binary operator on either operand's class.
105    /// Returns `Ok(Some(result))` if a method was found, `Ok(None)` otherwise.
106    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        // Try left operand's class first, then right
114        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                // Search the calling env (namespace during package loading),
119                // then global, then the S3 method registry
120                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
139// endregion
140
141// region: unary
142
143fn 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            // Bitwise NOT for raw vectors
186            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), // stub for unary ~
199    }
200}
201
202// endregion
203
204// region: binary dispatch
205
206fn 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    // Get vectors for element-wise operations.
219    // Lists are coerced to character vectors for comparison operators,
220    // matching R behavior: lapply(x, class) == "NULL" works element-wise.
221    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        // Pipe ops are handled in eval_in before reaching eval_binary
264        BinaryOp::Pipe | BinaryOp::AssignPipe | BinaryOp::TeePipe | BinaryOp::ExpoPipe => {
265            Ok(RValue::Null)
266        }
267
268        // Arithmetic (vectorized with recycling) — raw vectors cannot participate
269        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        // Comparison (vectorized)
288        BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge => {
289            eval_compare(op, lv, rv)
290        }
291
292        // Logical — for raw vectors, & and | are bitwise
293        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        // Scalar logical
301        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), // formula/plotmath stubs
329    }
330}
331
332// endregion
333
334// region: arithmetic
335
336fn eval_arith(op: BinaryOp, lv: &Vector, rv: &Vector) -> Result<RValue, RFlow> {
337    // Check if both are integer and op preserves integer type
338    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    // If either operand is complex, operate in complex space
387    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    // Use rayon for large vectors when the parallel feature is enabled
449    #[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
460// endregion
461
462// region: comparison
463
464fn eval_compare(op: BinaryOp, lv: &RVector, rv: &RVector) -> Result<RValue, RFlow> {
465    // Raw comparison: compares byte values
466    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    // Complex comparison: only == and != are defined
492    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 either is character, compare as strings
521    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
580// endregion
581
582// region: logical
583
584fn 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
611/// Bitwise &/| for raw vectors — returns raw
612fn 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
633// endregion
634
635// region: range and membership
636
637fn 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 either side is character, compare as strings
659            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            // Otherwise compare as doubles (handles int/double/logical correctly)
668            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// endregion
687
688// region: matrix multiplication
689
690/// Matrix multiplication using ndarray
691#[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), // treat as column vector
710            },
711            _ => (data.len(), 1), // treat as column vector
712        };
713        let flat: Vec<f64> = data.iter().map(|x| x.unwrap_or(f64::NAN)).collect();
714        // ndarray uses row-major by default, R uses column-major
715        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    // Convert back to column-major R vector
738    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// endregion