Skip to main content

r/
interpreter.rs

1mod arguments;
2mod assignment;
3pub mod builtins;
4pub mod call;
5mod call_eval;
6pub mod coerce;
7mod control_flow;
8pub mod environment;
9pub mod graphics;
10pub mod grid;
11pub(crate) mod indexing;
12#[cfg(feature = "native")]
13pub mod native;
14mod ops;
15pub mod packages;
16mod s3;
17pub mod value;
18
19use std::cell::RefCell;
20use std::io::Write;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::Arc;
23
24use tracing::{debug, info, trace};
25
26use crate::parser::ast::*;
27pub use call::BuiltinContext;
28pub(crate) use call::{CallFrame, S3DispatchContext};
29pub use call::{NativeBacktrace, TraceEntry};
30use environment::Environment;
31use value::*;
32
33/// Result of forcing all arguments: (positional_values, named_values).
34type ForcedArgs = (Vec<RValue>, Vec<(String, RValue)>);
35
36// region: InterpreterRng
37
38/// Selectable RNG backend for the interpreter.
39///
40/// `Fast` (default) uses `SmallRng` (Xoshiro256++) — fast, non-cryptographic,
41/// suitable for R's statistical RNG. `Deterministic` uses ChaCha20 — slower
42/// but produces identical sequences across all platforms and Rust versions,
43/// which is important for cross-platform reproducibility.
44#[cfg(feature = "random")]
45pub(crate) enum InterpreterRng {
46    Fast(rand::rngs::SmallRng),
47    Deterministic(Box<rand_chacha::ChaCha20Rng>),
48}
49
50#[cfg(feature = "random")]
51impl rand::TryRng for InterpreterRng {
52    type Error = std::convert::Infallible;
53
54    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
55        match self {
56            InterpreterRng::Fast(rng) => rng.try_next_u32(),
57            InterpreterRng::Deterministic(rng) => rng.try_next_u32(),
58        }
59    }
60
61    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
62        match self {
63            InterpreterRng::Fast(rng) => rng.try_next_u64(),
64            InterpreterRng::Deterministic(rng) => rng.try_next_u64(),
65        }
66    }
67
68    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
69        match self {
70            InterpreterRng::Fast(rng) => rng.try_fill_bytes(dest),
71            InterpreterRng::Deterministic(rng) => rng.try_fill_bytes(dest),
72        }
73    }
74}
75
76/// The kind of RNG currently selected, for reporting via `RNGkind()`.
77#[cfg(feature = "random")]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub(crate) enum RngKind {
80    Xoshiro,
81    ChaCha20,
82}
83
84#[cfg(feature = "random")]
85impl std::fmt::Display for RngKind {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            RngKind::Xoshiro => write!(f, "Xoshiro"),
89            RngKind::ChaCha20 => write!(f, "ChaCha20"),
90        }
91    }
92}
93
94// endregion
95
96thread_local! {
97    static INTERPRETER: RefCell<Interpreter> = RefCell::new(Interpreter::new());
98}
99
100/// Access the thread-local interpreter. Safe for nested/re-entrant calls
101/// because all methods take `&self` (shared borrows are re-entrant).
102pub fn with_interpreter<F, R>(f: F) -> R
103where
104    F: FnOnce(&Interpreter) -> R,
105{
106    INTERPRETER.with(|cell| f(&cell.borrow()))
107}
108
109/// Temporarily install an explicit interpreter instance into thread-local state
110/// while executing `f`. This keeps legacy builtin TLS access working, but lets
111/// higher-level code own interpreter instances directly.
112pub fn with_interpreter_state<F, R>(state: &mut Interpreter, f: F) -> R
113where
114    F: FnOnce(&Interpreter) -> R,
115{
116    INTERPRETER.with(|cell| {
117        {
118            let mut installed = cell.borrow_mut();
119            std::mem::swap(&mut *installed, state);
120        }
121
122        struct Restore<'a> {
123            cell: &'a RefCell<Interpreter>,
124            state: &'a mut Interpreter,
125        }
126
127        impl Drop for Restore<'_> {
128            fn drop(&mut self) {
129                let mut installed = self.cell.borrow_mut();
130                std::mem::swap(&mut *installed, self.state);
131            }
132        }
133
134        let _restore = Restore { cell, state };
135        let installed = cell.borrow();
136        f(&installed)
137    })
138}
139
140fn formula_value(expr: Expr, env: &Environment) -> RValue {
141    let mut lang = Language::new(expr);
142    lang.set_attr(
143        "class".to_string(),
144        RValue::vec(Vector::Character(vec![Some("formula".to_string())].into())),
145    );
146    lang.set_attr(".Environment".to_string(), RValue::Environment(env.clone()));
147    RValue::Language(lang)
148}
149
150/// A handler registered by withCallingHandlers().
151#[derive(Clone)]
152pub(crate) struct ConditionHandler {
153    pub class: String,
154    pub handler: RValue,
155    #[allow(dead_code)]
156    pub env: Environment,
157}
158
159/// Semantic styles for colored diagnostic output.
160///
161/// Used by `Interpreter::write_stderr_colored` to select the appropriate color
162/// when the `color` feature is enabled and stderr is connected to a terminal.
163/// When color is unavailable or disabled, the text is written uncolored.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum DiagnosticStyle {
166    /// Error messages — displayed in bold red.
167    Error,
168    /// Warning messages — displayed in bold yellow.
169    Warning,
170    /// Informational messages — displayed in cyan.
171    Message,
172}
173
174pub struct Interpreter {
175    pub global_env: Environment,
176    /// Per-interpreter stdout writer. Defaults to `std::io::stdout()`.
177    /// Use `Vec<u8>` for captured/embedded output.
178    pub(crate) stdout: RefCell<Box<dyn Write>>,
179    /// Per-interpreter stderr writer. Defaults to `std::io::stderr()`.
180    pub(crate) stderr: RefCell<Box<dyn Write>>,
181    /// Whether to emit colored diagnostics to stderr. True when stderr is a
182    /// real terminal and the `color` feature is enabled; false for captured
183    /// output sessions or when the feature is off.
184    color_stderr: bool,
185    s3_dispatch_stack: RefCell<Vec<S3DispatchContext>>,
186    call_stack: RefCell<Vec<CallFrame>>,
187    /// Snapshot of the call stack at the point the last error occurred.
188    /// Populated by `call_closure`/`call_closure_lazy` when an error propagates,
189    /// cleared at the start of each top-level `eval()`.
190    pub(crate) last_traceback: RefCell<Vec<TraceEntry>>,
191    /// Stack of source contexts for file:line resolution in tracebacks.
192    /// Pushed by `source()` / `eval_file()`, popped when done.
193    /// Each entry is (filename, source_text) — byte offsets in Span resolve against source_text.
194    pub(crate) source_stack: RefCell<Vec<(String, String)>>,
195    /// Generation counter for traceback capture. Incremented at each top-level
196    /// `eval()` so we can distinguish "same error propagating up" from "new error
197    /// in a subsequent eval".
198    traceback_generation: std::cell::Cell<u64>,
199    /// The generation at which `last_traceback` was captured.
200    traceback_captured_generation: std::cell::Cell<u64>,
201    /// Native backtrace from the most recent .Call/.C error (Rf_error in C code).
202    /// Set by `dot_call`/`dot_c` on error, consumed by `capture_traceback()`.
203    #[cfg(feature = "native")]
204    pub(crate) pending_native_backtrace: RefCell<Option<NativeBacktrace>>,
205    /// Stack of handler sets from withCallingHandlers() calls.
206    pub(crate) condition_handlers: RefCell<Vec<Vec<ConditionHandler>>>,
207    /// Per-interpreter RNG state. Defaults to `SmallRng` (Xoshiro256++) for
208    /// speed; can be switched to `ChaCha20Rng` via `RNGkind("ChaCha20")` for
209    /// cross-platform deterministic reproducibility.
210    ///
211    /// # Parallel RNG considerations
212    ///
213    /// The RNG is behind `RefCell` on the single-threaded `Interpreter`, so there
214    /// are no data races. If we ever add rayon-based parallel operations, each
215    /// worker thread must get its own RNG seeded deterministically from the parent
216    /// to avoid contention and ensure reproducibility.
217    #[cfg(feature = "random")]
218    rng: RefCell<InterpreterRng>,
219    /// Which RNG algorithm is currently selected.
220    #[cfg(feature = "random")]
221    pub(crate) rng_kind: std::cell::Cell<RngKind>,
222    /// Session-scoped temporary directory, auto-cleaned on drop.
223    pub(crate) temp_dir: temp_dir::TempDir,
224    /// Counter for unique tempfile names within the session.
225    pub(crate) temp_counter: std::cell::Cell<u64>,
226    /// Per-interpreter environment variables, snapshotted at interpreter creation
227    /// and then mutated locally via Sys.setenv().
228    pub(crate) env_vars: RefCell<std::collections::HashMap<String, String>>,
229    /// Per-interpreter working directory, snapshotted at interpreter creation
230    /// and then mutated locally via setwd().
231    pub(crate) working_dir: RefCell<std::path::PathBuf>,
232    /// Instant when the interpreter was created, used by proc.time() for elapsed time.
233    pub(crate) start_instant: std::time::Instant,
234    /// Collection objects (HashMap, BTreeMap, HashSet, BinaryHeap, VecDeque).
235    /// Each collection is addressed by its index in this Vec.
236    #[cfg(feature = "collections")]
237    pub(crate) collections: RefCell<Vec<builtins::collections::CollectionObject>>,
238    /// Connection table — slots 0-2 are stdin/stdout/stderr, lazily initialised.
239    pub(crate) connections: RefCell<Vec<builtins::connections::ConnectionInfo>>,
240    /// TCP stream handles, keyed by connection ID. Stored separately from
241    /// `ConnectionInfo` because `TcpStream` is not `Clone`.
242    pub(crate) tcp_streams: RefCell<std::collections::HashMap<usize, std::net::TcpStream>>,
243    /// Buffered response bodies for URL connections, keyed by connection ID.
244    /// URL connections eagerly fetch the entire HTTP response body, which is
245    /// stored here for subsequent `readLines()` calls.
246    #[cfg(feature = "tls")]
247    pub(crate) url_bodies: RefCell<std::collections::HashMap<usize, Vec<u8>>>,
248    /// Finalizers registered with reg.finalizer(onexit = TRUE), run when the
249    /// interpreter is dropped.
250    pub(crate) finalizers: RefCell<Vec<RValue>>,
251    /// Flag set by the SIGINT handler; checked at loop boundaries to interrupt
252    /// long-running computations without killing the process.
253    interrupted: Arc<AtomicBool>,
254    /// Per-interpreter R options (accessed via `options()` and `getOption()`).
255    pub(crate) options: RefCell<std::collections::HashMap<String, value::RValue>>,
256    /// Rd documentation help index for package `man/` directories.
257    pub(crate) rd_help_index: RefCell<packages::rd::RdHelpIndex>,
258    /// Visibility flag — set to `true` by `invisible()` to suppress auto-printing
259    pub(crate) last_value_invisible: std::cell::Cell<bool>,
260    /// Recursion depth counter — prevents stack overflow on deeply nested expressions.
261    eval_depth: std::cell::Cell<usize>,
262    /// Maximum eval depth for this interpreter, computed from the creating thread's
263    /// stack size. Different threads may have different stack sizes, so this adapts.
264    max_eval_depth: usize,
265    /// Stack pointer at interpreter creation — used to estimate remaining stack.
266    stack_base: usize,
267    /// S4 class registry: class name -> class definition.
268    pub(crate) s4_classes: RefCell<std::collections::HashMap<String, S4ClassDef>>,
269    /// S4 generic registry: generic name -> generic definition.
270    pub(crate) s4_generics: RefCell<std::collections::HashMap<String, S4GenericDef>>,
271    /// S4 method dispatch table: (generic, signature) -> method function.
272    pub(crate) s4_methods: RefCell<std::collections::HashMap<S4MethodKey, value::RValue>>,
273    /// Loaded package namespaces, keyed by package name.
274    pub(crate) loaded_namespaces:
275        RefCell<std::collections::HashMap<String, packages::LoadedNamespace>>,
276    /// Search path entries (between .GlobalEnv and package:base).
277    pub(crate) search_path: RefCell<Vec<packages::SearchPathEntry>>,
278    /// S3 method registry for methods declared via S3method() in NAMESPACE files.
279    /// Key is (generic, class), value is the method function.
280    /// Checked by dispatch_s3 after the environment chain lookup fails.
281    pub(crate) s3_method_registry:
282        RefCell<std::collections::HashMap<(String, String), value::RValue>>,
283    /// Active progress bars, keyed by integer ID.
284    #[cfg(feature = "progress")]
285    pub(crate) progress_bars: RefCell<Vec<Option<builtins::progress::ProgressBarState>>>,
286    /// Graphics parameters (par state) — per-interpreter, not global.
287    pub(crate) par_state: RefCell<graphics::par::ParState>,
288    /// Grid graphics display list — records grob objects drawn on the page (R-level).
289    pub(crate) grid_display_list: RefCell<Vec<RValue>>,
290    /// Grid viewport stack — tracks pushed viewports for grid graphics (R-level).
291    pub(crate) grid_viewport_stack: RefCell<Vec<RValue>>,
292    /// Grid grob store — stores Rust-level grobs indexed by GrobId for rendering.
293    pub(crate) grid_grob_store: RefCell<grid::grob::GrobStore>,
294    /// Grid Rust-level display list — records viewport pushes/pops and grob draws for replay.
295    pub(crate) grid_rust_display_list: RefCell<grid::display::DisplayList>,
296    /// Grid Rust-level viewport stack — tracks pushed viewports for rendering.
297    pub(crate) grid_rust_viewport_stack: RefCell<grid::viewport::ViewportStack>,
298    /// Color palette for indexed color access (e.g. col=1 means palette[0]).
299    pub(crate) color_palette: RefCell<Vec<graphics::color::RColor>>,
300    /// Current plot being accumulated (for egui_plot rendering).
301    pub(crate) current_plot: RefCell<Option<graphics::plot_data::PlotState>>,
302    /// Active file device (SVG/PNG/PDF) — when set, dev.off() writes to file.
303    pub(crate) file_device: RefCell<Option<graphics::FileDevice>>,
304    /// Loaded native shared libraries (dyn.load).
305    #[cfg(feature = "native")]
306    pub(crate) loaded_dlls: RefCell<Vec<native::dll::LoadedDll>>,
307    /// Channel sender for pushing plots to the GUI thread (when `plot` feature is on).
308    #[cfg(feature = "plot")]
309    pub(crate) plot_tx: RefCell<Option<graphics::egui_device::PlotSender>>,
310}
311
312/// S4 class definition stored in the per-interpreter class registry.
313#[derive(Debug, Clone)]
314pub struct S4ClassDef {
315    pub name: String,
316    pub slots: Vec<(String, String)>,
317    pub contains: Vec<String>,
318    pub prototype: Vec<(String, value::RValue)>,
319    pub is_virtual: bool,
320    pub validity: Option<value::RValue>,
321}
322
323/// Key for S4 method dispatch table: (generic_name, signature).
324pub(crate) type S4MethodKey = (String, Vec<String>);
325
326/// S4 generic function definition.
327#[derive(Debug, Clone)]
328#[allow(dead_code)]
329pub(crate) struct S4GenericDef {
330    pub name: String,
331    pub default: Option<value::RValue>,
332}
333
334impl Default for Interpreter {
335    fn default() -> Self {
336        Self::new()
337    }
338}
339
340impl Drop for Interpreter {
341    fn drop(&mut self) {
342        let finalizers: Vec<RValue> = self.finalizers.borrow_mut().drain(..).collect();
343        if finalizers.is_empty() {
344            return;
345        }
346        let env = self.global_env.clone();
347        for f in &finalizers {
348            // Best-effort: errors during finalizer execution are silently ignored,
349            // matching R's behavior for on-exit finalizers.
350            self.call_function(f, &[RValue::Environment(env.clone())], &[], &env)
351                .ok();
352        }
353    }
354}
355
356impl Interpreter {
357    fn ensure_builtin_min_arity(
358        name: &str,
359        min_args: usize,
360        actual_args: usize,
361    ) -> Result<(), RError> {
362        if min_args == 0 || actual_args >= min_args {
363            return Ok(());
364        }
365
366        let expectation = match min_args {
367            1 => "requires at least 1 argument".to_string(),
368            n => format!("requires at least {n} arguments"),
369        };
370        let suffix = if actual_args == 1 { "" } else { "s" };
371
372        Err(RError::new(
373            RErrorKind::Argument,
374            format!("{name}() {expectation}, got {actual_args} argument{suffix}"),
375        ))
376    }
377
378    fn ensure_builtin_max_arity(
379        name: &str,
380        max_args: Option<usize>,
381        actual_args: usize,
382    ) -> Result<(), RError> {
383        let Some(max_args) = max_args else {
384            return Ok(());
385        };
386
387        if actual_args <= max_args {
388            return Ok(());
389        }
390
391        let expectation = match max_args {
392            0 => "takes no arguments".to_string(),
393            1 => "takes at most 1 argument".to_string(),
394            n => format!("takes at most {n} arguments"),
395        };
396        let suffix = if actual_args == 1 { "" } else { "s" };
397
398        Err(RError::new(
399            RErrorKind::Argument,
400            format!("{name}() {expectation}, got {actual_args} argument{suffix}"),
401        ))
402    }
403
404    pub fn new() -> Self {
405        info!("creating new R interpreter");
406        // Initialize the native runtime globals (R_NilValue, symbols, etc.)
407        #[cfg(feature = "native")]
408        native::runtime::init_globals();
409
410        let base_env = Environment::new_global();
411        base_env.set_name("base".to_string());
412        builtins::register_builtins(&base_env);
413        let global_env = Environment::new_child(&base_env);
414        global_env.set_name("R_GlobalEnv".to_string());
415
416        // Bind R's standard environment variables in base
417        base_env.set(
418            ".GlobalEnv".to_string(),
419            value::RValue::Environment(global_env.clone()),
420        );
421        base_env.set(
422            ".BaseNamespaceEnv".to_string(),
423            value::RValue::Environment(base_env.clone()),
424        );
425
426        let color_stderr = {
427            use std::io::IsTerminal;
428            std::io::stderr().is_terminal()
429        };
430        let interp = Interpreter {
431            global_env,
432            stdout: RefCell::new(Box::new(std::io::stdout())),
433            stderr: RefCell::new(Box::new(std::io::stderr())),
434            color_stderr,
435            s3_dispatch_stack: RefCell::new(Vec::new()),
436            call_stack: RefCell::new(Vec::new()),
437            last_traceback: RefCell::new(Vec::new()),
438            source_stack: RefCell::new(Vec::new()),
439            traceback_generation: std::cell::Cell::new(0),
440            traceback_captured_generation: std::cell::Cell::new(0),
441            #[cfg(feature = "native")]
442            pending_native_backtrace: RefCell::new(None),
443            condition_handlers: RefCell::new(Vec::new()),
444            #[cfg(feature = "random")]
445            rng: RefCell::new({
446                use rand::SeedableRng;
447                let mut thread_rng = rand::rng();
448                InterpreterRng::Fast(rand::rngs::SmallRng::from_rng(&mut thread_rng))
449            }),
450            #[cfg(feature = "random")]
451            rng_kind: std::cell::Cell::new(RngKind::Xoshiro),
452            temp_dir: temp_dir::TempDir::new().expect("failed to create session temp directory"),
453            temp_counter: std::cell::Cell::new(0),
454            env_vars: RefCell::new(std::env::vars().collect()),
455            working_dir: RefCell::new(
456                std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
457            ),
458            start_instant: std::time::Instant::now(),
459            #[cfg(feature = "collections")]
460            collections: RefCell::new(Vec::new()),
461            connections: RefCell::new(Vec::new()),
462            tcp_streams: RefCell::new(std::collections::HashMap::new()),
463            #[cfg(feature = "tls")]
464            url_bodies: RefCell::new(std::collections::HashMap::new()),
465            finalizers: RefCell::new(Vec::new()),
466            interrupted: Arc::new(AtomicBool::new(false)),
467            options: RefCell::new(Self::default_options()),
468            rd_help_index: RefCell::new(packages::rd::RdHelpIndex::new()),
469            last_value_invisible: std::cell::Cell::new(false),
470            eval_depth: std::cell::Cell::new(0),
471            max_eval_depth: Self::compute_max_eval_depth(),
472            stack_base: Self::current_stack_addr(),
473            s4_classes: RefCell::new(std::collections::HashMap::new()),
474            s4_generics: RefCell::new(std::collections::HashMap::new()),
475            s4_methods: RefCell::new(std::collections::HashMap::new()),
476            loaded_namespaces: RefCell::new(std::collections::HashMap::new()),
477            search_path: RefCell::new(Vec::new()),
478            s3_method_registry: RefCell::new(std::collections::HashMap::new()),
479            #[cfg(feature = "progress")]
480            progress_bars: RefCell::new(Vec::new()),
481            par_state: RefCell::new(graphics::par::ParState::default()),
482            grid_display_list: RefCell::new(Vec::new()),
483            grid_viewport_stack: RefCell::new(Vec::new()),
484            grid_grob_store: RefCell::new(grid::grob::GrobStore::new()),
485            grid_rust_display_list: RefCell::new(grid::display::DisplayList::new()),
486            grid_rust_viewport_stack: RefCell::new(grid::viewport::ViewportStack::new(
487                17.78, 17.78,
488            )),
489            color_palette: RefCell::new(graphics::color::default_palette()),
490            current_plot: RefCell::new(None),
491            file_device: RefCell::new(None),
492            #[cfg(feature = "native")]
493            loaded_dlls: RefCell::new(Vec::new()),
494            #[cfg(feature = "plot")]
495            plot_tx: RefCell::new(None),
496        };
497
498        // Synthesize Rd help pages from builtin rustdoc comments so every
499        // builtin has a rich help page via ?name from the start.
500        builtins::synthesize_builtin_help(&mut interp.rd_help_index.borrow_mut());
501
502        interp
503    }
504
505    /// Index all `.Rd` files in a package's `man/` directory for `help()` lookup.
506    pub fn index_package_help(&self, package_name: &str, man_dir: &std::path::Path) {
507        self.rd_help_index
508            .borrow_mut()
509            .index_package_dir(package_name, man_dir);
510    }
511
512    /// Mark the current result as invisible (suppresses auto-printing).
513    pub(crate) fn set_invisible(&self) {
514        self.last_value_invisible.set(true);
515    }
516
517    /// Check and consume the invisible flag. Returns `true` if the last
518    /// value was marked invisible. The flag is reset after reading.
519    pub(crate) fn take_invisible(&self) -> bool {
520        self.last_value_invisible.replace(false)
521    }
522
523    /// Return a clone of the interrupt flag so the SIGINT handler can set it.
524    pub fn interrupt_flag(&self) -> Arc<AtomicBool> {
525        Arc::clone(&self.interrupted)
526    }
527
528    /// Check the interrupt flag; if set, clear it and return an interrupt error.
529    pub(crate) fn check_interrupt(&self) -> Result<(), RFlow> {
530        if self.interrupted.load(Ordering::Relaxed) {
531            self.interrupted.store(false, Ordering::Relaxed);
532            debug!("SIGINT interrupt detected");
533            Err(RFlow::Error(RError::interrupt()))
534        } else {
535            Ok(())
536        }
537    }
538
539    /// Signal a condition to withCallingHandlers handlers (non-unwinding).
540    /// Returns Ok(true) if muffled, Ok(false) if not handled, or Err if a handler
541    /// raised an unwinding condition (e.g. tryCatch's unwind handler).
542    pub(crate) fn signal_condition(
543        &self,
544        condition: &RValue,
545        env: &Environment,
546    ) -> Result<bool, RError> {
547        let classes = value::get_class(condition);
548        // Clone handlers to release the borrow — handlers may trigger nested conditions
549        let handler_stack: Vec<Vec<ConditionHandler>> = self.condition_handlers.borrow().clone();
550        // Walk handlers top-down (most recently established first)
551        for handler_set in handler_stack.iter().rev() {
552            for handler in handler_set {
553                if classes.iter().any(|c| c == &handler.class) {
554                    // Call the handler — if it returns normally, continue signaling
555                    let result = self.call_function(
556                        &handler.handler,
557                        std::slice::from_ref(condition),
558                        &[],
559                        env,
560                    );
561                    match &result {
562                        Err(RFlow::Error(RError::Standard { message: msg, .. }))
563                            if msg == "muffleWarning" || msg == "muffleMessage" =>
564                        {
565                            return Ok(true);
566                        }
567                        Err(e) => return Err(RError::from(e.clone())),
568                        Ok(_) => {} // handler returned normally, continue signaling
569                    }
570                }
571            }
572        }
573        Ok(false)
574    }
575
576    /// Default R options, matching GNU R defaults where sensible.
577    fn default_options() -> std::collections::HashMap<String, value::RValue> {
578        use value::{RValue, Vector};
579        let mut opts = std::collections::HashMap::new();
580        opts.insert(
581            "digits".to_string(),
582            RValue::vec(Vector::Integer(vec![Some(7)].into())),
583        );
584        opts.insert(
585            "warn".to_string(),
586            RValue::vec(Vector::Integer(vec![Some(0)].into())),
587        );
588        opts.insert(
589            "OutDec".to_string(),
590            RValue::vec(Vector::Character(vec![Some(".".to_string())].into())),
591        );
592        opts.insert(
593            "scipen".to_string(),
594            RValue::vec(Vector::Integer(vec![Some(0)].into())),
595        );
596        opts.insert(
597            "max.print".to_string(),
598            RValue::vec(Vector::Integer(vec![Some(99999)].into())),
599        );
600        opts.insert(
601            "width".to_string(),
602            RValue::vec(Vector::Integer(vec![Some(80)].into())),
603        );
604        opts.insert(
605            "warning.length".to_string(),
606            RValue::vec(Vector::Integer(vec![Some(1000)].into())),
607        );
608        opts.insert(
609            "prompt".to_string(),
610            RValue::vec(Vector::Character(vec![Some("> ".to_string())].into())),
611        );
612        opts.insert(
613            "continue".to_string(),
614            RValue::vec(Vector::Character(vec![Some("+ ".to_string())].into())),
615        );
616        opts.insert(
617            "encoding".to_string(),
618            RValue::vec(Vector::Character(
619                vec![Some("native.enc".to_string())].into(),
620            )),
621        );
622        opts.insert(
623            "stringsAsFactors".to_string(),
624            RValue::vec(Vector::Logical(vec![Some(false)].into())),
625        );
626        opts
627    }
628
629    #[cfg(feature = "random")]
630    pub(crate) fn rng(&self) -> &RefCell<InterpreterRng> {
631        &self.rng
632    }
633
634    /// Get an environment variable from the interpreter-local snapshot.
635    pub(crate) fn get_env_var(&self, name: &str) -> Option<String> {
636        self.env_vars.borrow().get(name).cloned()
637    }
638
639    /// Return a clone of the interpreter-local environment snapshot.
640    pub(crate) fn env_vars_snapshot(&self) -> std::collections::HashMap<String, String> {
641        self.env_vars.borrow().clone()
642    }
643
644    /// Set a per-interpreter environment variable (does not mutate process state).
645    pub(crate) fn set_env_var(&self, name: String, value: String) {
646        self.env_vars.borrow_mut().insert(name, value);
647    }
648
649    /// Remove a per-interpreter environment variable (does not mutate process state).
650    pub(crate) fn remove_env_var(&self, name: &str) {
651        self.env_vars.borrow_mut().remove(name);
652    }
653
654    /// Register an S3 method in the per-interpreter registry.
655    /// This is used by NAMESPACE S3method() directives to make methods
656    /// discoverable by S3 dispatch without polluting the base environment.
657    pub(crate) fn register_s3_method(&self, generic: String, class: String, method: RValue) {
658        self.s3_method_registry
659            .borrow_mut()
660            .insert((generic, class), method);
661    }
662
663    /// Look up an S3 method from the per-interpreter registry.
664    /// Returns the method function if one was registered for the given
665    /// generic/class combination.
666    pub(crate) fn lookup_s3_method(&self, generic: &str, class: &str) -> Option<RValue> {
667        self.s3_method_registry
668            .borrow()
669            .get(&(generic.to_string(), class.to_string()))
670            .cloned()
671    }
672
673    /// Get the interpreter-local working directory.
674    pub(crate) fn get_working_dir(&self) -> std::path::PathBuf {
675        self.working_dir.borrow().clone()
676    }
677
678    /// Set the per-interpreter working directory (does not mutate process state).
679    pub(crate) fn set_working_dir(&self, path: std::path::PathBuf) {
680        *self.working_dir.borrow_mut() = path;
681    }
682
683    /// Resolve a user-supplied path against the interpreter-local working directory.
684    pub(crate) fn resolve_path(&self, path: impl AsRef<std::path::Path>) -> std::path::PathBuf {
685        let path = path.as_ref();
686        if path.is_absolute() {
687            path.to_path_buf()
688        } else {
689            self.get_working_dir().join(path)
690        }
691    }
692
693    /// Write a message to the interpreter's stdout writer.
694    pub(crate) fn write_stdout(&self, msg: &str) {
695        // I/O errors on stdout/stderr are non-fatal — the interpreter continues.
696        // Propagating would require every print/cat/message call to handle io::Error.
697        if self.stdout.borrow_mut().write_all(msg.as_bytes()).is_err() {}
698    }
699
700    /// Write a message to the interpreter's stderr writer.
701    pub(crate) fn write_stderr(&self, msg: &str) {
702        if self.stderr.borrow_mut().write_all(msg.as_bytes()).is_err() {}
703    }
704
705    /// Whether colored stderr output is enabled for this interpreter.
706    pub fn color_stderr(&self) -> bool {
707        self.color_stderr
708    }
709
710    /// Enable or disable colored stderr output.
711    pub fn set_color_stderr(&mut self, enabled: bool) {
712        self.color_stderr = enabled;
713    }
714
715    /// Write a colored diagnostic message to the interpreter's stderr writer.
716    ///
717    /// When the `color` feature is enabled and `color_stderr` is true, the
718    /// message is written with the appropriate ANSI color. Otherwise, the
719    /// message is written as plain text.
720    pub(crate) fn write_stderr_colored(&self, msg: &str, style: DiagnosticStyle) {
721        if self.try_write_colored(msg, style) {
722            return;
723        }
724        // Fallback: no color
725        self.write_stderr(msg);
726    }
727
728    /// Attempt to write colored text. Returns `true` if color was applied,
729    /// `false` if the caller should fall back to plain text.
730    #[cfg(feature = "repl")]
731    fn try_write_colored(&self, msg: &str, style: DiagnosticStyle) -> bool {
732        if !self.color_stderr {
733            return false;
734        }
735        use crossterm::style::{Attribute, Color, Stylize};
736        use std::io::Write as _;
737
738        let (color, bold) = match style {
739            DiagnosticStyle::Error => (Color::Red, true),
740            DiagnosticStyle::Warning => (Color::Yellow, true),
741            DiagnosticStyle::Message => (Color::Cyan, false),
742        };
743
744        // Format the styled string to bytes, then write to the session stderr.
745        let styled = if bold {
746            format!("{}", msg.with(color).attribute(Attribute::Bold))
747        } else {
748            format!("{}", msg.with(color))
749        };
750        self.stderr.borrow_mut().write_all(styled.as_bytes()).ok();
751        true
752    }
753
754    /// Stub when the `repl` feature is not enabled.
755    #[cfg(not(feature = "repl"))]
756    fn try_write_colored(&self, _msg: &str, _style: DiagnosticStyle) -> bool {
757        false
758    }
759
760    /// Force a value: if it's a Promise, evaluate the promise expression and
761    /// cache the result. Returns the concrete (non-promise) value.
762    ///
763    /// Non-promise values pass through unchanged. This is the main entry point
764    /// for transparent promise forcing throughout the interpreter.
765    pub fn force_value(&self, value: RValue) -> Result<RValue, RFlow> {
766        match value {
767            RValue::Promise(ref p) => {
768                // Check if already forced
769                {
770                    let inner = p.borrow();
771                    if let Some(ref cached) = inner.value {
772                        return Ok(cached.clone());
773                    }
774                    if inner.forcing {
775                        return Err(RError::other(
776                            "promise already under evaluation: recursive default argument reference or earlier problems?"
777                                .to_string(),
778                        )
779                        .into());
780                    }
781                }
782
783                // Mark as forcing
784                p.borrow_mut().forcing = true;
785
786                // Extract what we need before evaluating (avoids holding borrow during eval)
787                let (expr, env) = {
788                    let inner = p.borrow();
789                    (inner.expr.clone(), inner.env.clone())
790                };
791
792                let result = self.eval_in(&expr, &env);
793
794                match result {
795                    Ok(val) => {
796                        // Recursively force in case evaluation returned another promise
797                        let forced = self.force_value(val)?;
798                        let mut inner = p.borrow_mut();
799                        inner.value = Some(forced.clone());
800                        inner.forcing = false;
801                        Ok(forced)
802                    }
803                    Err(e) => {
804                        p.borrow_mut().forcing = false;
805                        Err(e)
806                    }
807                }
808            }
809            other => Ok(other),
810        }
811    }
812
813    /// Force all promises in a slice of values, returning concrete values.
814    /// Used at builtin dispatch boundaries.
815    pub fn force_args(
816        &self,
817        positional: &[RValue],
818        named: &[(String, RValue)],
819    ) -> Result<ForcedArgs, RFlow> {
820        let forced_pos: Vec<RValue> = positional
821            .iter()
822            .map(|v| self.force_value(v.clone()))
823            .collect::<Result<_, _>>()?;
824        let forced_named: Vec<(String, RValue)> = named
825            .iter()
826            .map(|(n, v)| Ok((n.clone(), self.force_value(v.clone())?)))
827            .collect::<Result<_, RFlow>>()?;
828        Ok((forced_pos, forced_named))
829    }
830
831    pub fn eval(&self, expr: &Expr) -> Result<RValue, RFlow> {
832        // Increment generation so capture_traceback can distinguish new errors
833        // from the same error propagating up through frames.
834        self.traceback_generation
835            .set(self.traceback_generation.get().wrapping_add(1));
836        self.eval_in(expr, &self.global_env)
837    }
838
839    /// Maximum evaluation recursion depth before returning an error.
840    /// Stack reserved for non-eval overhead: main thread setup, native code
841    /// compilation (cc crate spawns compiler processes but builds argument
842    /// vectors on the stack), C trampoline, signal handlers, and serde
843    /// deserialization for sysdata.rda.
844    /// Default stack size assumed when we can't query the actual thread stack.
845    const DEFAULT_STACK_SIZE: usize = 8 * 1024 * 1024;
846    const STACK_RESERVED_BYTES: usize = 4 * 1024 * 1024;
847
848    /// Compute the per-frame stack cost from actual type sizes.
849    ///
850    /// Each eval recursion goes through:
851    ///   eval_in → eval_in_inner → (match arms with RValue/Expr temps)
852    ///     → call_function → eval_in (next level)
853    ///
854    /// The dominant cost is the Expr match in eval_in_inner (holds RValue
855    /// temporaries) and call_function (holds CallFrame + argument vectors).
856    /// We use size_of for the key types and add overhead for locals, return
857    /// values, and alignment padding.
858    fn stack_bytes_per_eval_frame() -> usize {
859        let rvalue = std::mem::size_of::<value::RValue>();
860        let expr = std::mem::size_of::<crate::parser::ast::Expr>();
861        let env = std::mem::size_of::<environment::Environment>();
862        let call_frame = std::mem::size_of::<call::CallFrame>();
863        // eval_in_inner holds ~4 RValue temps + 2 Expr refs + 1 env
864        // call_function holds 1 CallFrame + ~4 RValue args + return
865        // Multiply by 3 for alignment, spills, and debug info overhead
866        let frame_cost = (rvalue * 8 + expr * 2 + env * 2 + call_frame) * 3;
867        // Minimum 4KB per frame (measured baseline)
868        frame_cost.max(4096)
869    }
870
871    /// Compute the max eval depth from the available stack budget.
872    fn compute_max_eval_depth() -> usize {
873        // Default 8MB stack (macOS/Linux main thread)
874        let stack_size = 8 * 1024 * 1024_usize;
875        let usable = stack_size.saturating_sub(Self::STACK_RESERVED_BYTES);
876        let frame_cost = Self::stack_bytes_per_eval_frame();
877        let depth = usable / frame_cost;
878        depth.clamp(50, 5000)
879    }
880
881    /// Get the approximate current stack address (for stack usage estimation).
882    #[inline(never)]
883    fn current_stack_addr() -> usize {
884        let local = 0u8;
885        std::ptr::addr_of!(local) as usize
886    }
887
888    /// Check if we're running low on stack space (within STACK_RESERVED_BYTES
889    /// of the estimated stack limit). This catches recursion that bypasses
890    /// the eval_depth counter (e.g. Rust-level recursion in the loader).
891    #[inline]
892    fn stack_is_low(&self) -> bool {
893        let current = Self::current_stack_addr();
894        // Stack grows downward on all supported platforms
895        let used = self.stack_base.saturating_sub(current);
896        let limit = Self::DEFAULT_STACK_SIZE.saturating_sub(Self::STACK_RESERVED_BYTES);
897        used > limit
898    }
899
900    #[tracing::instrument(level = "trace", skip(self, env))]
901    pub fn eval_in(&self, expr: &Expr, env: &Environment) -> Result<RValue, RFlow> {
902        let depth = self.eval_depth.get();
903        if depth >= self.max_eval_depth || self.stack_is_low() {
904            // Print a traceback to help diagnose infinite recursion
905            let frames = self.call_stack.borrow();
906            if !frames.is_empty() {
907                self.write_stderr("Traceback (most recent call last):\n");
908                for (i, frame) in frames.iter().rev().enumerate().take(20) {
909                    let name = match &frame.call {
910                        Some(call_expr) => format!(
911                            "{}",
912                            crate::interpreter::value::RValue::Language(
913                                crate::interpreter::value::Language::new(call_expr.clone())
914                            )
915                        ),
916                        None => "<anonymous>".to_string(),
917                    };
918                    self.write_stderr(&format!("{}: {}\n", i + 1, name));
919                }
920            }
921            return Err(RFlow::Error(value::RError::other(
922                "evaluation nested too deeply: infinite recursion / options(expressions=) ?"
923                    .to_string(),
924            )));
925        }
926        self.eval_depth.set(depth + 1);
927        let result = self.eval_in_inner(expr, env);
928        self.eval_depth.set(depth);
929        result
930    }
931
932    fn eval_in_inner(&self, expr: &Expr, env: &Environment) -> Result<RValue, RFlow> {
933        match expr {
934            Expr::Null => Ok(RValue::Null),
935            Expr::Na(na_type) => Ok(match na_type {
936                NaType::Logical => RValue::vec(Vector::Logical(vec![None].into())),
937                NaType::Integer => RValue::vec(Vector::Integer(vec![None].into())),
938                NaType::Real => RValue::vec(Vector::Double(vec![None].into())),
939                NaType::Character => RValue::vec(Vector::Character(vec![None].into())),
940                NaType::Complex => RValue::vec(Vector::Double(vec![None].into())),
941            }),
942            Expr::Inf => Ok(RValue::vec(Vector::Double(
943                vec![Some(f64::INFINITY)].into(),
944            ))),
945            Expr::NaN => Ok(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into()))),
946            Expr::Bool(b) => Ok(RValue::vec(Vector::Logical(vec![Some(*b)].into()))),
947            Expr::Integer(i) => Ok(RValue::vec(Vector::Integer(vec![Some(*i)].into()))),
948            Expr::Double(f) => Ok(RValue::vec(Vector::Double(vec![Some(*f)].into()))),
949            Expr::String(s) => Ok(RValue::vec(Vector::Character(vec![Some(s.clone())].into()))),
950            Expr::Complex(f) => Ok(RValue::vec(Vector::Complex(
951                vec![Some(num_complex::Complex64::new(0.0, *f))].into(),
952            ))),
953            Expr::Symbol(name) => {
954                // Check for active bindings first — these re-evaluate a function on every access
955                if let Some(fun) = env.get_active_binding(name) {
956                    return self.call_function(&fun, &[], &[], env);
957                }
958                let val = env.get(name).ok_or_else(|| {
959                    debug!(symbol = name.as_str(), "symbol not found");
960                    RFlow::from(RError::new(RErrorKind::Name, name.clone()))
961                })?;
962                // Transparently force promises on access
963                self.force_value(val)
964            }
965            Expr::Dots => {
966                // Return the ... list from the current environment
967                env.get("...").ok_or_else(|| {
968                    RError::other("'...' used in incorrect context".to_string()).into()
969                })
970            }
971            Expr::DotDot(n) => {
972                if *n == 0 {
973                    return Err(RError::other(
974                        "..0 is not valid — R uses 1-based indexing for ... arguments.\n  \
975                         Did you mean ..1? (..1 is the first element, ..2 is the second, etc.)",
976                    )
977                    .into());
978                }
979                // ..1, ..2 etc. — 1-indexed access into ...
980                let dots = env
981                    .get("...")
982                    .ok_or_else(|| RError::other(format!("'..{}' used in incorrect context", n)))?;
983                match dots {
984                    RValue::List(list) => {
985                        let idx = usize::try_from(i64::from(*n))?.saturating_sub(1);
986                        let val =
987                            list.values
988                                .get(idx)
989                                .map(|(_, v)| v.clone())
990                                .ok_or_else(|| {
991                                    RFlow::from(RError::other(format!(
992                                        "the ... list does not contain {} elements",
993                                        n
994                                    )))
995                                })?;
996                        // Force promise if the dots element is a lazy argument
997                        self.force_value(val)
998                    }
999                    _ => Err(RError::other(format!("'..{}' used in incorrect context", n)).into()),
1000                }
1001            }
1002
1003            Expr::UnaryOp { op, operand } => {
1004                let val = self.eval_in(operand, env)?;
1005                self.eval_unary(*op, &val)
1006            }
1007            Expr::BinaryOp { op, lhs, rhs } => {
1008                // Special handling for pipe operators and walrus assignment
1009                match op {
1010                    // := — walrus operator, behaves like <- (used by data.table)
1011                    BinaryOp::Special(SpecialOp::Walrus) => {
1012                        let val = self.eval_in(rhs, env)?;
1013                        return self.eval_assign(
1014                            &crate::parser::ast::AssignOp::LeftAssign,
1015                            lhs,
1016                            val,
1017                            env,
1018                        );
1019                    }
1020                    BinaryOp::Pipe => return self.eval_pipe(lhs, rhs, env),
1021                    BinaryOp::AssignPipe => {
1022                        // %<>% — pipe and assign back: x %<>% f() → x <- f(x)
1023                        let result = self.eval_pipe(lhs, rhs, env)?;
1024                        self.eval_assign(
1025                            &crate::parser::ast::AssignOp::LeftAssign,
1026                            lhs,
1027                            result.clone(),
1028                            env,
1029                        )?;
1030                        return Ok(result);
1031                    }
1032                    BinaryOp::TeePipe => {
1033                        // %T>% — pipe for side effect, return LHS
1034                        // Evaluate the pipe (which evaluates LHS and calls RHS)
1035                        // but return the original LHS value, not the RHS result.
1036                        let left_val = self.eval_in(lhs, env)?;
1037                        let _ = self.eval_pipe(lhs, rhs, env);
1038                        return Ok(left_val);
1039                    }
1040                    BinaryOp::ExpoPipe => {
1041                        // %$% — expose LHS names to RHS (like with())
1042                        let left_val = self.eval_in(lhs, env)?;
1043                        let child_env =
1044                            crate::interpreter::environment::Environment::new_child(env);
1045                        // Expose list/data.frame columns as bindings
1046                        if let RValue::List(list) = &left_val {
1047                            for (name, val) in &list.values {
1048                                if let Some(n) = name {
1049                                    child_env.set(n.clone(), val.clone());
1050                                }
1051                            }
1052                        }
1053                        return self.eval_in(rhs, &child_env);
1054                    }
1055                    // Short-circuit && and || — don't evaluate RHS if LHS determines result
1056                    BinaryOp::AndScalar => {
1057                        let left = self.eval_in(lhs, env)?;
1058                        let a = left.as_vector().and_then(|v| v.as_logical_scalar());
1059                        if a == Some(false) {
1060                            return Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())));
1061                        }
1062                        let right = self.eval_in(rhs, env)?;
1063                        let b = right.as_vector().and_then(|v| v.as_logical_scalar());
1064                        return match (a, b) {
1065                            (Some(true), Some(true)) => {
1066                                Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
1067                            }
1068                            (Some(false), _) | (_, Some(false)) => {
1069                                Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
1070                            }
1071                            _ => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
1072                        };
1073                    }
1074                    BinaryOp::OrScalar => {
1075                        let left = self.eval_in(lhs, env)?;
1076                        let a = left.as_vector().and_then(|v| v.as_logical_scalar());
1077                        if a == Some(true) {
1078                            return Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())));
1079                        }
1080                        let right = self.eval_in(rhs, env)?;
1081                        let b = right.as_vector().and_then(|v| v.as_logical_scalar());
1082                        return match (a, b) {
1083                            (Some(true), _) | (_, Some(true)) => {
1084                                Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
1085                            }
1086                            (Some(false), Some(false)) => {
1087                                Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
1088                            }
1089                            _ => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
1090                        };
1091                    }
1092                    _ => {}
1093                }
1094                let left = self.eval_in(lhs, env)?;
1095                let right = self.eval_in(rhs, env)?;
1096                // User-defined %op% operators: look up the function and call it
1097                if let BinaryOp::Special(SpecialOp::Other(ref name)) = op {
1098                    if let Some(func) = env.get(name) {
1099                        return self.call_function(&func, &[left, right], &[], env);
1100                    }
1101                    return Err(RError::new(
1102                        RErrorKind::Other,
1103                        format!("could not find function \"{}\"", name),
1104                    )
1105                    .into());
1106                }
1107                self.eval_binary(op.clone(), &left, &right, env)
1108            }
1109            Expr::Assign { op, target, value } => {
1110                let val = self.eval_in(value, env)?;
1111                self.eval_assign(op, target, val, env)
1112            }
1113
1114            Expr::Call {
1115                func, args, span, ..
1116            } => self.eval_call(func, args, *span, env),
1117            Expr::Index { object, indices } => self.eval_index(object, indices, env),
1118            Expr::IndexDouble { object, indices } => self.eval_index_double(object, indices, env),
1119            Expr::Dollar { object, member } => self.eval_dollar(object, member, env),
1120            Expr::Slot { object, member } => self.eval_dollar(object, member, env), // treat like $
1121            Expr::NsGet { namespace, name } => self.eval_ns_get(namespace, name, env),
1122            Expr::NsGetInt { namespace, name } => self.eval_ns_get(namespace, name, env),
1123
1124            Expr::Formula { .. } => Ok(formula_value(expr.clone(), env)),
1125
1126            Expr::If {
1127                condition,
1128                then_body,
1129                else_body,
1130            } => {
1131                trace!("eval if");
1132                self.eval_if(condition, then_body, else_body.as_deref(), env)
1133            }
1134
1135            Expr::For { var, iter, body } => {
1136                trace!(var = var.as_str(), "eval for");
1137                let iter_val = self.eval_in(iter, env)?;
1138                self.eval_for(var, &iter_val, body, env)
1139            }
1140
1141            Expr::While { condition, body } => {
1142                trace!("eval while");
1143                self.eval_while(condition, body, env)
1144            }
1145
1146            Expr::Repeat { body } => self.eval_repeat(body, env),
1147
1148            Expr::Break => Err(RFlow::Signal(RSignal::Break)),
1149            Expr::Next => Err(RFlow::Signal(RSignal::Next)),
1150            Expr::Return(val) => {
1151                let ret_val = match val {
1152                    Some(expr) => self.eval_in(expr, env)?,
1153                    None => RValue::Null,
1154                };
1155                Err(RFlow::Signal(RSignal::Return(ret_val)))
1156            }
1157
1158            Expr::Block(exprs) => {
1159                let mut result = RValue::Null;
1160                for expr in exprs {
1161                    self.check_interrupt()?;
1162                    result = self.eval_in(expr, env)?;
1163                }
1164                Ok(result)
1165            }
1166
1167            Expr::Function { params, body } => Ok(RValue::Function(RFunction::Closure {
1168                params: params.clone(),
1169                body: (**body).clone(),
1170                env: env.clone(),
1171            })),
1172
1173            Expr::Program(exprs) => {
1174                let mut result = RValue::Null;
1175                for expr in exprs {
1176                    self.check_interrupt()?;
1177                    result = self.eval_in(expr, env)?;
1178                }
1179                Ok(result)
1180            }
1181        }
1182    }
1183}