Skip to main content

r/interpreter/builtins/
pre_eval.rs

1//! Pre-eval builtins — functions that intercept before argument evaluation.
2//! Each is auto-registered via `#[pre_eval_builtin]`.
3//! The interpreter is accessed via the `BuiltinContext` passed at dispatch time.
4
5use std::collections::BTreeSet;
6
7use crate::interpreter::environment::Environment;
8use crate::interpreter::value::*;
9use crate::interpreter::BuiltinContext;
10use crate::parser::ast::{Arg, Expr};
11use itertools::Itertools;
12use minir_macros::pre_eval_builtin;
13
14#[derive(Clone)]
15struct DataFrameColumn {
16    name: String,
17    value: RValue,
18    row_count: usize,
19    row_names: Option<RowNames>,
20}
21
22type RowNames = Vec<Option<String>>;
23
24fn is_data_frame_control_arg(name: &str) -> bool {
25    matches!(
26        name,
27        "row.names" | "stringsAsFactors" | "check.rows" | "check.names" | "fix.empty.names"
28    )
29}
30
31fn sanitize_data_frame_name(source: &str) -> String {
32    let mut out = String::new();
33    for ch in source.chars() {
34        if ch.is_ascii_alphanumeric() || ch == '.' || ch == '_' {
35            out.push(ch);
36        } else {
37            out.push('.');
38        }
39    }
40
41    if out.is_empty() || out == "." {
42        out = "X".to_string();
43    }
44
45    if out
46        .chars()
47        .next()
48        .is_some_and(|ch| !(ch.is_ascii_alphabetic() || ch == '.'))
49    {
50        out.insert(0, 'X');
51    }
52
53    out
54}
55
56fn default_data_frame_name(expr: Option<&Expr>, index: usize) -> String {
57    expr.map(|expr| sanitize_data_frame_name(&deparse_expr(expr)))
58        .unwrap_or_else(|| format!("V{}", index))
59}
60
61fn row_names_to_strings(value: &RValue) -> Option<RowNames> {
62    match value {
63        RValue::Vector(rv) => match &rv.inner {
64            Vector::Character(values) => Some(values.to_vec()),
65            Vector::Integer(values) => Some(
66                values
67                    .iter_opt()
68                    .map(|v| v.map(|v| v.to_string()))
69                    .collect(),
70            ),
71            Vector::Double(values) => {
72                Some(values.iter_opt().map(|v| v.map(format_r_double)).collect())
73            }
74            _ => None,
75        },
76        _ => None,
77    }
78}
79
80fn vector_names(rv: &RVector) -> Option<RowNames> {
81    rv.get_attr("names").and_then(row_names_to_strings)
82}
83
84fn expr_vector_names(expr: &Expr) -> Option<RowNames> {
85    let Expr::Call { func, args, .. } = expr else {
86        return None;
87    };
88    let Expr::Symbol(name) = func.as_ref() else {
89        return None;
90    };
91    if name != "c" || !args.iter().any(|arg| arg.name.is_some()) {
92        return None;
93    }
94    Some(args.iter().map(|arg| arg.name.clone()).collect())
95}
96
97fn matrix_dimnames(rv: &RVector) -> (Option<RowNames>, Option<RowNames>) {
98    let Some(RValue::List(dimnames)) = rv.get_attr("dimnames") else {
99        return (None, None);
100    };
101
102    let row_names = dimnames
103        .values
104        .first()
105        .and_then(|(_, value)| row_names_to_strings(value));
106    let col_names = dimnames
107        .values
108        .get(1)
109        .and_then(|(_, value)| row_names_to_strings(value));
110
111    (row_names, col_names)
112}
113
114fn factorize_character_vector(values: Vec<Option<String>>) -> Result<RValue, RError> {
115    let levels: Vec<String> = values.iter().flatten().unique().sorted().cloned().collect();
116
117    let codes: Vec<Option<i64>> = values
118        .iter()
119        .map(|value| match value {
120            Some(value) => levels
121                .iter()
122                .position(|level| level == value)
123                .map(|idx| i64::try_from(idx + 1))
124                .transpose(),
125            None => Ok(None),
126        })
127        .collect::<Result<_, _>>()?;
128
129    let mut rv = RVector::from(Vector::Integer(codes.into()));
130    rv.set_attr(
131        "levels".to_string(),
132        RValue::vec(Vector::Character(
133            levels.into_iter().map(Some).collect::<Vec<_>>().into(),
134        )),
135    );
136    rv.set_attr(
137        "class".to_string(),
138        RValue::vec(Vector::Character(vec![Some("factor".to_string())].into())),
139    );
140    Ok(RValue::Vector(rv))
141}
142
143fn maybe_factorize_strings(value: RValue, strings_as_factors: bool) -> Result<RValue, RError> {
144    if !strings_as_factors {
145        return Ok(value);
146    }
147
148    match value {
149        RValue::Vector(rv)
150            if matches!(rv.inner, Vector::Character(_)) && rv.get_attr("class").is_none() =>
151        {
152            let Vector::Character(values) = rv.inner else {
153                unreachable!();
154            };
155            factorize_character_vector(values.to_vec())
156        }
157        other => Ok(other),
158    }
159}
160
161fn strip_names_attr(value: &mut RValue) {
162    match value {
163        RValue::Vector(rv) => {
164            rv.attrs.as_mut().map(|attrs| attrs.shift_remove("names"));
165        }
166        RValue::List(list) => {
167            list.attrs.as_mut().map(|attrs| attrs.shift_remove("names"));
168        }
169        _ => {}
170    }
171}
172
173pub(super) fn recycle_value(value: &RValue, target_len: usize) -> Result<RValue, RError> {
174    match value {
175        RValue::Vector(rv) => {
176            let mut recycled = rv.clone();
177            recycled.inner = match &rv.inner {
178                Vector::Raw(values) => Vector::Raw(
179                    (0..target_len)
180                        .map(|idx| values[idx % values.len()])
181                        .collect::<Vec<_>>(),
182                ),
183                Vector::Logical(values) => Vector::Logical(
184                    (0..target_len)
185                        .map(|idx| values[idx % values.len()])
186                        .collect::<Vec<_>>()
187                        .into(),
188                ),
189                Vector::Integer(values) => Vector::Integer(
190                    (0..target_len)
191                        .map(|idx| values.get_opt(idx % values.len()))
192                        .collect::<Vec<_>>()
193                        .into(),
194                ),
195                Vector::Double(values) => Vector::Double(
196                    (0..target_len)
197                        .map(|idx| values.get_opt(idx % values.len()))
198                        .collect::<Vec<_>>()
199                        .into(),
200                ),
201                Vector::Complex(values) => Vector::Complex(
202                    (0..target_len)
203                        .map(|idx| values[idx % values.len()])
204                        .collect::<Vec<_>>()
205                        .into(),
206                ),
207                Vector::Character(values) => Vector::Character(
208                    (0..target_len)
209                        .map(|idx| values[idx % values.len()].clone())
210                        .collect::<Vec<_>>()
211                        .into(),
212                ),
213            };
214            Ok(RValue::Vector(recycled))
215        }
216        RValue::List(list) => {
217            let mut recycled = list.clone();
218            recycled.values = (0..target_len)
219                .map(|idx| {
220                    let (name, value) = &list.values[idx % list.values.len()];
221                    (name.clone(), value.clone())
222                })
223                .collect();
224            Ok(RValue::List(recycled))
225        }
226        other if target_len == 1 => Ok(other.clone()),
227        other if other.length() == 1 => Ok(other.clone()),
228        other => Err(RError::other(format!(
229            "cannot recycle {} to {} rows",
230            other.type_name(),
231            target_len
232        ))),
233    }
234}
235
236fn matrix_columns(
237    rv: &RVector,
238    explicit_name: Option<&str>,
239) -> Result<Vec<DataFrameColumn>, RError> {
240    let Some(dims) = super::get_dim_ints(rv.get_attr("dim")) else {
241        return Ok(Vec::new());
242    };
243    if dims.len() < 2 {
244        return Ok(Vec::new());
245    }
246
247    let nrow = usize::try_from(dims[0].unwrap_or(0))?;
248    let ncol = usize::try_from(dims[1].unwrap_or(0))?;
249    let (row_names, col_names) = matrix_dimnames(rv);
250
251    let columns = match &rv.inner {
252        Vector::Raw(values) => (0..ncol)
253            .map(|col_idx| {
254                let start = col_idx * nrow;
255                DataFrameColumn {
256                    name: match (
257                        explicit_name,
258                        col_names
259                            .as_ref()
260                            .and_then(|n| n.get(col_idx))
261                            .cloned()
262                            .flatten(),
263                    ) {
264                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
265                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
266                        (None, Some(name)) => name,
267                        (None, None) => format!("X{}", col_idx + 1),
268                    },
269                    value: RValue::vec(Vector::Raw(values[start..start + nrow].to_vec())),
270                    row_count: nrow,
271                    row_names: row_names.clone(),
272                }
273            })
274            .collect(),
275        Vector::Logical(values) => (0..ncol)
276            .map(|col_idx| {
277                let start = col_idx * nrow;
278                DataFrameColumn {
279                    name: match (
280                        explicit_name,
281                        col_names
282                            .as_ref()
283                            .and_then(|n| n.get(col_idx))
284                            .cloned()
285                            .flatten(),
286                    ) {
287                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
288                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
289                        (None, Some(name)) => name,
290                        (None, None) => format!("X{}", col_idx + 1),
291                    },
292                    value: RValue::vec(Vector::Logical(
293                        values[start..start + nrow].to_vec().into(),
294                    )),
295                    row_count: nrow,
296                    row_names: row_names.clone(),
297                }
298            })
299            .collect(),
300        Vector::Integer(values) => (0..ncol)
301            .map(|col_idx| {
302                let start = col_idx * nrow;
303                DataFrameColumn {
304                    name: match (
305                        explicit_name,
306                        col_names
307                            .as_ref()
308                            .and_then(|n| n.get(col_idx))
309                            .cloned()
310                            .flatten(),
311                    ) {
312                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
313                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
314                        (None, Some(name)) => name,
315                        (None, None) => format!("X{}", col_idx + 1),
316                    },
317                    value: RValue::vec(Vector::Integer(values.slice(start, nrow))),
318                    row_count: nrow,
319                    row_names: row_names.clone(),
320                }
321            })
322            .collect(),
323        Vector::Double(values) => (0..ncol)
324            .map(|col_idx| {
325                let start = col_idx * nrow;
326                DataFrameColumn {
327                    name: match (
328                        explicit_name,
329                        col_names
330                            .as_ref()
331                            .and_then(|n| n.get(col_idx))
332                            .cloned()
333                            .flatten(),
334                    ) {
335                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
336                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
337                        (None, Some(name)) => name,
338                        (None, None) => format!("X{}", col_idx + 1),
339                    },
340                    value: RValue::vec(Vector::Double(values.slice(start, nrow))),
341                    row_count: nrow,
342                    row_names: row_names.clone(),
343                }
344            })
345            .collect(),
346        Vector::Complex(values) => (0..ncol)
347            .map(|col_idx| {
348                let start = col_idx * nrow;
349                DataFrameColumn {
350                    name: match (
351                        explicit_name,
352                        col_names
353                            .as_ref()
354                            .and_then(|n| n.get(col_idx))
355                            .cloned()
356                            .flatten(),
357                    ) {
358                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
359                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
360                        (None, Some(name)) => name,
361                        (None, None) => format!("X{}", col_idx + 1),
362                    },
363                    value: RValue::vec(Vector::Complex(
364                        values[start..start + nrow].to_vec().into(),
365                    )),
366                    row_count: nrow,
367                    row_names: row_names.clone(),
368                }
369            })
370            .collect(),
371        Vector::Character(values) => (0..ncol)
372            .map(|col_idx| {
373                let start = col_idx * nrow;
374                DataFrameColumn {
375                    name: match (
376                        explicit_name,
377                        col_names
378                            .as_ref()
379                            .and_then(|n| n.get(col_idx))
380                            .cloned()
381                            .flatten(),
382                    ) {
383                        (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
384                        (Some(prefix), None) => format!("{}.{}", prefix, col_idx + 1),
385                        (None, Some(name)) => name,
386                        (None, None) => format!("X{}", col_idx + 1),
387                    },
388                    value: RValue::vec(Vector::Character(
389                        values[start..start + nrow].to_vec().into(),
390                    )),
391                    row_count: nrow,
392                    row_names: row_names.clone(),
393                }
394            })
395            .collect(),
396    };
397
398    Ok(columns)
399}
400
401fn expand_data_frame_value(
402    value: &RValue,
403    explicit_name: Option<&str>,
404    default_name: &str,
405    fallback_row_names: Option<RowNames>,
406    strings_as_factors: bool,
407) -> Result<Vec<DataFrameColumn>, RError> {
408    match value {
409        RValue::Null => Ok(Vec::new()),
410        RValue::List(list) => {
411            let source_row_names = if get_class(value)
412                .iter()
413                .any(|class_name| class_name == "data.frame")
414            {
415                list.get_attr("row.names").and_then(row_names_to_strings)
416            } else {
417                None
418            };
419
420            let mut columns = Vec::new();
421            for (idx, (name, column_value)) in list.values.iter().enumerate() {
422                let column_name = match (explicit_name, name.as_deref()) {
423                    (Some(prefix), Some(name)) => format!("{}.{}", prefix, name),
424                    (Some(prefix), None) => format!("{}.{}", prefix, idx + 1),
425                    (None, Some(name)) => name.to_string(),
426                    (None, None) => format!("{}.{}", default_name, idx + 1),
427                };
428                let row_names = source_row_names.clone().or_else(|| match column_value {
429                    RValue::Vector(rv) => vector_names(rv),
430                    _ => None,
431                });
432                let value = maybe_factorize_strings(column_value.clone(), strings_as_factors)?;
433                columns.push(DataFrameColumn {
434                    name: column_name,
435                    row_count: column_value.length(),
436                    value,
437                    row_names,
438                });
439            }
440            Ok(columns)
441        }
442        RValue::Vector(rv) if super::get_dim_ints(rv.get_attr("dim")).is_some() => {
443            let mut columns = matrix_columns(rv, explicit_name)?;
444            for column in &mut columns {
445                column.value = maybe_factorize_strings(column.value.clone(), strings_as_factors)?;
446            }
447            Ok(columns)
448        }
449        _ => Ok(vec![DataFrameColumn {
450            name: explicit_name.unwrap_or(default_name).to_string(),
451            row_count: value.length(),
452            row_names: match value {
453                RValue::Vector(rv) => vector_names(rv).or(fallback_row_names),
454                _ => None,
455            },
456            value: maybe_factorize_strings(value.clone(), strings_as_factors)?,
457        }]),
458    }
459}
460
461fn automatic_row_names(count: usize) -> RValue {
462    RValue::vec(Vector::Integer(
463        (1..=i64::try_from(count).unwrap_or(0))
464            .map(Some)
465            .collect::<Vec<_>>()
466            .into(),
467    ))
468}
469
470/// Construct a data frame from named or unnamed column vectors.
471///
472/// @param ... vectors, factors, matrices, or data frames to combine as columns
473/// @param row.names character or integer vector of row names (optional)
474/// @param stringsAsFactors if TRUE, convert character columns to factors (default FALSE)
475/// @return a data frame (list with class "data.frame")
476#[pre_eval_builtin(name = "data.frame")]
477fn pre_eval_data_frame(
478    args: &[Arg],
479    env: &Environment,
480    context: &BuiltinContext,
481) -> Result<RValue, RError> {
482    let mut explicit_row_names = None;
483    let mut strings_as_factors = false;
484    let mut columns = Vec::new();
485    let mut unnamed_index = 0usize;
486
487    context.with_interpreter(|interp| {
488        for arg in args {
489            let Some(name) = arg.name.as_deref() else {
490                continue;
491            };
492            if !is_data_frame_control_arg(name) {
493                continue;
494            }
495            let Some(expr) = arg.value.as_ref() else {
496                continue;
497            };
498            let value = interp.eval_in(expr, env).map_err(RError::from)?;
499            match name {
500                "row.names" => explicit_row_names = Some(value),
501                "stringsAsFactors" => {
502                    strings_as_factors = value
503                        .as_vector()
504                        .and_then(Vector::as_logical_scalar)
505                        .unwrap_or(false);
506                }
507                _ => {}
508            }
509        }
510
511        // Use a child environment so that each named column is visible to
512        // subsequent column expressions.  This is a miniR enhancement over
513        // GNU R (which doesn't support forward-references in data.frame()),
514        // matching the behaviour of dplyr::tibble().
515        let df_env = Environment::new_child(env);
516        for arg in args {
517            let Some(expr) = arg.value.as_ref() else {
518                continue;
519            };
520            if arg.name.as_deref().is_some_and(is_data_frame_control_arg) {
521                continue;
522            }
523
524            unnamed_index += 1;
525            let value = interp.eval_in(expr, &df_env).map_err(RError::from)?;
526
527            // Bind named columns so later columns can reference them.
528            if let Some(col_name) = arg.name.as_deref() {
529                df_env.set(col_name.to_string(), value.clone());
530            }
531
532            let default_name = default_data_frame_name(
533                if arg.name.is_none() { Some(expr) } else { None },
534                unnamed_index,
535            );
536            columns.extend(expand_data_frame_value(
537                &value,
538                arg.name.as_deref(),
539                &default_name,
540                expr_vector_names(expr),
541                strings_as_factors,
542            )?);
543        }
544        Ok::<(), RError>(())
545    })?;
546
547    let mut lengths = BTreeSet::new();
548    for column in &columns {
549        lengths.insert(column.row_count);
550    }
551
552    let target_rows = match explicit_row_names.as_ref() {
553        Some(RValue::Null) => lengths.iter().copied().max().unwrap_or(0),
554        Some(value) => value.length(),
555        None => lengths.iter().copied().max().unwrap_or(0),
556    };
557
558    if let Some(row_names) = explicit_row_names.as_ref() {
559        if !matches!(row_names, RValue::Null) && !columns.is_empty() {
560            let valid = columns.iter().all(|column| column.row_count == target_rows);
561            if !valid {
562                return Err(RError::other(
563                    "row names supplied are of the wrong length".to_string(),
564                ));
565            }
566        }
567    }
568
569    let invalid_lengths: Vec<usize> = columns
570        .iter()
571        .filter_map(|column| {
572            if column.row_count == target_rows {
573                None
574            } else if column.row_count == 0 || target_rows % column.row_count != 0 {
575                Some(column.row_count)
576            } else {
577                None
578            }
579        })
580        .collect();
581
582    if !invalid_lengths.is_empty() {
583        let mut all_lengths = lengths;
584        all_lengths.insert(target_rows);
585        return Err(RError::other(format!(
586            "arguments imply differing number of rows: {}",
587            all_lengths
588                .iter()
589                .map(|length| length.to_string())
590                .join(", ")
591        )));
592    }
593
594    let row_names_attr = match explicit_row_names {
595        Some(RValue::Null) => automatic_row_names(target_rows),
596        Some(value) => value,
597        None => columns
598            .iter()
599            .find(|column| column.row_count == target_rows)
600            .and_then(|column| column.row_names.clone())
601            .map(|names| RValue::vec(Vector::Character(names.into())))
602            .unwrap_or_else(|| automatic_row_names(target_rows)),
603    };
604
605    let mut output_columns = Vec::new();
606    for mut column in columns {
607        if column.row_count != target_rows {
608            column.value = recycle_value(&column.value, target_rows)?;
609        }
610        strip_names_attr(&mut column.value);
611        output_columns.push((Some(column.name), column.value));
612    }
613
614    let mut list = RList::new(output_columns);
615    let names: Vec<Option<String>> = list.values.iter().map(|(name, _)| name.clone()).collect();
616    list.set_attr(
617        "class".to_string(),
618        RValue::vec(Vector::Character(
619            vec![Some("data.frame".to_string())].into(),
620        )),
621    );
622    list.set_attr(
623        "names".to_string(),
624        RValue::vec(Vector::Character(names.into())),
625    );
626    list.set_attr("row.names".to_string(), row_names_attr);
627    Ok(RValue::List(list))
628}
629
630/// Evaluate an expression with error/warning/message handlers.
631///
632/// @param expr the expression to evaluate
633/// @param error handler function for error conditions (optional)
634/// @param warning handler function for warning conditions (optional)
635/// @param message handler function for message conditions (optional)
636/// @param finally expression to evaluate after expr, regardless of outcome (optional)
637/// @return the result of expr, or the return value of the matching handler
638#[pre_eval_builtin(name = "tryCatch", min_args = 1)]
639fn pre_eval_try_catch(
640    args: &[Arg],
641    env: &Environment,
642    context: &BuiltinContext,
643) -> Result<RValue, RError> {
644    use crate::interpreter::ConditionHandler;
645
646    // First unnamed arg is the expression to evaluate; also accept expr=...
647    let expr = args
648        .iter()
649        .find(|a| a.name.is_none())
650        .or_else(|| args.iter().find(|a| a.name.as_deref() == Some("expr")))
651        .and_then(|a| a.value.as_ref());
652
653    // Collect named handlers and finally expression
654    let mut handlers: Vec<(String, RValue)> = Vec::new();
655    let mut finally_expr = None;
656    context.with_interpreter(|interp| {
657        for arg in args {
658            match arg.name.as_deref() {
659                Some("finally") => {
660                    finally_expr = arg.value.clone();
661                }
662                Some("expr") => {} // handled above
663                Some(class) => {
664                    if let Some(ref val_expr) = arg.value {
665                        let handler = interp.eval_in(val_expr, env)?;
666                        handlers.push((class.to_string(), handler));
667                    }
668                }
669                None => {} // the expression itself
670            }
671        }
672        Ok::<(), RError>(())
673    })?;
674
675    // For non-error classes (warning, message, etc.), install withCallingHandlers-style
676    // handlers that convert them to unwinding RError::Condition so tryCatch can catch them.
677    let non_error_classes: Vec<String> = handlers
678        .iter()
679        .filter(|(c, _)| c != "error")
680        .map(|(c, _)| c.clone())
681        .collect();
682
683    let unwind_handlers: Vec<ConditionHandler> = non_error_classes
684        .iter()
685        .map(|class| ConditionHandler {
686            class: class.clone(),
687            handler: RValue::Function(RFunction::Builtin {
688                name: "tryCatch_unwinder".to_string(),
689                implementation: BuiltinImplementation::Eager(|args, _named| {
690                    // Re-raise the condition to unwind past tryCatch
691                    let condition = args.first().cloned().unwrap_or(RValue::Null);
692                    let cond_classes = get_class(&condition);
693                    let kind = if cond_classes.iter().any(|c| c == "warning") {
694                        ConditionKind::Warning
695                    } else if cond_classes.iter().any(|c| c == "message") {
696                        ConditionKind::Message
697                    } else {
698                        ConditionKind::Error
699                    };
700                    Err(RError::Condition { condition, kind })
701                }),
702                min_args: 0,
703                max_args: None,
704                formals: &[],
705            }),
706            env: env.clone(),
707        })
708        .collect();
709
710    // Install non-error handlers if any, then evaluate
711    let result = context.with_interpreter(|interp| {
712        if !unwind_handlers.is_empty() {
713            interp.condition_handlers.borrow_mut().push(unwind_handlers);
714        }
715        let eval_result = match expr {
716            Some(e) => interp.eval_in(e, env).map_err(RError::from),
717            None => Ok(RValue::Null),
718        };
719        if !non_error_classes.is_empty() {
720            interp.condition_handlers.borrow_mut().pop();
721        }
722
723        match eval_result {
724            Ok(val) => Ok(val),
725            Err(RError::Condition { condition, kind }) => {
726                // Match against handler classes
727                let cond_classes = get_class(&condition);
728                for (handler_class, handler) in &handlers {
729                    if cond_classes.iter().any(|c| c == handler_class) {
730                        return interp
731                            .call_function(handler, std::slice::from_ref(&condition), &[], env)
732                            .map_err(RError::from);
733                    }
734                }
735                // No matching handler — re-raise
736                Err(RError::Condition { condition, kind })
737            }
738            Err(other) => {
739                // Non-condition errors: check for "error" handler
740                if let Some((_, handler)) = handlers.iter().find(|(c, _)| c == "error") {
741                    let err_msg = other.message();
742                    let condition =
743                        make_condition(&err_msg, &["simpleError", "error", "condition"]);
744                    // Reset recursion depth so the handler can execute even if
745                    // the error was caused by hitting the recursion limit.
746                    interp.eval_depth.set(0);
747                    interp
748                        .call_function(handler, &[condition], &[], env)
749                        .map_err(RError::from)
750                } else {
751                    Err(other)
752                }
753            }
754        }
755    });
756
757    // Run finally block if present
758    if let Some(ref fin) = finally_expr {
759        context.with_interpreter(|interp| interp.eval_in(fin, env).map_err(RError::from))?;
760    }
761
762    result
763}
764
765/// Evaluate an expression, catching errors and returning them as a string.
766///
767/// @param expr the expression to evaluate
768/// @return the result of expr on success, or the error message as a character string
769#[pre_eval_builtin(name = "try", min_args = 1)]
770fn pre_eval_try(
771    args: &[Arg],
772    env: &Environment,
773    context: &BuiltinContext,
774) -> Result<RValue, RError> {
775    let expr = args
776        .iter()
777        .find(|a| a.name.is_none())
778        .and_then(|a| a.value.as_ref());
779    context.with_interpreter(|interp| match expr {
780        Some(e) => match interp.eval_in(e, env).map_err(RError::from) {
781            Ok(val) => Ok(val),
782            Err(err) => {
783                interp.eval_depth.set(0); // Reset recursion depth for recovery
784                let msg = format!("{}", err);
785                interp.write_stderr(&format!("Error in try : {}\n", msg));
786                Ok(RValue::vec(Vector::Character(vec![Some(msg)].into())))
787            }
788        },
789        None => Ok(RValue::Null),
790    })
791}
792
793/// Evaluate an expression with calling handlers for conditions.
794///
795/// Unlike tryCatch, handlers run without unwinding the call stack, allowing
796/// the signaling code to resume execution after the handler returns.
797///
798/// @param expr the expression to evaluate
799/// @param ... named handlers: class = handler_function
800/// @return the result of expr
801#[pre_eval_builtin(name = "withCallingHandlers", min_args = 1)]
802fn pre_eval_with_calling_handlers(
803    args: &[Arg],
804    env: &Environment,
805    context: &BuiltinContext,
806) -> Result<RValue, RError> {
807    use crate::interpreter::ConditionHandler;
808
809    let expr = args
810        .iter()
811        .find(|a| a.name.is_none())
812        .or_else(|| args.iter().find(|a| a.name.as_deref() == Some("expr")))
813        .and_then(|a| a.value.as_ref());
814
815    // Collect named handlers (class = handler_function)
816    let mut handler_set: Vec<ConditionHandler> = Vec::new();
817    context.with_interpreter(|interp| {
818        for arg in args {
819            match arg.name.as_deref() {
820                Some("expr") => {} // handled above
821                Some(class) => {
822                    if let Some(ref val_expr) = arg.value {
823                        let handler = interp.eval_in(val_expr, env).map_err(RError::from)?;
824                        handler_set.push(ConditionHandler {
825                            class: class.to_string(),
826                            handler,
827                            env: env.clone(),
828                        });
829                    }
830                }
831                None => {} // the expression itself
832            }
833        }
834        Ok::<(), RError>(())
835    })?;
836
837    // Push handler set onto the stack, evaluate, then pop
838    context.with_interpreter(|interp| {
839        interp.condition_handlers.borrow_mut().push(handler_set);
840        let result = match expr {
841            Some(e) => interp.eval_in(e, env).map_err(RError::from),
842            None => Ok(RValue::Null),
843        };
844        interp.condition_handlers.borrow_mut().pop();
845        result
846    })
847}
848
849/// Evaluate an expression, suppressing all warning conditions.
850///
851/// @param expr the expression to evaluate
852/// @return the result of expr with warnings silenced
853#[pre_eval_builtin(name = "suppressWarnings", min_args = 1)]
854fn pre_eval_suppress_warnings(
855    args: &[Arg],
856    env: &Environment,
857    context: &BuiltinContext,
858) -> Result<RValue, RError> {
859    use crate::interpreter::ConditionHandler;
860
861    let expr = args
862        .first()
863        .and_then(|a| a.value.as_ref())
864        .ok_or_else(|| RError::new(RErrorKind::Argument, "argument is missing".to_string()))?;
865
866    // Create a handler that muffles warnings by signaling muffleWarning
867    let muffle_handler = RValue::Function(RFunction::Builtin {
868        name: "suppressWarnings_handler".to_string(),
869        implementation: BuiltinImplementation::Eager(|_args, _named| {
870            Err(RError::other("muffleWarning".to_string()))
871        }),
872        min_args: 0,
873        max_args: None,
874        formals: &[],
875    });
876
877    let handler_set = vec![ConditionHandler {
878        class: "warning".to_string(),
879        handler: muffle_handler,
880        env: env.clone(),
881    }];
882
883    context.with_interpreter(|interp| {
884        interp.condition_handlers.borrow_mut().push(handler_set);
885        let result = interp.eval_in(expr, env).map_err(RError::from);
886        interp.condition_handlers.borrow_mut().pop();
887        result
888    })
889}
890
891/// Evaluate an expression, suppressing all message conditions.
892///
893/// @param expr the expression to evaluate
894/// @return the result of expr with messages silenced
895#[pre_eval_builtin(name = "suppressMessages", min_args = 1)]
896fn pre_eval_suppress_messages(
897    args: &[Arg],
898    env: &Environment,
899    context: &BuiltinContext,
900) -> Result<RValue, RError> {
901    use crate::interpreter::ConditionHandler;
902
903    let expr = args
904        .first()
905        .and_then(|a| a.value.as_ref())
906        .ok_or_else(|| RError::new(RErrorKind::Argument, "argument is missing".to_string()))?;
907
908    let muffle_handler = RValue::Function(RFunction::Builtin {
909        name: "suppressMessages_handler".to_string(),
910        implementation: BuiltinImplementation::Eager(|_args, _named| {
911            Err(RError::other("muffleMessage".to_string()))
912        }),
913        min_args: 0,
914        max_args: None,
915        formals: &[],
916    });
917
918    let handler_set = vec![ConditionHandler {
919        class: "message".to_string(),
920        handler: muffle_handler,
921        env: env.clone(),
922    }];
923
924    context.with_interpreter(|interp| {
925        interp.condition_handlers.borrow_mut().push(handler_set);
926        let result = interp.eval_in(expr, env).map_err(RError::from);
927        interp.condition_handlers.borrow_mut().pop();
928        result
929    })
930}
931
932/// Register an expression to be evaluated when the current function exits.
933///
934/// @param expr expression to evaluate on exit (or NULL to clear)
935/// @param add if TRUE, append to existing on.exit expressions; if FALSE, replace them
936/// @param after if TRUE (default), append after existing; if FALSE, prepend before existing
937/// @return NULL, invisibly
938#[pre_eval_builtin(name = "on.exit")]
939fn pre_eval_on_exit(
940    args: &[Arg],
941    env: &Environment,
942    context: &BuiltinContext,
943) -> Result<RValue, RError> {
944    let expr = args.first().and_then(|a| a.value.as_ref()).cloned();
945
946    // Evaluate add= and after= arguments
947    let (add, after) = context.with_interpreter(|interp| -> Result<(bool, bool), RError> {
948        let mut add = false;
949        let mut after = true;
950
951        for arg in args.iter().skip(1) {
952            match arg.name.as_deref() {
953                Some("add") => {
954                    if let Some(ref val_expr) = arg.value {
955                        let val = interp.eval_in(val_expr, env)?;
956                        add = val
957                            .as_vector()
958                            .and_then(|v| v.as_logical_scalar())
959                            .unwrap_or(false);
960                    }
961                }
962                Some("after") => {
963                    if let Some(ref val_expr) = arg.value {
964                        let val = interp.eval_in(val_expr, env)?;
965                        after = val
966                            .as_vector()
967                            .and_then(|v| v.as_logical_scalar())
968                            .unwrap_or(true);
969                    }
970                }
971                _ => {}
972            }
973        }
974
975        // Check positional args if named args were not found
976        let has_named_add = args
977            .iter()
978            .skip(1)
979            .any(|a| a.name.as_deref() == Some("add"));
980        let has_named_after = args
981            .iter()
982            .skip(1)
983            .any(|a| a.name.as_deref() == Some("after"));
984
985        if !has_named_add {
986            if let Some(arg) = args.get(1) {
987                if arg.name.is_none() {
988                    if let Some(ref val_expr) = arg.value {
989                        let val = interp.eval_in(val_expr, env)?;
990                        add = val
991                            .as_vector()
992                            .and_then(|v| v.as_logical_scalar())
993                            .unwrap_or(false);
994                    }
995                }
996            }
997        }
998
999        if !has_named_after {
1000            if let Some(arg) = args.get(2) {
1001                if arg.name.is_none() {
1002                    if let Some(ref val_expr) = arg.value {
1003                        let val = interp.eval_in(val_expr, env)?;
1004                        after = val
1005                            .as_vector()
1006                            .and_then(|v| v.as_logical_scalar())
1007                            .unwrap_or(true);
1008                    }
1009                }
1010            }
1011        }
1012
1013        Ok((add, after))
1014    })?;
1015
1016    match expr {
1017        Some(e) => env.push_on_exit(e, add, after),
1018        None => {
1019            // on.exit() with no args clears on.exit handlers
1020            env.take_on_exit();
1021        }
1022    }
1023
1024    Ok(RValue::Null)
1025}
1026
1027/// Test whether a formal argument was supplied in the current function call.
1028///
1029/// @param x unquoted name of a formal argument
1030/// @return TRUE if the argument was not supplied, FALSE otherwise
1031#[pre_eval_builtin(name = "missing", min_args = 1)]
1032fn pre_eval_missing(
1033    args: &[Arg],
1034    _env: &Environment,
1035    context: &BuiltinContext,
1036) -> Result<RValue, RError> {
1037    let expr = args
1038        .first()
1039        .and_then(|a| a.value.as_ref())
1040        .ok_or_else(|| RError::other("'missing(x)' did not find an argument".to_string()))?;
1041
1042    let is_missing = context.with_interpreter(|interp| {
1043        let frame = interp
1044            .current_call_frame()
1045            .ok_or_else(|| RError::other("'missing(x)' did not find an argument".to_string()))?;
1046
1047        match expr {
1048            Expr::Symbol(name) => {
1049                if !frame.formal_args.contains(name) {
1050                    return Err(RError::other(format!(
1051                        "'missing({})' did not find an argument",
1052                        name
1053                    )));
1054                }
1055                Ok(!frame.supplied_args.contains(name))
1056            }
1057            Expr::Dots => {
1058                if !frame.formal_args.contains("...") {
1059                    return Err(RError::other("'missing(...)' did not find an argument"));
1060                }
1061                let dots_len = match frame.env.get("...") {
1062                    Some(RValue::List(list)) => list.values.len(),
1063                    _ => 0,
1064                };
1065                Ok(dots_len == 0)
1066            }
1067            Expr::DotDot(n) => {
1068                if !frame.formal_args.contains("...") {
1069                    return Err(RError::other("'missing(...)' did not find an argument"));
1070                }
1071                let dots_len = match frame.env.get("...") {
1072                    Some(RValue::List(list)) => list.values.len(),
1073                    _ => 0,
1074                };
1075                Ok(dots_len < usize::try_from(*n).unwrap_or(0))
1076            }
1077            _ => Err(RError::other("invalid use of 'missing'".to_string())),
1078        }
1079    })?;
1080
1081    Ok(RValue::vec(Vector::Logical(vec![Some(is_missing)].into())))
1082}
1083
1084/// Construct a pairlist of unevaluated arguments.
1085///
1086/// Unlike list(), alist() does not evaluate its arguments. Missing
1087/// arguments (e.g. `alist(x = )`) produce empty symbol names, matching
1088/// R's convention for function formals.
1089///
1090/// @param ... unevaluated arguments
1091/// @return a list of language objects
1092#[pre_eval_builtin(name = "alist")]
1093fn pre_eval_alist(
1094    args: &[Arg],
1095    _env: &Environment,
1096    _context: &BuiltinContext,
1097) -> Result<RValue, RError> {
1098    let entries: Vec<(Option<String>, RValue)> = args
1099        .iter()
1100        .map(|arg| {
1101            let name = arg.name.clone();
1102            let value = match &arg.value {
1103                Some(expr) => RValue::Language(Language::new(expr.clone())),
1104                None => RValue::Language(Language::new(Expr::Symbol(String::new()))),
1105            };
1106            (name, value)
1107        })
1108        .collect();
1109    Ok(RValue::List(RList::new(entries)))
1110}
1111
1112/// Return an unevaluated expression (language object).
1113///
1114/// @param expr any R expression (not evaluated)
1115/// @return the expression as a language object
1116#[pre_eval_builtin(name = "quote", min_args = 1)]
1117fn pre_eval_quote(
1118    args: &[Arg],
1119    _env: &Environment,
1120    _context: &BuiltinContext,
1121) -> Result<RValue, RError> {
1122    match args.first().and_then(|a| a.value.as_ref()) {
1123        Some(expr) => Ok(RValue::Language(Language::new(expr.clone()))),
1124        None => Ok(RValue::Null),
1125    }
1126}
1127
1128/// Return an unevaluated expression with variables substituted from the environment.
1129///
1130/// @param expr any R expression (not evaluated)
1131/// @return the expression with symbols replaced by their values from the calling environment
1132#[pre_eval_builtin(name = "substitute", min_args = 1)]
1133fn pre_eval_substitute(
1134    args: &[Arg],
1135    env: &Environment,
1136    _context: &BuiltinContext,
1137) -> Result<RValue, RError> {
1138    let expr = match args.first().and_then(|a| a.value.as_ref()) {
1139        Some(e) => e.clone(),
1140        None => return Ok(RValue::Null),
1141    };
1142    // Walk the AST and replace symbols with their values from the environment
1143    let substituted = substitute_expr(&expr, env);
1144    Ok(RValue::Language(Language::new(substituted)))
1145}
1146
1147/// Walk an AST, replacing symbols with their values from the environment.
1148///
1149/// For function parameters (which have promise expressions stored by the call
1150/// mechanism), substitute with the original unevaluated source expression.
1151/// For other bindings, if bound to an RValue::Language, splice in the inner
1152/// Expr. If bound to a literal value, convert to the appropriate Expr literal.
1153fn substitute_expr(expr: &Expr, env: &Environment) -> Expr {
1154    match expr {
1155        Expr::Symbol(name) => {
1156            // Check if the binding is a promise — if so, use the promise's
1157            // unevaluated expression. This is what makes
1158            // `f <- function(x) substitute(x); f(a+b)` return `a + b`.
1159            if let Some(val) = env.get(name) {
1160                if let RValue::Promise(p) = &val {
1161                    return p.borrow().expr.clone();
1162                }
1163                // Also check legacy promise_exprs for backward compat
1164                if let Some(promise_expr) = env.get_promise_expr(name) {
1165                    return promise_expr;
1166                }
1167                rvalue_to_expr(&val)
1168            } else {
1169                expr.clone()
1170            }
1171        }
1172        Expr::Call { func, args, .. } => Expr::Call {
1173            func: Box::new(substitute_expr(func, env)),
1174            span: None,
1175            args: args
1176                .iter()
1177                .map(|a| Arg {
1178                    name: a.name.clone(),
1179                    value: a.value.as_ref().map(|v| substitute_expr(v, env)),
1180                })
1181                .collect(),
1182        },
1183        Expr::BinaryOp { op, lhs, rhs } => Expr::BinaryOp {
1184            op: op.clone(),
1185            lhs: Box::new(substitute_expr(lhs, env)),
1186            rhs: Box::new(substitute_expr(rhs, env)),
1187        },
1188        Expr::UnaryOp { op, operand } => Expr::UnaryOp {
1189            op: *op,
1190            operand: Box::new(substitute_expr(operand, env)),
1191        },
1192        Expr::If {
1193            condition,
1194            then_body,
1195            else_body,
1196        } => Expr::If {
1197            condition: Box::new(substitute_expr(condition, env)),
1198            then_body: Box::new(substitute_expr(then_body, env)),
1199            else_body: else_body
1200                .as_ref()
1201                .map(|e| Box::new(substitute_expr(e, env))),
1202        },
1203        Expr::Block(exprs) => Expr::Block(exprs.iter().map(|e| substitute_expr(e, env)).collect()),
1204        // For other AST nodes, return as-is (can expand later)
1205        _ => expr.clone(),
1206    }
1207}
1208
1209/// Evaluate a quoted expression in a specified environment.
1210///
1211/// Equivalent to eval(quote(expr), envir). The expression is not evaluated
1212/// in the calling environment before being passed to eval.
1213///
1214/// @param expr expression to evaluate (quoted, not evaluated first)
1215/// @param envir environment in which to evaluate (default: calling environment)
1216/// @return the result of evaluating expr in envir
1217#[pre_eval_builtin(name = "evalq", min_args = 1)]
1218fn pre_eval_evalq(
1219    args: &[Arg],
1220    env: &Environment,
1221    context: &BuiltinContext,
1222) -> Result<RValue, RError> {
1223    // evalq(expr, envir) is equivalent to eval(quote(expr), envir)
1224    // First arg is the expression to quote-then-eval
1225    let expr = match args.first().and_then(|a| a.value.as_ref()) {
1226        Some(e) => e,
1227        None => return Ok(RValue::Null),
1228    };
1229
1230    // Determine evaluation environment from second arg or named envir=
1231    let eval_env = context.with_interpreter(|interp| -> Result<Option<Environment>, RError> {
1232        // Check named envir= first
1233        for arg in args.iter().skip(1) {
1234            if arg.name.as_deref() == Some("envir") {
1235                if let Some(ref val_expr) = arg.value {
1236                    let val = interp.eval_in(val_expr, env)?;
1237                    if let RValue::Environment(e) = val {
1238                        return Ok(Some(e));
1239                    }
1240                }
1241            }
1242        }
1243        // Check second positional arg
1244        if let Some(arg) = args.get(1) {
1245            if arg.name.is_none() {
1246                if let Some(ref val_expr) = arg.value {
1247                    let val = interp.eval_in(val_expr, env)?;
1248                    if let RValue::Environment(e) = val {
1249                        return Ok(Some(e));
1250                    }
1251                }
1252            }
1253        }
1254        Ok(None)
1255    })?;
1256
1257    let target_env = eval_env.unwrap_or_else(|| env.clone());
1258    context
1259        .with_interpreter(|interp| interp.eval_in(expr, &target_env))
1260        .map_err(RError::from)
1261}
1262
1263/// Partial substitution: quote an expression, evaluating only .() splices.
1264///
1265/// @param expr expression to quote (not evaluated, except for .() sub-expressions)
1266/// @return a language object with .() splices replaced by their evaluated values
1267#[pre_eval_builtin(name = "bquote", min_args = 1)]
1268fn pre_eval_bquote(
1269    args: &[Arg],
1270    env: &Environment,
1271    context: &BuiltinContext,
1272) -> Result<RValue, RError> {
1273    // bquote(expr) is like quote() but evaluates anything wrapped in .()
1274    let expr = match args.first().and_then(|a| a.value.as_ref()) {
1275        Some(e) => e.clone(),
1276        None => return Ok(RValue::Null),
1277    };
1278    let interp = context.interpreter();
1279    let result = bquote_expr(&expr, env, interp)?;
1280    Ok(RValue::Language(Language::new(result)))
1281}
1282
1283/// Walk an AST for bquote: evaluate .() splice expressions, leave everything else quoted.
1284fn bquote_expr(
1285    expr: &Expr,
1286    env: &Environment,
1287    interp: &crate::interpreter::Interpreter,
1288) -> Result<Expr, RError> {
1289    match expr {
1290        // Check for .(expr) — a call to `.` with one argument
1291        Expr::Call { func, args, .. } => {
1292            if let Expr::Symbol(name) = func.as_ref() {
1293                if name == "." && args.len() == 1 {
1294                    // Evaluate the inner expression
1295                    if let Some(ref inner) = args[0].value {
1296                        let val = interp.eval_in(inner, env).map_err(RError::from)?;
1297                        return Ok(rvalue_to_expr(&val));
1298                    }
1299                }
1300            }
1301            // Not a .() call — recurse into func and args
1302            let new_func = Box::new(bquote_expr(func, env, interp)?);
1303            let new_args: Result<Vec<Arg>, RError> = args
1304                .iter()
1305                .map(|a| {
1306                    Ok(Arg {
1307                        name: a.name.clone(),
1308                        value: match &a.value {
1309                            Some(v) => Some(bquote_expr(v, env, interp)?),
1310                            None => None,
1311                        },
1312                    })
1313                })
1314                .collect();
1315            Ok(Expr::Call {
1316                func: new_func,
1317                args: new_args?,
1318                span: None,
1319            })
1320        }
1321        Expr::BinaryOp { op, lhs, rhs } => Ok(Expr::BinaryOp {
1322            op: op.clone(),
1323            lhs: Box::new(bquote_expr(lhs, env, interp)?),
1324            rhs: Box::new(bquote_expr(rhs, env, interp)?),
1325        }),
1326        Expr::UnaryOp { op, operand } => Ok(Expr::UnaryOp {
1327            op: *op,
1328            operand: Box::new(bquote_expr(operand, env, interp)?),
1329        }),
1330        Expr::If {
1331            condition,
1332            then_body,
1333            else_body,
1334        } => Ok(Expr::If {
1335            condition: Box::new(bquote_expr(condition, env, interp)?),
1336            then_body: Box::new(bquote_expr(then_body, env, interp)?),
1337            else_body: match else_body {
1338                Some(e) => Some(Box::new(bquote_expr(e, env, interp)?)),
1339                None => None,
1340            },
1341        }),
1342        Expr::Block(exprs) => {
1343            let new_exprs: Result<Vec<Expr>, RError> =
1344                exprs.iter().map(|e| bquote_expr(e, env, interp)).collect();
1345            Ok(Expr::Block(new_exprs?))
1346        }
1347        // Everything else stays as-is
1348        _ => Ok(expr.clone()),
1349    }
1350}
1351
1352/// Evaluate an expression and return the result with a visibility flag.
1353///
1354/// @param expr the expression to evaluate
1355/// @return a list with components "value" (the result) and "visible" (logical)
1356#[pre_eval_builtin(name = "withVisible", min_args = 1)]
1357fn pre_eval_with_visible(
1358    args: &[Arg],
1359    env: &Environment,
1360    context: &BuiltinContext,
1361) -> Result<RValue, RError> {
1362    let expr = args
1363        .first()
1364        .and_then(|a| a.value.as_ref())
1365        .ok_or_else(|| RError::new(RErrorKind::Argument, "argument 'x' is missing".to_string()))?;
1366
1367    let value = context.with_interpreter(|interp| interp.eval_in(expr, env))?;
1368
1369    // We don't track visibility yet, so always TRUE
1370    Ok(RValue::List(RList::new(vec![
1371        (Some("value".to_string()), value),
1372        (
1373            Some("visible".to_string()),
1374            RValue::vec(Vector::Logical(vec![Some(true)].into())),
1375        ),
1376    ])))
1377}
1378
1379/// `expression(...)` — construct an expression object from unevaluated arguments.
1380/// Returns a list of Language objects, each wrapping the unevaluated expression.
1381#[pre_eval_builtin(name = "expression")]
1382fn pre_eval_expression(
1383    args: &[Arg],
1384    _env: &Environment,
1385    _context: &BuiltinContext,
1386) -> Result<RValue, RError> {
1387    let entries: Vec<(Option<String>, RValue)> = args
1388        .iter()
1389        .filter_map(|a| {
1390            a.value
1391                .as_ref()
1392                .map(|expr| (None, RValue::Language(Language::new(expr.clone()))))
1393        })
1394        .collect();
1395    let mut list = RList::new(entries);
1396    list.set_attr(
1397        "class".to_string(),
1398        RValue::vec(Vector::Character(
1399            vec![Some("expression".to_string())].into(),
1400        )),
1401    );
1402    Ok(RValue::List(list))
1403}
1404
1405/// Measure the wall-clock time to evaluate an expression.
1406///
1407/// Returns a `proc_time` object (named numeric vector with class `"proc_time"`).
1408/// User and system CPU times are reported as 0 since we only measure wall-clock time.
1409///
1410/// @param expr the expression to time
1411/// @return proc_time vector c(user.self=0, sys.self=0, elapsed=<wall time>)
1412#[pre_eval_builtin(name = "system.time", min_args = 1)]
1413fn pre_eval_system_time(
1414    args: &[Arg],
1415    env: &Environment,
1416    context: &BuiltinContext,
1417) -> Result<RValue, RError> {
1418    let expr = args
1419        .first()
1420        .and_then(|a| a.value.as_ref())
1421        .ok_or_else(|| RError::new(RErrorKind::Argument, "argument is missing".to_string()))?;
1422    let start = std::time::Instant::now();
1423    // Evaluate the expression; we discard the result and only keep timing
1424    context.with_interpreter(|interp| interp.eval_in(expr, env))?;
1425    let elapsed = start.elapsed().as_secs_f64();
1426    Ok(super::system::make_proc_time(0.0, 0.0, elapsed))
1427}
1428
1429/// Evaluate an expression in a temporary local environment.
1430///
1431/// Creates a new child of `envir` (default: the calling environment) and evaluates
1432/// `expr` in it. The local environment is discarded after evaluation, so any
1433/// bindings created inside are not visible to the caller.
1434///
1435/// @param expr expression to evaluate (not evaluated before dispatch)
1436/// @param envir parent environment for the local scope (default: calling environment)
1437/// @return the result of evaluating expr
1438#[pre_eval_builtin(name = "local", min_args = 1)]
1439fn pre_eval_local(
1440    args: &[Arg],
1441    env: &Environment,
1442    context: &BuiltinContext,
1443) -> Result<RValue, RError> {
1444    let expr = match args.first().and_then(|a| a.value.as_ref()) {
1445        Some(e) => e,
1446        None => return Ok(RValue::Null),
1447    };
1448
1449    // Determine parent environment from second positional or named envir= arg
1450    let parent_env = context.with_interpreter(|interp| -> Result<Option<Environment>, RError> {
1451        for arg in args.iter().skip(1) {
1452            if arg.name.as_deref() == Some("envir") {
1453                if let Some(ref val_expr) = arg.value {
1454                    let val = interp.eval_in(val_expr, env)?;
1455                    if let RValue::Environment(e) = val {
1456                        return Ok(Some(e));
1457                    }
1458                }
1459            }
1460        }
1461        if let Some(arg) = args.get(1) {
1462            if arg.name.is_none() {
1463                if let Some(ref val_expr) = arg.value {
1464                    let val = interp.eval_in(val_expr, env)?;
1465                    if let RValue::Environment(e) = val {
1466                        return Ok(Some(e));
1467                    }
1468                }
1469            }
1470        }
1471        Ok(None)
1472    })?;
1473
1474    let parent = parent_env.unwrap_or_else(|| env.clone());
1475    let local_env = Environment::new_child(&parent);
1476    context
1477        .with_interpreter(|interp| interp.eval_in(expr, &local_env))
1478        .map_err(RError::from)
1479}
1480
1481/// Remove objects from an environment.
1482///
1483/// Supports non-standard evaluation: bare symbol names are interpreted as the
1484/// names of objects to remove (e.g., `rm(x)` removes the variable `x`).
1485/// Also accepts character strings for compatibility: `rm("x")`.
1486///
1487/// @param ... names of objects to remove (bare symbols or character strings)
1488/// @param list character vector of names to remove
1489/// @param envir environment from which to remove (default: calling environment)
1490/// @return NULL (invisibly)
1491#[pre_eval_builtin(name = "rm", names = ["remove"])]
1492fn pre_eval_rm(
1493    args: &[Arg],
1494    env: &Environment,
1495    context: &BuiltinContext,
1496) -> Result<RValue, RError> {
1497    let mut names_to_remove: Vec<String> = Vec::new();
1498    let mut target_env: Option<Environment> = None;
1499
1500    for arg in args {
1501        match (&arg.name, &arg.value) {
1502            // Named arg: envir = <expr>
1503            (Some(name), Some(expr)) if name == "envir" => {
1504                let val = context.with_interpreter(|interp| interp.eval_in(expr, env))?;
1505                match val {
1506                    RValue::Environment(e) => target_env = Some(e),
1507                    _ => {
1508                        return Err(RError::new(
1509                            RErrorKind::Argument,
1510                            "invalid 'envir' argument".to_string(),
1511                        ))
1512                    }
1513                }
1514            }
1515            // Named arg: list = <expr>
1516            (Some(name), Some(expr)) if name == "list" => {
1517                let val = context.with_interpreter(|interp| interp.eval_in(expr, env))?;
1518                if let Some(Vector::Character(c)) = val.as_vector() {
1519                    for s in c.iter().flatten() {
1520                        names_to_remove.push(s.clone());
1521                    }
1522                }
1523            }
1524            // Positional arg: bare symbol → use symbol name
1525            (None, Some(Expr::Symbol(name))) => {
1526                names_to_remove.push(name.clone());
1527            }
1528            // Positional arg: string literal → use string value
1529            (None, Some(Expr::String(s))) => {
1530                names_to_remove.push(s.clone());
1531            }
1532            // Positional arg: other expression → evaluate and use as character
1533            (None, Some(expr)) => {
1534                let val = context.with_interpreter(|interp| interp.eval_in(expr, env))?;
1535                match val.as_vector() {
1536                    Some(Vector::Character(c)) => {
1537                        for s in c.iter().flatten() {
1538                            names_to_remove.push(s.clone());
1539                        }
1540                    }
1541                    _ => {
1542                        return Err(RError::new(
1543                            RErrorKind::Argument,
1544                            format!(
1545                                "rm() expects names of objects to remove, got {}",
1546                                val.type_name()
1547                            ),
1548                        ));
1549                    }
1550                }
1551            }
1552            _ => {}
1553        }
1554    }
1555
1556    let target = target_env.unwrap_or_else(|| env.clone());
1557    for name in &names_to_remove {
1558        target.remove(name);
1559    }
1560
1561    Ok(RValue::Null)
1562}
1563
1564/// Convert an RValue back to an AST expression (for substitute).
1565fn rvalue_to_expr(val: &RValue) -> Expr {
1566    match val {
1567        RValue::Language(expr) => *expr.inner.clone(),
1568        RValue::Null => Expr::Null,
1569        RValue::Vector(rv) => match &rv.inner {
1570            Vector::Double(d) if d.len() == 1 => match d.get_opt(0) {
1571                Some(v) => Expr::Double(v),
1572                None => Expr::Na(crate::parser::ast::NaType::Real),
1573            },
1574            Vector::Integer(i) if i.len() == 1 => match i.get_opt(0) {
1575                Some(v) => Expr::Integer(v),
1576                None => Expr::Na(crate::parser::ast::NaType::Integer),
1577            },
1578            Vector::Logical(l) if l.len() == 1 => match l[0] {
1579                Some(v) => Expr::Bool(v),
1580                None => Expr::Na(crate::parser::ast::NaType::Logical),
1581            },
1582            Vector::Character(c) if c.len() == 1 => match &c[0] {
1583                Some(v) => Expr::String(v.clone()),
1584                None => Expr::Na(crate::parser::ast::NaType::Character),
1585            },
1586            _ => Expr::Symbol(format!("{}", val)),
1587        },
1588        _ => Expr::Symbol(format!("{}", val)),
1589    }
1590}
1591
1592// region: library / require (NSE for package names)
1593
1594/// Extract a package name from a pre-eval argument list.
1595/// Accepts both `library("pkg")` (string) and `library(pkg)` (bare symbol).
1596fn extract_package_name_nse(
1597    args: &[Arg],
1598    env: &Environment,
1599    context: &BuiltinContext,
1600) -> Result<String, RError> {
1601    let first_arg = args.first().and_then(|a| a.value.as_ref()).ok_or_else(|| {
1602        RError::new(
1603            RErrorKind::Argument,
1604            "library/require requires a package name".to_string(),
1605        )
1606    })?;
1607
1608    // When character.only=TRUE, evaluate the first arg as a variable.
1609    // Evaluate the argument (not just check for literal TRUE) so that
1610    // `co <- TRUE; library(pkg, character.only=co)` works.
1611    let character_only = args
1612        .iter()
1613        .find(|a| a.name.as_deref() == Some("character.only"))
1614        .and_then(|a| a.value.as_ref())
1615        .map(|expr| match expr {
1616            Expr::Bool(b) => *b,
1617            other => context
1618                .with_interpreter(|interp| interp.eval_in(other, env))
1619                .ok()
1620                .and_then(|v| v.as_vector().and_then(|v| v.as_logical_scalar()))
1621                .unwrap_or(false),
1622        })
1623        .unwrap_or(false);
1624
1625    if character_only {
1626        let val = context
1627            .with_interpreter(|interp| interp.eval_in(first_arg, env).map_err(RError::from))?;
1628        return val
1629            .as_vector()
1630            .and_then(|v| v.as_character_scalar())
1631            .ok_or_else(|| {
1632                RError::new(
1633                    RErrorKind::Argument,
1634                    "invalid package name argument".to_string(),
1635                )
1636            });
1637    }
1638
1639    match first_arg {
1640        // Bare symbol: library(dplyr) → package name is "dplyr"
1641        Expr::Symbol(name) => Ok(name.clone()),
1642        // String literal: library("dplyr")
1643        Expr::String(s) => Ok(s.clone()),
1644        // Anything else: evaluate it and hope for a character scalar
1645        other => {
1646            let val = context
1647                .with_interpreter(|interp| interp.eval_in(other, env).map_err(RError::from))?;
1648            val.as_vector()
1649                .and_then(|v| v.as_character_scalar())
1650                .ok_or_else(|| {
1651                    RError::new(
1652                        RErrorKind::Argument,
1653                        "invalid package name argument".to_string(),
1654                    )
1655                })
1656        }
1657    }
1658}
1659
1660/// Load and attach a package by name.
1661///
1662/// Accepts both quoted and unquoted package names:
1663///   library("dplyr")   # quoted string
1664///   library(dplyr)     # bare symbol (NSE)
1665///
1666/// @param package name of the package to load
1667/// @return character string with the package name (invisibly)
1668#[pre_eval_builtin(name = "library", min_args = 1)]
1669fn pre_eval_library(
1670    args: &[Arg],
1671    env: &Environment,
1672    context: &BuiltinContext,
1673) -> Result<RValue, RError> {
1674    let pkg = extract_package_name_nse(args, env, context)?;
1675
1676    context.with_interpreter(|interp| {
1677        interp.load_namespace(&pkg)?;
1678        interp.attach_package(&pkg)?;
1679        Ok(RValue::vec(Vector::Character(vec![Some(pkg)].into())))
1680    })
1681}
1682
1683/// Load a package if available, returning TRUE/FALSE.
1684///
1685/// Like library(), but returns FALSE instead of erroring if the
1686/// package is not found. Accepts bare symbols: require(dplyr)
1687///
1688/// @param package name of the package to load
1689/// @param quietly logical: suppress messages?
1690/// @return logical: TRUE if loaded, FALSE otherwise
1691#[pre_eval_builtin(name = "require", min_args = 1)]
1692fn pre_eval_require(
1693    args: &[Arg],
1694    env: &Environment,
1695    context: &BuiltinContext,
1696) -> Result<RValue, RError> {
1697    let pkg = extract_package_name_nse(args, env, context)?;
1698
1699    // Check for quietly parameter
1700    let quietly = args.iter().any(|a| {
1701        a.name.as_deref() == Some("quietly")
1702            && a.value
1703                .as_ref()
1704                .is_some_and(|e| matches!(e, Expr::Bool(true)))
1705    });
1706
1707    let result = context.with_interpreter(|interp| match interp.load_namespace(&pkg) {
1708        Ok(_) => {
1709            interp.attach_package(&pkg)?;
1710            Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
1711        }
1712        Err(_) => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
1713    });
1714
1715    if let Ok(RValue::Vector(rv)) = &result {
1716        if rv.as_logical_scalar() == Some(false) && !quietly {
1717            context.write_err(&format!(
1718                "Warning message:\nthere is no package called '{}'\n",
1719                pkg
1720            ));
1721        }
1722    }
1723    result
1724}
1725
1726// endregion
1727
1728// region: switch (lazy branch evaluation)
1729
1730/// switch(EXPR, ...) — only evaluate the matching branch.
1731///
1732/// Critical for rlang's ns_env() which has `switch(typeof(x), builtin=, special=ns_env("base"), ...)`
1733/// — the `ns_env("base")` must NOT be evaluated unless typeof(x) is "builtin" or "special".
1734///
1735/// @param EXPR expression to evaluate (determines which branch)
1736/// @param ... named branches (name=value) and optional default
1737/// @return value of the matching branch
1738/// @namespace base
1739#[pre_eval_builtin(name = "switch")]
1740fn pre_eval_switch(
1741    args: &[Arg],
1742    env: &Environment,
1743    context: &BuiltinContext,
1744) -> Result<RValue, RError> {
1745    if args.is_empty() {
1746        return Err(RError::new(
1747            RErrorKind::Argument,
1748            "'EXPR' is missing".to_string(),
1749        ));
1750    }
1751
1752    // Evaluate EXPR (first argument)
1753    let expr_val = context.with_interpreter(|interp| {
1754        args[0]
1755            .value
1756            .as_ref()
1757            .map(|e| interp.eval_in(e, env))
1758            .transpose()
1759            .map_err(RError::from)
1760    })?;
1761    let expr_val = expr_val.unwrap_or(RValue::Null);
1762
1763    let is_character =
1764        matches!(&expr_val, RValue::Vector(rv) if matches!(rv.inner, Vector::Character(_)));
1765
1766    if is_character {
1767        let s = expr_val
1768            .as_vector()
1769            .and_then(|v| v.as_character_scalar())
1770            .unwrap_or_default();
1771
1772        // Find the matching named arg. Fall-through: if matched name has no value,
1773        // continue to next named arg that has a value.
1774        let named_args: Vec<_> = args[1..].iter().filter(|a| a.name.is_some()).collect();
1775        let default_arg = args[1..].iter().find(|a| a.name.is_none());
1776
1777        let mut found = false;
1778        for arg in &named_args {
1779            let name = arg.name.as_deref().unwrap_or("");
1780            if name == s {
1781                found = true;
1782                // If value is Some, evaluate and return it
1783                if let Some(ref expr) = arg.value {
1784                    return context.with_interpreter(|interp| {
1785                        interp.eval_in(expr, env).map_err(RError::from)
1786                    });
1787                }
1788                // Fall-through: no value → continue to next
1789            } else if found {
1790                if let Some(ref expr) = arg.value {
1791                    return context.with_interpreter(|interp| {
1792                        interp.eval_in(expr, env).map_err(RError::from)
1793                    });
1794                }
1795            }
1796        }
1797
1798        // No match — try default (unnamed arg after EXPR)
1799        if let Some(arg) = default_arg {
1800            if let Some(ref expr) = arg.value {
1801                return context
1802                    .with_interpreter(|interp| interp.eval_in(expr, env).map_err(RError::from));
1803            }
1804        }
1805
1806        Ok(RValue::Null)
1807    } else {
1808        // Integer indexing
1809        let idx = expr_val.as_vector().and_then(|v| v.as_integer_scalar());
1810        match idx {
1811            Some(i) if i >= 1 => {
1812                let remaining: Vec<_> = args[1..].iter().collect();
1813                if let Some(arg) = remaining.get(usize::try_from(i - 1)?) {
1814                    if let Some(ref expr) = arg.value {
1815                        return context.with_interpreter(|interp| {
1816                            interp.eval_in(expr, env).map_err(RError::from)
1817                        });
1818                    }
1819                }
1820                Ok(RValue::Null)
1821            }
1822            _ => Ok(RValue::Null),
1823        }
1824    }
1825}
1826
1827// endregion
1828
1829// region: delayedAssign
1830
1831/// `delayedAssign(x, value, eval.env, assign.env)` — create a promise binding.
1832///
1833/// The variable `x` is bound as a promise in `assign.env`. When first accessed,
1834/// `value` is evaluated in `eval.env` and the result cached.
1835///
1836/// This is the core mechanism behind rlang's `on_load()` system.
1837///
1838/// @param x character: variable name to bind
1839/// @param value expression (unevaluated) to evaluate on first access
1840/// @param eval.env environment for evaluating value (default: parent.frame())
1841/// @param assign.env environment to bind in (default: parent.frame())
1842/// @return NULL (invisibly)
1843/// @namespace base
1844#[pre_eval_builtin(name = "delayedAssign", min_args = 2)]
1845fn pre_eval_delayed_assign(
1846    args: &[Arg],
1847    env: &Environment,
1848    context: &BuiltinContext,
1849) -> Result<RValue, RError> {
1850    // First arg: variable name (evaluated to get a string)
1851    let name = args.first().and_then(|a| a.value.as_ref()).ok_or_else(|| {
1852        RError::new(
1853            RErrorKind::Argument,
1854            "delayedAssign requires a variable name",
1855        )
1856    })?;
1857    let name = context.with_interpreter(|interp| interp.eval_in(name, env))?;
1858    let name = name
1859        .as_vector()
1860        .and_then(|v| v.as_character_scalar())
1861        .ok_or_else(|| {
1862            RError::new(
1863                RErrorKind::Argument,
1864                "first argument must be a character string",
1865            )
1866        })?;
1867
1868    // Second arg: the expression to evaluate lazily (captured as unevaluated AST)
1869    let value_expr = args
1870        .get(1)
1871        .and_then(|a| a.value.as_ref())
1872        .cloned()
1873        .unwrap_or(Expr::Null);
1874
1875    // Third arg: eval.env (default: parent.frame() = env)
1876    let eval_env = if let Some(arg) = args.get(2).and_then(|a| a.value.as_ref()) {
1877        match context.with_interpreter(|interp| interp.eval_in(arg, env))? {
1878            RValue::Environment(e) => e,
1879            _ => env.clone(),
1880        }
1881    } else {
1882        env.clone()
1883    };
1884
1885    // Fourth arg: assign.env (default: parent.frame() = env)
1886    let assign_env = if let Some(arg) = args.get(3).and_then(|a| a.value.as_ref()) {
1887        match context.with_interpreter(|interp| interp.eval_in(arg, env))? {
1888            RValue::Environment(e) => e,
1889            _ => env.clone(),
1890        }
1891    } else {
1892        env.clone()
1893    };
1894
1895    // Create a promise and bind it in assign_env
1896    let promise = RValue::Promise(std::rc::Rc::new(std::cell::RefCell::new(RPromise::new(
1897        value_expr, eval_env,
1898    ))));
1899    assign_env.set(name, promise);
1900
1901    Ok(RValue::Null)
1902}
1903
1904// endregion