Skip to main content

miniextendr_api/
expression.rs

1//! Safe wrappers for R expression evaluation.
2//!
3//! This module provides ergonomic types for building and evaluating R function
4//! calls from Rust, handling GC protection and error propagation automatically.
5//!
6//! # Types
7//!
8//! | Type | Purpose |
9//! |------|---------|
10//! | [`RSymbol`] | Interned R symbol (SYMSXP) |
11//! | [`RCall`] | Builder for R function calls (LANGSXP) |
12//! | [`REnv`] | Well-known R environments |
13//!
14//! # Example
15//!
16//! ```ignore
17//! use miniextendr_api::expression::{RCall, REnv};
18//!
19//! unsafe {
20//!     // Call paste0("hello", " world") in the base environment
21//!     let result = RCall::new("paste0")
22//!         .arg(Rf_mkString(c"hello".as_ptr()))
23//!         .arg(Rf_mkString(c" world".as_ptr()))
24//!         .eval(REnv::base().as_sexp())?;
25//! }
26//! ```
27
28use crate::gc_protect::{OwnedProtect, ProtectScope};
29use crate::sexp_ext::PairListExt;
30use crate::sys::{
31    self, ParseStatus, R_BaseEnv, R_EmptyEnv, R_GlobalEnv, R_ParseVector, R_tryEvalSilent,
32    Rf_install,
33};
34use crate::{SEXP, SexpExt};
35use std::ffi::{CStr, CString};
36
37// region: RSymbol
38
39/// A safe wrapper around R symbols (SYMSXP).
40///
41/// R symbols are interned strings used as variable and function names.
42/// They are never garbage collected, so `RSymbol` does not need GC protection.
43///
44/// # Example
45///
46/// ```ignore
47/// let sym = RSymbol::new("paste0");
48/// // sym.as_sexp() is a SYMSXP that can be used in call construction
49/// ```
50pub struct RSymbol {
51    sexp: SEXP,
52}
53
54impl RSymbol {
55    /// Create or retrieve an interned R symbol.
56    ///
57    /// # Safety
58    ///
59    /// Must be called from the R main thread.
60    ///
61    /// # Panics
62    ///
63    /// Panics if `name` contains a null byte.
64    #[inline]
65    pub unsafe fn new(name: &str) -> Self {
66        let c_name = CString::new(name).expect("symbol name must not contain null bytes");
67        RSymbol {
68            sexp: unsafe { Rf_install(c_name.as_ptr()) },
69        }
70    }
71
72    /// Create a symbol from a C string literal.
73    ///
74    /// This avoids the allocation needed by [`new`](Self::new) when you have
75    /// a `&CStr` available (e.g., from `c"name"` literals).
76    ///
77    /// # Safety
78    ///
79    /// Must be called from the R main thread.
80    #[inline]
81    pub unsafe fn from_cstr(name: &CStr) -> Self {
82        RSymbol {
83            sexp: unsafe { Rf_install(name.as_ptr()) },
84        }
85    }
86
87    /// Get the underlying SEXP.
88    #[inline]
89    pub fn as_sexp(&self) -> SEXP {
90        self.sexp
91    }
92}
93// endregion
94
95// region: REnv
96
97/// Handle to a well-known R environment.
98///
99/// Provides access to R's standard environments without raw FFI calls.
100pub struct REnv {
101    sexp: SEXP,
102}
103
104impl REnv {
105    /// The global environment (`R_GlobalEnv`).
106    ///
107    /// # Safety
108    ///
109    /// Must be called from the R main thread.
110    #[inline]
111    pub unsafe fn global() -> Self {
112        REnv {
113            sexp: unsafe { R_GlobalEnv },
114        }
115    }
116
117    /// The base environment (`R_BaseEnv`).
118    ///
119    /// # Safety
120    ///
121    /// Must be called from the R main thread.
122    #[inline]
123    pub unsafe fn base() -> Self {
124        REnv {
125            sexp: unsafe { R_BaseEnv },
126        }
127    }
128
129    /// The empty environment (`R_EmptyEnv`).
130    ///
131    /// # Safety
132    ///
133    /// Must be called from the R main thread.
134    #[inline]
135    pub unsafe fn empty() -> Self {
136        REnv {
137            sexp: unsafe { R_EmptyEnv },
138        }
139    }
140
141    /// The base namespace (`SEXP::base_namespace()`).
142    ///
143    /// Unlike [`base()`](Self::base) which is the base *environment* (exported
144    /// functions visible to users), this is the base *namespace* (includes
145    /// internal helpers). Rarely needed — prefer [`base()`](Self::base) unless
146    /// you specifically need unexported base internals.
147    ///
148    /// # Safety
149    ///
150    /// Must be called from the R main thread.
151    #[inline]
152    pub fn base_namespace() -> Self {
153        REnv {
154            sexp: SEXP::base_namespace(),
155        }
156    }
157
158    /// A package's namespace environment.
159    ///
160    /// Finds the namespace for a loaded package. Use this to evaluate functions
161    /// that live in a specific package (e.g., `slot()` from `methods`).
162    ///
163    /// This is a safe wrapper around `R_FindNamespace` — it uses
164    /// `R_tryEvalSilent` internally so that a missing namespace returns
165    /// `Err` instead of longjmping through Rust frames.
166    ///
167    /// # Safety
168    ///
169    /// Must be called from the R main thread.
170    ///
171    /// # Errors
172    ///
173    /// Returns `Err` if the package namespace is not found (package not loaded).
174    pub unsafe fn package_namespace(name: &str) -> Result<Self, String> {
175        unsafe {
176            let name_sexp = OwnedProtect::new(SEXP::scalar_string_from_str(name));
177            RCall::new("getNamespace")
178                .arg(name_sexp.get())
179                .eval(R_BaseEnv)
180                .map(|sexp| REnv { sexp })
181        }
182    }
183
184    /// The current execution environment.
185    ///
186    /// Returns the environment of the innermost active closure on R's call
187    /// stack, or the global environment if no closure is active.
188    ///
189    /// Useful when you need to evaluate an expression in the caller's context
190    /// rather than a fixed well-known environment.
191    ///
192    /// # Safety
193    ///
194    /// Must be called from the R main thread.
195    #[inline]
196    pub unsafe fn caller() -> Self {
197        REnv {
198            sexp: unsafe { sys::R_GetCurrentEnv() },
199        }
200    }
201
202    /// Wrap an arbitrary environment SEXP.
203    ///
204    /// # Safety
205    ///
206    /// `sexp` must be a valid ENVSXP.
207    #[inline]
208    pub unsafe fn from_sexp(sexp: SEXP) -> Self {
209        REnv { sexp }
210    }
211
212    /// Get the underlying SEXP.
213    #[inline]
214    pub fn as_sexp(&self) -> SEXP {
215        self.sexp
216    }
217}
218// endregion
219
220// region: RCall
221
222/// Builder for constructing and evaluating R function calls.
223///
224/// `RCall` constructs a LANGSXP (R language object) from a function name or
225/// SEXP and a sequence of arguments (optionally named). It handles GC
226/// protection during construction and evaluation.
227///
228/// # Example
229///
230/// ```ignore
231/// use miniextendr_api::expression::RCall;
232/// use miniextendr_api::sys;
233///
234/// unsafe {
235///     // seq_len(10)
236///     let result = RCall::new("seq_len")
237///         .arg(SEXP::scalar_integer(10))
238///         .eval_base()?;
239///
240///     // paste(x, collapse = ", ")
241///     let result = RCall::new("paste")
242///         .arg(some_sexp)
243///         .named_arg("collapse", sys::Rf_mkString(c", ".as_ptr()))
244///         .eval_base()?;
245/// }
246/// ```
247pub struct RCall {
248    /// Function symbol or SEXP.
249    fun: SEXP,
250    /// Arguments as (optional_name, value) pairs.
251    args: Vec<(Option<CString>, SEXP)>,
252}
253
254impl RCall {
255    /// Start building a call to a named R function.
256    ///
257    /// The function is looked up via `Rf_install`, which returns an interned symbol.
258    ///
259    /// # Safety
260    ///
261    /// Must be called from the R main thread.
262    ///
263    /// # Panics
264    ///
265    /// Panics if `fun_name` contains a null byte.
266    #[inline]
267    pub unsafe fn new(fun_name: &str) -> Self {
268        let c_name = CString::new(fun_name).expect("function name must not contain null bytes");
269        RCall {
270            fun: unsafe { Rf_install(c_name.as_ptr()) },
271            args: Vec::new(),
272        }
273    }
274
275    /// Start building a call to a function given as a C string literal.
276    ///
277    /// More efficient than [`new`](Self::new) when a `&CStr` is available.
278    ///
279    /// # Safety
280    ///
281    /// Must be called from the R main thread.
282    #[inline]
283    pub unsafe fn from_cstr(fun_name: &CStr) -> Self {
284        RCall {
285            fun: unsafe { Rf_install(fun_name.as_ptr()) },
286            args: Vec::new(),
287        }
288    }
289
290    /// Start building a call with a function SEXP (closure, builtin, etc.).
291    ///
292    /// # Safety
293    ///
294    /// `fun` must be a valid SEXP representing a callable R object.
295    #[inline]
296    pub unsafe fn from_sexp(fun: SEXP) -> Self {
297        RCall {
298            fun,
299            args: Vec::new(),
300        }
301    }
302
303    /// Add a positional argument.
304    #[inline]
305    pub fn arg(mut self, value: SEXP) -> Self {
306        self.args.push((None, value));
307        self
308    }
309
310    /// Add a named argument.
311    ///
312    /// # Panics
313    ///
314    /// Panics if `name` contains a null byte.
315    #[inline]
316    pub fn named_arg(mut self, name: &str, value: SEXP) -> Self {
317        let c_name = CString::new(name).expect("argument name must not contain null bytes");
318        self.args.push((Some(c_name), value));
319        self
320    }
321
322    /// Build the LANGSXP without evaluating it.
323    ///
324    /// The returned SEXP is **unprotected**. The caller must protect it if
325    /// further allocations will occur before use.
326    ///
327    /// # Safety
328    ///
329    /// Must be called from the R main thread. All argument SEXPs must still
330    /// be valid (protected or otherwise reachable by R's GC).
331    pub unsafe fn build(&self) -> SEXP {
332        unsafe {
333            // Build the argument pairlist from back to front using Rf_cons.
334            // ProtectScope tracks all intermediate cons cells and the final
335            // LANGSXP head, then unprotects them all on drop. The returned
336            // call is unprotected — caller protects it if needed.
337            let scope = ProtectScope::new();
338
339            let mut tail = SEXP::nil();
340            for (name, value) in self.args.iter().rev() {
341                tail = scope.protect_raw(value.cons(tail));
342                if let Some(c_name) = name {
343                    tail.set_tag(Rf_install(c_name.as_ptr()));
344                }
345            }
346
347            // Prepend the function as LANGSXP head.
348            // ProtectScope drops here; the call is unprotected on return
349            // (callers re-protect via OwnedProtect before invoking eval).
350            scope.protect_raw(self.fun.lcons(tail))
351        }
352    }
353
354    /// Evaluate the call in the given environment.
355    ///
356    /// Uses `R_tryEvalSilent` so that R errors are captured as `Err(String)`
357    /// rather than causing a longjmp through Rust frames.
358    ///
359    /// # Safety
360    ///
361    /// - Must be called from the R main thread.
362    /// - `env` must be a valid ENVSXP.
363    /// - All argument SEXPs must still be valid.
364    ///
365    /// # Returns
366    ///
367    /// - `Ok(SEXP)` with the result (unprotected — caller should protect if needed)
368    /// - `Err(String)` with the R error message on failure
369    pub unsafe fn eval(&self, env: SEXP) -> Result<SEXP, String> {
370        unsafe {
371            let call = OwnedProtect::new(self.build());
372
373            let mut error_occurred: std::os::raw::c_int = 0;
374            let result = R_tryEvalSilent(call.get(), env, &mut error_occurred);
375
376            if error_occurred != 0 {
377                Err(get_r_error_message())
378            } else {
379                Ok(result)
380            }
381        }
382    }
383
384    /// Evaluate in `R_BaseEnv`.
385    ///
386    /// # Safety
387    ///
388    /// Same as [`eval`](Self::eval).
389    #[inline]
390    pub unsafe fn eval_base(&self) -> Result<SEXP, String> {
391        unsafe { self.eval(R_BaseEnv) }
392    }
393
394    /// Start building a namespaced call: `pkg::fun(args…)`.
395    ///
396    /// Looks up `pkg::fun_name` in the base environment and uses the resolved
397    /// function closure as the call target. This respects R's namespace lookup
398    /// rules (exported + non-exported via `::` / `:::`).
399    ///
400    /// This is the runtime counterpart of the lowered `pkg::fn(args…)` form
401    /// in `r!(pkg::fn(args…))`.
402    ///
403    /// # Safety
404    ///
405    /// Must be called from the R main thread.
406    ///
407    /// # Panics
408    ///
409    /// Panics if `pkg` or `fun_name` contain a null byte.
410    ///
411    /// # Errors
412    ///
413    /// Returns `Err(String)` if the namespace lookup fails (package not loaded
414    /// or function not found).
415    pub unsafe fn namespaced(pkg: &str, fun_name: &str) -> Result<Self, String> {
416        unsafe {
417            // Build (:: pkg fun_name) as a LANGSXP and evaluate it to get the closure.
418            let ns_op = Rf_install(c"::".as_ptr());
419            let pkg_sym = {
420                let c = CString::new(pkg).expect("pkg name must not contain null bytes");
421                Rf_install(c.as_ptr())
422            };
423            let fun_sym = {
424                let c = CString::new(fun_name).expect("fun name must not contain null bytes");
425                Rf_install(c.as_ptr())
426            };
427
428            // Build `(:: pkg fun_name)` pairlist from back to front.
429            let scope = crate::gc_protect::ProtectScope::new();
430            let nil = crate::sys::R_NilValue;
431            let fun_cons = scope.protect_raw(fun_sym.cons(nil));
432            let pkg_cons = scope.protect_raw(pkg_sym.cons(fun_cons));
433            let ns_call = scope.protect_raw(ns_op.lcons(pkg_cons));
434
435            // Evaluate to resolve the function closure.
436            let mut err: std::os::raw::c_int = 0;
437            let fun_sexp = R_tryEvalSilent(ns_call, R_BaseEnv, &mut err);
438            if err != 0 {
439                return Err(get_r_error_message());
440            }
441
442            // fun_sexp is the closure; it is reachable via R_GlobalEnv bindings,
443            // so we don't need explicit protection for the RCall builder lifetime.
444            Ok(RCall {
445                fun: fun_sexp,
446                args: Vec::new(),
447            })
448        }
449    }
450}
451
452/// Build and evaluate `target$name` — the R `$` extraction operator.
453///
454/// This is a convenience wrapper that avoids hand-rolling
455/// `Rf_install("$") + Rf_lang3(...) + R_tryEvalSilent(...)` ladders.
456/// Equivalent to:
457///
458/// ```ignore
459/// RCall::new("$")
460///     .arg(target)
461///     .arg(SEXP::scalar_string_from_str(name))
462///     .eval_base()
463/// ```
464///
465/// but uses the more direct LANGSXP form internally and protects all
466/// intermediate allocations via RAII.
467///
468/// # Safety
469///
470/// - Must be called from the R main thread.
471/// - `target` must be a valid SEXP (typically a list, environment, or S4
472///   object that supports `$` extraction).
473///
474/// # Returns
475///
476/// - `Ok(SEXP)` with the extracted value (unprotected — caller should protect if needed).
477/// - `Err(String)` with the R error message if `$` extraction fails or the
478///   evaluation errors.
479pub unsafe fn dollar_extract(target: SEXP, name: &str) -> Result<SEXP, String> {
480    unsafe {
481        let name_sexp = OwnedProtect::new(SEXP::scalar_string_from_str(name));
482        RCall::new("$").arg(target).arg(name_sexp.get()).eval_base()
483    }
484}
485// endregion
486
487// region: r_eval_str (runtime string parse + eval)
488
489/// Parse a string of R source and evaluate it in `env`.
490///
491/// This is the runtime workhorse behind the [`r_str!`](crate::r_str) and
492/// [`r!`](crate::r) macros. It performs the full
493/// `R_ParseVector` → check status → `Rf_eval` ladder with correct GC
494/// protection on every intermediate SEXP, so callers never have to hand-roll
495/// `OwnedProtect` around the parse tree.
496///
497/// Only the **last** top-level expression's value is returned (matching R's
498/// `eval(parse(text = ...))` semantics): each parsed expression is evaluated in
499/// order so that side effects (assignments, `library()`, …) take effect, and
500/// the value of the final one is returned. An empty / whitespace-only string
501/// yields `R_NilValue`.
502///
503/// # Safety
504///
505/// - Must be called from (or routed to) the R main thread. The parse and eval
506///   FFI calls go through the checked `#[r_ffi_checked]` variants, which
507///   serialize onto the R thread via `with_r_thread`, so calling from a
508///   worker thread is sound — but the returned SEXP must not outlive the R
509///   session.
510/// - `env` must be a valid ENVSXP.
511///
512/// # Returns
513///
514/// - `Ok(SEXP)` with the value of the last expression (**unprotected** — the
515///   caller should protect it if further allocations will occur before use).
516/// - `Err(String)` if parsing fails (syntax error / incomplete input) or if
517///   evaluation raises an R error. The error is captured via
518///   `R_tryEvalSilent` + `geterrmessage()`, so it never longjmps through Rust
519///   frames.
520///
521/// # Example
522///
523/// ```ignore
524/// use miniextendr_api::expression::r_eval_str;
525/// use miniextendr_api::sys::R_GlobalEnv;
526///
527/// unsafe {
528///     let three = r_eval_str("1L + 2L", R_GlobalEnv)?;
529///     // three is an INTSXP holding 3
530/// }
531/// ```
532pub unsafe fn r_eval_str(code: &str, env: SEXP) -> Result<SEXP, String> {
533    unsafe {
534        // 1. Wrap the source in a length-1 STRSXP. scalar_string_from_str
535        //    allocates a CHARSXP + STRSXP; protect it across the parse, which
536        //    allocates again.
537        let code_sexp = OwnedProtect::new(SEXP::scalar_string_from_str(code));
538
539        // 2. Parse. R_ParseVector returns an EXPRSXP (a vector of expressions).
540        //    Protect it across the subsequent VECTOR_ELT / Rf_eval allocations.
541        let mut status = ParseStatus::PARSE_NULL;
542        let parsed = R_ParseVector(code_sexp.get(), -1, &mut status, sys::R_NilValue);
543
544        match status {
545            ParseStatus::PARSE_OK => {}
546            ParseStatus::PARSE_INCOMPLETE => {
547                return Err(format!(
548                    "incomplete R expression (unbalanced delimiter?): {code}"
549                ));
550            }
551            ParseStatus::PARSE_ERROR => {
552                return Err(format!("R syntax error while parsing: {code}"));
553            }
554            ParseStatus::PARSE_EOF => {
555                return Err(format!("unexpected end of input while parsing: {code}"));
556            }
557            ParseStatus::PARSE_NULL => {
558                return Err(format!("R_ParseVector returned PARSE_NULL for: {code}"));
559            }
560        }
561
562        let parsed = OwnedProtect::new(parsed);
563
564        // 3. Evaluate each parsed expression in order; return the value of the
565        //    last one. An empty EXPRSXP (blank source) yields R_NilValue.
566        let n = parsed.get().xlength();
567        let mut result = sys::R_NilValue;
568        for i in 0..n {
569            // VECTOR_ELT borrows from `parsed` (still protected). The element
570            // is part of the protected EXPRSXP, so it stays reachable.
571            let expr = parsed.get().vector_elt(i);
572
573            let mut error_occurred: std::os::raw::c_int = 0;
574            result = R_tryEvalSilent(expr, env, &mut error_occurred);
575            if error_occurred != 0 {
576                return Err(get_r_error_message());
577            }
578        }
579
580        Ok(result)
581    }
582}
583
584/// Parse and evaluate a string of R source in `R_GlobalEnv`.
585///
586/// Convenience wrapper over [`r_eval_str`] for the common case. See that
587/// function for safety and return semantics.
588///
589/// # Safety
590///
591/// Same as [`r_eval_str`].
592#[inline]
593pub unsafe fn r_eval_str_global(code: &str) -> Result<SEXP, String> {
594    unsafe { r_eval_str(code, R_GlobalEnv) }
595}
596// endregion
597
598// region: Error message extraction
599
600/// Extract the most recent R error message.
601///
602/// Uses `geterrmessage()` which is public R API (unlike `R_curErrorBuf`
603/// which is non-API). Falls back to a generic message if extraction fails.
604unsafe fn get_r_error_message() -> String {
605    unsafe {
606        // Call geterrmessage() — a public R function that returns the last
607        // error message as a character(1) string.
608        let call = OwnedProtect::new(sys::Rf_lang1(Rf_install(c"geterrmessage".as_ptr())));
609
610        let mut err: std::os::raw::c_int = 0;
611        let msg_sexp = R_tryEvalSilent(call.get(), R_BaseEnv, &mut err);
612
613        if err != 0 || msg_sexp.is_null() {
614            return "R error occurred (could not retrieve message)".to_string();
615        }
616
617        let _msg_guard = OwnedProtect::new(msg_sexp);
618
619        // geterrmessage() returns character(1)
620        if msg_sexp.xlength() > 0 {
621            let charsxp = msg_sexp.string_elt(0);
622            if let Some(msg) = crate::from_r::charsxp_to_string_lossy(charsxp) {
623                return msg.trim_end().to_string();
624            }
625        }
626        "R error occurred".to_string()
627    }
628}
629// endregion
630
631// region: Tests
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636
637    // These tests verify compilation and basic invariants.
638    // Full integration tests require the R runtime.
639
640    #[test]
641    fn rcall_arg_accumulation() {
642        // Verify the builder pattern accumulates args correctly.
643        // We can't call R functions without an R runtime, but we can
644        // check that the Vec grows as expected.
645        let call = RCall {
646            fun: SEXP(std::ptr::null_mut()),
647            args: Vec::new(),
648        };
649        let call = call
650            .arg(SEXP(std::ptr::null_mut()))
651            .arg(SEXP(std::ptr::null_mut()));
652        assert_eq!(call.args.len(), 2);
653        assert!(call.args[0].0.is_none());
654        assert!(call.args[1].0.is_none());
655    }
656
657    #[test]
658    fn rcall_named_arg() {
659        let call = RCall {
660            fun: SEXP(std::ptr::null_mut()),
661            args: Vec::new(),
662        };
663        let call = call.named_arg("collapse", SEXP(std::ptr::null_mut()));
664        assert_eq!(call.args.len(), 1);
665        assert_eq!(
666            call.args[0].0.as_ref().unwrap(),
667            &CString::new("collapse").unwrap()
668        );
669    }
670
671    #[test]
672    fn renv_types_are_sized() {
673        // Just verify types compile and are sized
674        fn assert_sized<T: Sized>() {}
675        assert_sized::<RSymbol>();
676        assert_sized::<RCall>();
677        assert_sized::<REnv>();
678    }
679
680    #[test]
681    fn renv_constructors_compile() {
682        // Verify all REnv constructor signatures compile.
683        // Actual testing requires the R runtime.
684        fn assert_env_fn<F: FnOnce() -> REnv>(_f: F) {}
685        fn assert_env_result_fn<F: FnOnce() -> Result<REnv, String>>(_f: F) {}
686
687        assert_env_fn(|| unsafe { REnv::global() });
688        assert_env_fn(|| unsafe { REnv::base() });
689        assert_env_fn(|| unsafe { REnv::empty() });
690        assert_env_fn(REnv::base_namespace);
691        assert_env_fn(|| unsafe { REnv::caller() });
692        assert_env_result_fn(|| unsafe { REnv::package_namespace("base") });
693    }
694}
695// endregion