Skip to main content

r/interpreter/
indexing.rs

1//! Read-side indexing helpers for vectors, lists, matrices, and data frames.
2//! Write-side (replacement) is in `assignment.rs`.
3
4use derive_more::{Display, Error};
5
6use crate::interpreter::environment::Environment;
7use crate::interpreter::value::*;
8use crate::interpreter::Interpreter;
9use crate::parser::ast::{Arg, Expr};
10
11// region: IndexingError
12
13/// Structured error type for indexing operations.
14#[derive(Debug, Display, Error)]
15pub enum IndexingError {
16    #[display("can't mix positive and negative subscripts")]
17    MixedSubscripts,
18
19    #[display("invalid index type")]
20    InvalidIndexType,
21
22    #[display("object is not subsettable")]
23    NotSubsettable,
24
25    #[display("incorrect number of dimensions")]
26    IncorrectDimensions,
27
28    #[display("subscript out of bounds (no dimnames to match against)")]
29    NoDimnames,
30
31    #[display("subscript out of bounds: '{}'", name)]
32    SubscriptOutOfBounds { name: String },
33
34    #[display("invalid subscript type")]
35    InvalidSubscriptType,
36
37    #[display("undefined row selected: '{}'", name)]
38    UndefinedRow { name: String },
39}
40
41impl From<IndexingError> for RError {
42    fn from(e: IndexingError) -> Self {
43        RError::from_source(RErrorKind::Index, e)
44    }
45}
46
47impl From<IndexingError> for RFlow {
48    fn from(e: IndexingError) -> Self {
49        RFlow::Error(RError::from(e))
50    }
51}
52
53// endregion
54
55impl Interpreter {
56    pub(super) fn eval_index(
57        &self,
58        object: &Expr,
59        indices: &[Arg],
60        env: &Environment,
61    ) -> Result<RValue, RFlow> {
62        eval_index(self, object, indices, env)
63    }
64
65    #[cfg(feature = "random")]
66    pub(crate) fn index_by_integer(
67        &self,
68        v: &Vector,
69        indices: &[Option<i64>],
70    ) -> Result<RValue, RFlow> {
71        index_by_integer(self, v, indices)
72    }
73
74    pub(super) fn eval_index_double(
75        &self,
76        object: &Expr,
77        indices: &[Arg],
78        env: &Environment,
79    ) -> Result<RValue, RFlow> {
80        eval_index_double(self, object, indices, env)
81    }
82
83    pub(super) fn eval_dollar(
84        &self,
85        object: &Expr,
86        member: &str,
87        env: &Environment,
88    ) -> Result<RValue, RFlow> {
89        eval_dollar(self, object, member, env)
90    }
91}
92
93pub(super) fn eval_index(
94    interp: &Interpreter,
95    object: &Expr,
96    indices: &[Arg],
97    env: &Environment,
98) -> Result<RValue, RFlow> {
99    let obj = interp.eval_in(object, env)?;
100
101    if indices.is_empty() {
102        return Ok(obj);
103    }
104
105    if indices.len() >= 2 {
106        return eval_matrix_index(interp, &obj, indices, env);
107    }
108
109    let idx_val = if let Some(val_expr) = &indices[0].value {
110        interp.eval_in(val_expr, env)?
111    } else {
112        return Ok(obj);
113    };
114
115    match &obj {
116        RValue::Vector(v) => match &idx_val {
117            RValue::Vector(idx_vec) => {
118                if let Vector::Logical(mask) = &idx_vec.inner {
119                    return index_by_logical(interp, v, mask);
120                }
121
122                // Character indexing: look up names attribute to resolve positions
123                if let Vector::Character(idx_names) = &idx_vec.inner {
124                    let names_attr = v.get_attr("names").and_then(|a| a.as_vector());
125                    let name_strs: Vec<Option<String>> =
126                        names_attr.map(|nv| nv.to_characters()).unwrap_or_default();
127                    let positions: Vec<Option<i64>> = idx_names
128                        .iter()
129                        .map(|idx_name| {
130                            idx_name.as_ref().and_then(|name| {
131                                name_strs
132                                    .iter()
133                                    .position(|n| n.as_deref() == Some(name.as_str()))
134                                    .and_then(|p| i64::try_from(p + 1).ok())
135                            })
136                        })
137                        .collect();
138                    return index_by_integer(interp, v, &positions);
139                }
140
141                let indices = idx_vec.to_integers();
142                // Drop zeros (R ignores them), then check sign consistency
143                let nonzero: Vec<Option<i64>> = indices
144                    .iter()
145                    .filter(|x| !matches!(x, Some(0)))
146                    .copied()
147                    .collect();
148                let has_pos = nonzero.iter().any(|x| x.map(|i| i > 0).unwrap_or(false));
149                let has_neg = nonzero.iter().any(|x| x.map(|i| i < 0).unwrap_or(false));
150                if has_pos && has_neg {
151                    return Err(IndexingError::MixedSubscripts.into());
152                }
153                if has_neg {
154                    return index_by_negative(interp, v, &nonzero);
155                }
156                index_by_integer(interp, v, &nonzero)
157            }
158            RValue::Null => Ok(obj.clone()),
159            _ => Err(IndexingError::InvalidIndexType.into()),
160        },
161        RValue::List(list) => match &idx_val {
162            RValue::Vector(idx_vec) => {
163                if let Vector::Character(names) = &idx_vec.inner {
164                    let mut result = Vec::new();
165                    for name in names.iter().flatten() {
166                        let found = list
167                            .values
168                            .iter()
169                            .find(|(n, _)| n.as_ref() == Some(name))
170                            .map(|(n, v)| (n.clone(), v.clone()));
171                        if let Some(item) = found {
172                            result.push(item);
173                        }
174                    }
175                    return Ok(RValue::List(RList::new(result)));
176                }
177
178                let indices = idx_vec.to_integers();
179                // Drop zeros, then check sign consistency
180                let nonzero: Vec<Option<i64>> = indices
181                    .iter()
182                    .filter(|x| !matches!(x, Some(0)))
183                    .copied()
184                    .collect();
185                let has_pos = nonzero.iter().any(|x| x.map(|i| i > 0).unwrap_or(false));
186                let has_neg = nonzero.iter().any(|x| x.map(|i| i < 0).unwrap_or(false));
187                if has_pos && has_neg {
188                    return Err(IndexingError::MixedSubscripts.into());
189                }
190                if has_neg {
191                    // Negative indexing: exclude those positions
192                    let exclude: Vec<usize> = nonzero
193                        .iter()
194                        .filter_map(|x| x.and_then(|i| usize::try_from(-i).ok()))
195                        .collect();
196                    let result: Vec<_> = list
197                        .values
198                        .iter()
199                        .enumerate()
200                        .filter(|(i, _)| !exclude.contains(&(i + 1)))
201                        .map(|(_, v)| v.clone())
202                        .collect();
203                    return Ok(RValue::List(RList::new(result)));
204                }
205                let mut result = Vec::new();
206                for i in nonzero.iter().flatten() {
207                    let i = usize::try_from(*i).unwrap_or(0);
208                    if i > 0 && i <= list.values.len() {
209                        result.push(list.values[i - 1].clone());
210                    }
211                }
212                Ok(RValue::List(RList::new(result)))
213            }
214            _ => Err(IndexingError::InvalidIndexType.into()),
215        },
216        RValue::Environment(target_env) => {
217            // env["key"] — look up variable(s) in the environment, return as a named list
218            match &idx_val {
219                RValue::Vector(idx_vec) => {
220                    if let Vector::Character(names) = &idx_vec.inner {
221                        let mut result = Vec::new();
222                        for name in names.iter().flatten() {
223                            let val = target_env.get(name).unwrap_or(RValue::Null);
224                            result.push((Some(name.clone()), val));
225                        }
226                        return Ok(RValue::List(RList::new(result)));
227                    }
228                    Err(IndexingError::InvalidIndexType.into())
229                }
230                RValue::Null => Ok(RValue::List(RList::new(Vec::new()))),
231                _ => Err(IndexingError::InvalidIndexType.into()),
232            }
233        }
234        // NULL[anything] returns NULL in R
235        RValue::Null => Ok(RValue::Null),
236        _ => Err(IndexingError::NotSubsettable.into()),
237    }
238}
239
240fn eval_matrix_index(
241    interp: &Interpreter,
242    obj: &RValue,
243    indices: &[Arg],
244    env: &Environment,
245) -> Result<RValue, RFlow> {
246    let (data, dim_attr) = match obj {
247        RValue::Vector(rv) => (&rv.inner, rv.get_attr("dim")),
248        RValue::List(list) => {
249            return eval_list_2d_index(interp, list, indices, env);
250        }
251        _ => {
252            return Err(IndexingError::IncorrectDimensions.into());
253        }
254    };
255
256    let dims: Vec<Option<i64>> = match dim_attr {
257        Some(RValue::Vector(rv)) => match &rv.inner {
258            Vector::Integer(d) => d.iter_opt().collect(),
259            _ => {
260                return Err(IndexingError::IncorrectDimensions.into());
261            }
262        },
263        _ => {
264            return Err(IndexingError::IncorrectDimensions.into());
265        }
266    };
267
268    if dims.len() < 2 {
269        return Err(IndexingError::IncorrectDimensions.into());
270    }
271    let nrow = usize::try_from(dims[0].unwrap_or(0)).unwrap_or(0);
272    let ncol = usize::try_from(dims[1].unwrap_or(0)).unwrap_or(0);
273
274    let row_idx = if let Some(val_expr) = &indices[0].value {
275        Some(interp.eval_in(val_expr, env)?)
276    } else {
277        None
278    };
279
280    let col_idx = if let Some(val_expr) = &indices[1].value {
281        Some(interp.eval_in(val_expr, env)?)
282    } else {
283        None
284    };
285
286    // Get dimnames for character index resolution
287    let dimnames = match obj {
288        RValue::Vector(rv) => rv.get_attr("dimnames"),
289        _ => None,
290    };
291    let row_names = extract_dim_names(dimnames, 0);
292    let col_names = extract_dim_names(dimnames, 1);
293
294    let rows: Vec<usize> = resolve_dim_index(&row_idx, nrow, &row_names)?;
295    let cols: Vec<usize> = resolve_dim_index(&col_idx, ncol, &col_names)?;
296
297    // Collect flat indices in column-major order
298    let flat_indices: Vec<usize> = cols
299        .iter()
300        .flat_map(|&j| rows.iter().map(move |&i| j * nrow + i))
301        .collect();
302
303    let result = data.select_indices(&flat_indices);
304
305    if rows.len() == 1 && cols.len() == 1 {
306        return Ok(RValue::vec(result));
307    }
308
309    let mut rv = RVector::from(result);
310    if rows.len() > 1 || cols.len() > 1 {
311        rv.set_attr(
312            "dim".to_string(),
313            RValue::vec(Vector::Integer(
314                vec![
315                    Some(i64::try_from(rows.len())?),
316                    Some(i64::try_from(cols.len())?),
317                ]
318                .into(),
319            )),
320        );
321    }
322    Ok(RValue::Vector(rv))
323}
324
325fn eval_list_2d_index(
326    interp: &Interpreter,
327    list: &RList,
328    indices: &[Arg],
329    env: &Environment,
330) -> Result<RValue, RFlow> {
331    let is_df = if let Some(RValue::Vector(rv)) = list.get_attr("class") {
332        if let Vector::Character(cls) = &rv.inner {
333            cls.iter().any(|c| c.as_deref() == Some("data.frame"))
334        } else {
335            false
336        }
337    } else {
338        false
339    };
340    if !is_df {
341        if let Some(val_expr) = &indices[0].value {
342            let idx_val = interp.eval_in(val_expr, env)?;
343            return match &idx_val {
344                RValue::Vector(iv) => {
345                    let i = usize::try_from(iv.as_integer_scalar().unwrap_or(0)).unwrap_or(0);
346                    if i > 0 && i <= list.values.len() {
347                        Ok(list.values[i - 1].1.clone())
348                    } else {
349                        Ok(RValue::Null)
350                    }
351                }
352                _ => Ok(RValue::Null),
353            };
354        }
355        return Ok(RValue::Null);
356    }
357
358    // Extract `drop` named argument (defaults to TRUE in R)
359    let drop = extract_drop_arg(interp, indices, env)?;
360
361    let col_idx = if let Some(val_expr) = &indices[1].value {
362        Some(interp.eval_in(val_expr, env)?)
363    } else {
364        None
365    };
366
367    let selected_cols: Vec<(Option<String>, RValue)> = match &col_idx {
368        None => list.values.clone(),
369        Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Character(_)) => {
370            let Vector::Character(names) = &rv.inner else {
371                unreachable!()
372            };
373            names
374                .iter()
375                .filter_map(|n| {
376                    n.as_ref().and_then(|name| {
377                        list.values
378                            .iter()
379                            .find(|(k, _)| k.as_ref() == Some(name))
380                            .cloned()
381                    })
382                })
383                .collect()
384        }
385        Some(RValue::Vector(rv)) => {
386            let idxs = rv.to_integers();
387            idxs.iter()
388                .filter_map(|i| {
389                    i.and_then(|i| {
390                        let i = usize::try_from(i - 1).ok()?;
391                        list.values.get(i).cloned()
392                    })
393                })
394                .collect()
395        }
396        _ => list.values.clone(),
397    };
398
399    let row_idx = if let Some(val_expr) = &indices[0].value {
400        Some(interp.eval_in(val_expr, env)?)
401    } else {
402        None
403    };
404
405    // Number of rows in the data frame (from first column)
406    let nrows = selected_cols.first().map(|(_, v)| v.length()).unwrap_or(0);
407
408    if row_idx.is_none() {
409        // No row subsetting — drop single column to vector if drop=TRUE
410        if drop && col_idx.is_some() && selected_cols.len() == 1 {
411            return Ok(selected_cols
412                .into_iter()
413                .next()
414                .expect("selected_cols.len() == 1 guarantees an element")
415                .1);
416        }
417
418        let col_names: Vec<Option<String>> = selected_cols.iter().map(|(n, _)| n.clone()).collect();
419        let mut result = RList::new(selected_cols);
420        result.set_attr(
421            "class".to_string(),
422            RValue::vec(Vector::Character(
423                vec![Some("data.frame".to_string())].into(),
424            )),
425        );
426        result.set_attr(
427            "names".to_string(),
428            RValue::vec(Vector::Character(col_names.into())),
429        );
430        let row_names_attr = subset_row_names(
431            list,
432            &(1..=i64::try_from(nrows).unwrap_or(0))
433                .map(Some)
434                .collect::<Vec<_>>(),
435        );
436        result.set_attr("row.names".to_string(), row_names_attr);
437        return Ok(RValue::List(result));
438    }
439
440    let int_rows: Vec<Option<i64>> = resolve_df_row_index(&row_idx, nrows, list)?;
441
442    // Drop single column to vector when drop=TRUE (R default)
443    if drop && selected_cols.len() == 1 {
444        if let RValue::Vector(rv) = &selected_cols[0].1 {
445            return index_by_integer(interp, &rv.inner, &int_rows);
446        }
447        return Ok(selected_cols[0].1.clone());
448    }
449
450    let mut result_cols = Vec::new();
451    for (name, col_val) in &selected_cols {
452        if let RValue::Vector(rv) = col_val {
453            let indexed = index_by_integer(interp, &rv.inner, &int_rows)?;
454            result_cols.push((name.clone(), indexed));
455        } else {
456            result_cols.push((name.clone(), col_val.clone()));
457        }
458    }
459    let col_names: Vec<Option<String>> = result_cols.iter().map(|(n, _)| n.clone()).collect();
460    let mut result = RList::new(result_cols);
461    result.set_attr(
462        "class".to_string(),
463        RValue::vec(Vector::Character(
464            vec![Some("data.frame".to_string())].into(),
465        )),
466    );
467    result.set_attr(
468        "names".to_string(),
469        RValue::vec(Vector::Character(col_names.into())),
470    );
471    // Preserve selected row names from the original data frame
472    let row_names_attr = subset_row_names(list, &int_rows);
473    result.set_attr("row.names".to_string(), row_names_attr);
474    Ok(RValue::List(result))
475}
476
477/// Extract the `drop` named argument from index args (3rd+ position).
478/// Returns `true` if `drop` is not specified (R default).
479fn extract_drop_arg(
480    interp: &Interpreter,
481    indices: &[Arg],
482    env: &Environment,
483) -> Result<bool, RFlow> {
484    for arg in indices.iter().skip(2) {
485        if arg.name.as_deref() == Some("drop") {
486            if let Some(val_expr) = &arg.value {
487                let val = interp.eval_in(val_expr, env)?;
488                if let Some(rv) = val.as_vector() {
489                    if let Some(b) = rv.as_logical_scalar() {
490                        return Ok(b);
491                    }
492                }
493            }
494            return Ok(true);
495        }
496    }
497    Ok(true) // default: drop = TRUE
498}
499
500/// Resolve data frame row indices, handling positive integers, negative integers,
501/// logical masks, and character row names. Returns 1-based positive indices.
502fn resolve_df_row_index(
503    row_idx: &Option<RValue>,
504    nrows: usize,
505    list: &RList,
506) -> Result<Vec<Option<i64>>, RFlow> {
507    match row_idx {
508        None => Ok((1..=i64::try_from(nrows).unwrap_or(0)).map(Some).collect()),
509        Some(RValue::Vector(rv)) => {
510            match &rv.inner {
511                // Logical mask
512                Vector::Logical(lv) => Ok(lv
513                    .iter()
514                    .enumerate()
515                    .filter(|(_, v)| v.unwrap_or(false))
516                    .filter_map(|(i, _)| i64::try_from(i).ok().map(|i| Some(i + 1)))
517                    .collect()),
518                // Character: look up in row.names
519                Vector::Character(names) => {
520                    let row_names = get_row_names_vec(list);
521                    let mut result = Vec::new();
522                    for name in names.iter() {
523                        match name {
524                            Some(n) => {
525                                let pos = row_names
526                                    .iter()
527                                    .position(|rn| rn.as_deref() == Some(n.as_str()));
528                                match pos {
529                                    Some(p) => result.push(Some(i64::try_from(p + 1)?)),
530                                    None => {
531                                        return Err(IndexingError::UndefinedRow {
532                                            name: n.clone(),
533                                        }
534                                        .into());
535                                    }
536                                }
537                            }
538                            None => result.push(None),
539                        }
540                    }
541                    Ok(result)
542                }
543                // Numeric: handle positive and negative
544                _ => {
545                    let ints = rv.to_integers();
546                    // Filter zeros
547                    let nonzero: Vec<Option<i64>> = ints
548                        .iter()
549                        .filter(|x| !matches!(x, Some(0)))
550                        .copied()
551                        .collect();
552                    let has_pos = nonzero.iter().any(|x| x.map(|i| i > 0).unwrap_or(false));
553                    let has_neg = nonzero.iter().any(|x| x.map(|i| i < 0).unwrap_or(false));
554                    if has_pos && has_neg {
555                        return Err(IndexingError::MixedSubscripts.into());
556                    }
557                    if has_neg {
558                        // Negative indexing: exclude those rows
559                        let exclude: Vec<usize> = nonzero
560                            .iter()
561                            .filter_map(|x| x.and_then(|i| usize::try_from(-i).ok()))
562                            .collect();
563                        Ok((1..=nrows)
564                            .filter(|i| !exclude.contains(i))
565                            .filter_map(|i| i64::try_from(i).ok().map(Some))
566                            .collect())
567                    } else {
568                        Ok(nonzero)
569                    }
570                }
571            }
572        }
573        _ => Ok(vec![]),
574    }
575}
576
577/// Get row.names from a data frame list as character vector.
578fn get_row_names_vec(list: &RList) -> Vec<Option<String>> {
579    if let Some(rn_attr) = list.get_attr("row.names") {
580        if let Some(rn_vec) = rn_attr.as_vector() {
581            return rn_vec.to_characters();
582        }
583    }
584    vec![]
585}
586
587pub(crate) fn index_by_integer(
588    _interp: &Interpreter,
589    v: &Vector,
590    indices: &[Option<i64>],
591) -> Result<RValue, RFlow> {
592    macro_rules! index_vec_option {
593        ($vals:expr, $variant:ident) => {{
594            let result: Vec<_> = indices
595                .iter()
596                .map(|idx| {
597                    idx.and_then(|i| {
598                        let i = usize::try_from(i).unwrap_or(0);
599                        if i > 0 && i <= $vals.len() {
600                            $vals[i - 1].clone().into()
601                        } else {
602                            None
603                        }
604                    })
605                })
606                .collect();
607            Ok(RValue::vec(Vector::$variant(result.into())))
608        }};
609    }
610
611    macro_rules! index_vec_buffer {
612        ($vals:expr, $variant:ident) => {{
613            let result: Vec<_> = indices
614                .iter()
615                .map(|idx| {
616                    idx.and_then(|i| {
617                        let i = usize::try_from(i).unwrap_or(0);
618                        if i > 0 && i <= $vals.len() {
619                            $vals.get_opt(i - 1)
620                        } else {
621                            None
622                        }
623                    })
624                })
625                .collect();
626            Ok(RValue::vec(Vector::$variant(result.into())))
627        }};
628    }
629
630    match v {
631        Vector::Raw(vals) => {
632            let result: Vec<u8> = indices
633                .iter()
634                .map(|idx| {
635                    idx.and_then(|i| {
636                        let i = usize::try_from(i).unwrap_or(0);
637                        if i > 0 && i <= vals.len() {
638                            Some(vals[i - 1])
639                        } else {
640                            Some(0u8)
641                        }
642                    })
643                    .unwrap_or(0u8)
644                })
645                .collect();
646            Ok(RValue::vec(Vector::Raw(result)))
647        }
648        Vector::Double(vals) => index_vec_buffer!(vals, Double),
649        Vector::Integer(vals) => index_vec_buffer!(vals, Integer),
650        Vector::Logical(vals) => index_vec_option!(vals, Logical),
651        Vector::Complex(vals) => index_vec_option!(vals, Complex),
652        Vector::Character(vals) => index_vec_option!(vals, Character),
653    }
654}
655
656fn index_by_negative(
657    _interp: &Interpreter,
658    v: &Vector,
659    indices: &[Option<i64>],
660) -> Result<RValue, RFlow> {
661    let exclude: Vec<usize> = indices
662        .iter()
663        .filter_map(|x| x.and_then(|i| usize::try_from(-i).ok()))
664        .collect();
665
666    macro_rules! filter_vec_option {
667        ($vals:expr, $variant:ident) => {{
668            let result: Vec<_> = $vals
669                .iter()
670                .enumerate()
671                .filter(|(i, _)| !exclude.contains(&(i + 1)))
672                .map(|(_, v)| v.clone())
673                .collect();
674            Ok(RValue::vec(Vector::$variant(result.into())))
675        }};
676    }
677
678    macro_rules! filter_vec_buffer {
679        ($vals:expr, $variant:ident) => {{
680            let result: Vec<_> = $vals
681                .iter()
682                .enumerate()
683                .filter(|(i, _)| !exclude.contains(&(i + 1)))
684                .map(|(_, v)| v)
685                .collect();
686            Ok(RValue::vec(Vector::$variant(result.into())))
687        }};
688    }
689
690    match v {
691        Vector::Raw(vals) => {
692            let result: Vec<u8> = vals
693                .iter()
694                .enumerate()
695                .filter(|(i, _)| !exclude.contains(&(i + 1)))
696                .map(|(_, &v)| v)
697                .collect();
698            Ok(RValue::vec(Vector::Raw(result)))
699        }
700        Vector::Double(vals) => filter_vec_buffer!(vals, Double),
701        Vector::Integer(vals) => filter_vec_buffer!(vals, Integer),
702        Vector::Logical(vals) => filter_vec_option!(vals, Logical),
703        Vector::Complex(vals) => filter_vec_option!(vals, Complex),
704        Vector::Character(vals) => filter_vec_option!(vals, Character),
705    }
706}
707
708fn index_by_logical(
709    _interp: &Interpreter,
710    v: &Vector,
711    mask: &[Option<bool>],
712) -> Result<RValue, RFlow> {
713    let vlen = v.len();
714    if mask.is_empty() {
715        // Empty mask selects nothing
716        return Ok(RValue::vec(v.select_indices(&[])));
717    }
718
719    // Recycle mask to vector length.
720    // For each position: Some(true) -> include element, Some(false) -> skip,
721    // None (NA) -> include NA.
722    let recycled_mask = |i: usize| -> Option<bool> { mask[i % mask.len()] };
723
724    macro_rules! mask_vec_option {
725        ($vals:expr, $variant:ident) => {{
726            let result: Vec<_> = (0..vlen)
727                .filter_map(|i| match recycled_mask(i) {
728                    Some(true) => Some($vals.get(i).cloned().unwrap_or(None)),
729                    Some(false) => None,
730                    None => Some(None), // NA in mask -> NA in result
731                })
732                .collect();
733            Ok(RValue::vec(Vector::$variant(result.into())))
734        }};
735    }
736
737    macro_rules! mask_vec_buffer {
738        ($vals:expr, $variant:ident) => {{
739            let result: Vec<Option<_>> = (0..vlen)
740                .filter_map(|i| match recycled_mask(i) {
741                    Some(true) => Some($vals.get_opt(i)),
742                    Some(false) => None,
743                    None => Some(None), // NA in mask -> NA in result
744                })
745                .collect();
746            Ok(RValue::vec(Vector::$variant(result.into())))
747        }};
748    }
749
750    match v {
751        Vector::Raw(vals) => {
752            let result: Vec<u8> = (0..vlen)
753                .filter_map(|i| match recycled_mask(i) {
754                    Some(true) => Some(vals.get(i).copied().unwrap_or(0)),
755                    Some(false) => None,
756                    None => Some(0u8), // Raw has no NA representation
757                })
758                .collect();
759            Ok(RValue::vec(Vector::Raw(result)))
760        }
761        Vector::Double(vals) => mask_vec_buffer!(vals, Double),
762        Vector::Integer(vals) => mask_vec_buffer!(vals, Integer),
763        Vector::Logical(vals) => mask_vec_option!(vals, Logical),
764        Vector::Complex(vals) => mask_vec_option!(vals, Complex),
765        Vector::Character(vals) => mask_vec_option!(vals, Character),
766    }
767}
768
769/// Resolve a row or column index against dimension size and optional dimnames.
770/// Returns 0-based indices.
771fn resolve_dim_index(
772    idx: &Option<RValue>,
773    dim_size: usize,
774    dim_names: &Option<Vec<String>>,
775) -> Result<Vec<usize>, RFlow> {
776    match idx {
777        None => Ok((0..dim_size).collect()),
778        Some(RValue::Vector(rv)) => {
779            // Logical indices: filter by mask (recycled to dim_size)
780            if let Vector::Logical(mask) = &rv.inner {
781                let result: Vec<usize> = (0..dim_size)
782                    .filter(|&i| {
783                        if mask.is_empty() {
784                            false
785                        } else {
786                            mask[i % mask.len()].unwrap_or(false)
787                        }
788                    })
789                    .collect();
790                return Ok(result);
791            }
792            // Character indices: look up in dimnames
793            if matches!(rv.inner, Vector::Character(_)) {
794                let names = dim_names.as_ref().ok_or(IndexingError::NoDimnames)?;
795                let chars = rv.inner.to_characters();
796                let mut result = Vec::new();
797                for ch in chars.into_iter().flatten() {
798                    let pos = names
799                        .iter()
800                        .position(|n| n == &ch)
801                        .ok_or_else(|| IndexingError::SubscriptOutOfBounds { name: ch.clone() })?;
802                    result.push(pos);
803                }
804                return Ok(result);
805            }
806            // Numeric indices (positive or negative)
807            let ints = rv.to_integers();
808            let nonzero: Vec<Option<i64>> = ints
809                .iter()
810                .filter(|x| !matches!(x, Some(0)))
811                .copied()
812                .collect();
813            let has_neg = nonzero.iter().any(|x| x.map(|i| i < 0).unwrap_or(false));
814            if has_neg {
815                let exclude: Vec<usize> = nonzero
816                    .iter()
817                    .filter_map(|x| x.and_then(|i| usize::try_from(-i).ok()))
818                    .collect();
819                return Ok((0..dim_size)
820                    .filter(|i| !exclude.contains(&(i + 1)))
821                    .collect());
822            }
823            Ok(nonzero
824                .iter()
825                .filter_map(|x| x.and_then(|i| usize::try_from(i - 1).ok()))
826                .collect())
827        }
828        _ => Err(IndexingError::InvalidSubscriptType.into()),
829    }
830}
831
832/// Subset row.names from a data frame list using 1-based integer indices.
833/// If the original has row names, select the corresponding ones.
834/// Otherwise generate fresh 1-based row names.
835fn subset_row_names(list: &RList, int_rows: &[Option<i64>]) -> RValue {
836    if let Some(rn_attr) = list.get_attr("row.names") {
837        if let Some(rn_vec) = rn_attr.as_vector() {
838            let orig = rn_vec.to_characters();
839            let selected: Vec<Option<String>> = int_rows
840                .iter()
841                .map(|idx| {
842                    idx.and_then(|i| {
843                        let i = usize::try_from(i - 1).ok()?;
844                        orig.get(i).cloned().flatten()
845                    })
846                })
847                .collect();
848            return RValue::vec(Vector::Character(selected.into()));
849        }
850    }
851    // Fallback: generate 1-based row names
852    let row_names: Vec<Option<i64>> = (1..=i64::try_from(int_rows.len()).unwrap_or(0))
853        .map(Some)
854        .collect();
855    RValue::vec(Vector::Integer(row_names.into()))
856}
857
858/// Extract a dimnames component (row names at index 0, col names at index 1).
859fn extract_dim_names(dimnames: Option<&RValue>, dim: usize) -> Option<Vec<String>> {
860    let RValue::List(list) = dimnames? else {
861        return None;
862    };
863    let (_, val) = list.values.get(dim)?;
864    let vec = val.as_vector()?;
865    let chars = vec.to_characters();
866    let names: Vec<String> = chars.into_iter().flatten().collect();
867    if names.is_empty() {
868        None
869    } else {
870        Some(names)
871    }
872}
873
874pub(super) fn eval_index_double(
875    interp: &Interpreter,
876    object: &Expr,
877    indices: &[Arg],
878    env: &Environment,
879) -> Result<RValue, RFlow> {
880    let obj = interp.eval_in(object, env)?;
881    if indices.is_empty() {
882        return Ok(obj);
883    }
884
885    let idx_val = if let Some(val_expr) = &indices[0].value {
886        interp.eval_in(val_expr, env)?
887    } else {
888        return Ok(obj);
889    };
890
891    match &obj {
892        RValue::List(list) => match &idx_val {
893            RValue::Vector(rv) if matches!(rv.inner, Vector::Character(_)) => {
894                let Vector::Character(names) = &rv.inner else {
895                    unreachable!()
896                };
897                if let Some(Some(name)) = names.first() {
898                    for (n, v) in &list.values {
899                        if n.as_ref() == Some(name) {
900                            return Ok(v.clone());
901                        }
902                    }
903                }
904                Ok(RValue::Null)
905            }
906            RValue::Vector(v) => {
907                let i = usize::try_from(v.as_integer_scalar().unwrap_or(0)).unwrap_or(0);
908                if i > 0 && i <= list.values.len() {
909                    Ok(list.values[i - 1].1.clone())
910                } else {
911                    Ok(RValue::Null)
912                }
913            }
914            _ => Ok(RValue::Null),
915        },
916        RValue::Vector(v) => {
917            // Character indexing: look up name in the "names" attribute
918            if let RValue::Vector(iv) = &idx_val {
919                if let Vector::Character(idx_names) = &iv.inner {
920                    if let Some(Some(name)) = idx_names.first() {
921                        if let Some(names_attr) = v.get_attr("names") {
922                            if let Some(names_vec) = names_attr.as_vector() {
923                                let name_strs = names_vec.to_characters();
924                                for (j, n) in name_strs.iter().enumerate() {
925                                    if n.as_deref() == Some(name.as_str()) && j < v.len() {
926                                        return Ok(extract_vector_element(v, j));
927                                    }
928                                }
929                            }
930                        }
931                        return Ok(RValue::Null);
932                    }
933                }
934            }
935            let i = match &idx_val {
936                RValue::Vector(iv) => {
937                    usize::try_from(iv.as_integer_scalar().unwrap_or(0)).unwrap_or(0)
938                }
939                _ => 0,
940            };
941            if i > 0 && i <= v.len() {
942                Ok(extract_vector_element(v, i - 1))
943            } else {
944                Ok(RValue::Null)
945            }
946        }
947        RValue::Language(lang) => {
948            let i = match &idx_val {
949                RValue::Vector(iv) => {
950                    usize::try_from(iv.as_integer_scalar().unwrap_or(0)).unwrap_or(0)
951                }
952                _ => 0,
953            };
954            lang.language_element(i).ok_or_else(|| {
955                RFlow::Error(RError::new(
956                    RErrorKind::Index,
957                    format!(
958                        "subscript out of bounds: index {} into language object of length {}",
959                        i,
960                        lang.language_length()
961                    ),
962                ))
963            })
964        }
965        RValue::Environment(target_env) => {
966            // env[["key"]] — look up a variable in the environment
967            if let Some(name) = idx_val.as_vector().and_then(|v| v.as_character_scalar()) {
968                Ok(target_env.get(&name).unwrap_or(RValue::Null))
969            } else {
970                Ok(RValue::Null)
971            }
972        }
973        // NULL[[anything]] returns NULL in R
974        RValue::Null => Ok(RValue::Null),
975        _ => Err(IndexingError::NotSubsettable.into()),
976    }
977}
978
979/// Extract a single element from an RVector at `idx` (0-based).
980pub fn extract_vector_element(v: &RVector, idx: usize) -> RValue {
981    match &v.inner {
982        Vector::Raw(vals) => RValue::vec(Vector::Raw(vec![vals[idx]])),
983        Vector::Double(vals) => RValue::vec(Vector::Double(vec![vals.get_opt(idx)].into())),
984        Vector::Integer(vals) => RValue::vec(Vector::Integer(vec![vals.get_opt(idx)].into())),
985        Vector::Logical(vals) => RValue::vec(Vector::Logical(vec![vals[idx]].into())),
986        Vector::Complex(vals) => RValue::vec(Vector::Complex(vec![vals[idx]].into())),
987        Vector::Character(vals) => RValue::vec(Vector::Character(vec![vals[idx].clone()].into())),
988    }
989}
990
991pub(super) fn eval_dollar(
992    interp: &Interpreter,
993    object: &Expr,
994    member: &str,
995    env: &Environment,
996) -> Result<RValue, RFlow> {
997    let obj = interp.eval_in(object, env)?;
998    match &obj {
999        RValue::List(list) => {
1000            for (name, val) in &list.values {
1001                if name.as_deref() == Some(member) {
1002                    return Ok(val.clone());
1003                }
1004            }
1005            Ok(RValue::Null)
1006        }
1007        RValue::Environment(e) => e
1008            .get(member)
1009            .ok_or_else(|| RError::new(RErrorKind::Name, member.to_string()).into()),
1010        _ => Ok(RValue::Null),
1011    }
1012}