Skip to main content

extendr_api/wrapper/
environment.rs

1use super::*;
2use extendr_ffi::{
3    get_parent_env, get_var_in_frame, R_BaseEnv, R_BindingIsLocked, R_EmptyEnv,
4    R_EnvironmentIsLocked, R_GetCurrentEnv, R_GlobalEnv, R_existsVarInFrame, R_lsInternal3,
5    R_removeVarFromFrame, Rboolean, Rf_defineVar,
6};
7#[derive(PartialEq, Clone)]
8pub struct Environment {
9    pub(crate) robj: Robj,
10}
11
12impl Environment {
13    /// Create a new, empty environment.
14    /// ```
15    /// use extendr_api::prelude::*;
16    /// test! {
17    ///     let env = Environment::new_with_parent(Environment::global());
18    ///     assert_eq!(env.len(), 0);
19    /// }
20    /// ```
21    pub fn new_with_parent(parent: Environment) -> Self {
22        // 14 is a reasonable default.
23        Environment::new_with_capacity(parent, 14)
24    }
25
26    /// Create a new, empty environment with a reserved size.
27    ///
28    /// This function will guess the hash table size if required.
29    /// Use the Env{} wrapper for more detail.
30    /// ```
31    /// use extendr_api::prelude::*;
32    /// test! {
33    ///     let env = Environment::new_with_capacity(Environment::global(), 5);
34    ///     env.set_local(sym!(a), 1);
35    ///     env.set_local(sym!(b), 2);
36    ///     assert_eq!(env.len(), 2);
37    /// }
38    /// ```
39    pub fn new_with_capacity(parent: Environment, capacity: usize) -> Self {
40        if capacity <= 5 {
41            // Unhashed envirnment
42            new_env(parent, false, 0)
43        } else {
44            // Hashed environment for larger hashmaps.
45            new_env(parent, true, capacity as i32 * 2 + 1)
46        }
47    }
48
49    /// Make an R environment object.
50    /// ```
51    /// use extendr_api::prelude::*;
52    /// use std::convert::TryInto;
53    /// test! {
54    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
55    ///     let mut env = Environment::from_pairs(Environment::global(), names_and_values);
56    ///     assert_eq!(env.len(), 100);
57    /// }
58    /// ```
59    #[allow(clippy::wrong_self_convention)]
60    pub fn from_pairs<NV>(parent: Environment, names_and_values: NV) -> Self
61    where
62        NV: IntoIterator,
63        NV::Item: SymPair,
64    {
65        single_threaded(|| {
66            let dict_len = 29;
67            let env = new_env(parent, true, dict_len);
68            for nv in names_and_values {
69                let (n, v) = nv.sym_pair();
70                if let Some(n) = n {
71                    unsafe { Rf_defineVar(n.get(), v.get(), env.get()) }
72                }
73            }
74            env
75        })
76    }
77
78    /// Get the current evaluation environment.
79    pub fn current() -> Self {
80        unsafe { Robj::from_sexp(R_GetCurrentEnv()).try_into().unwrap() }
81    }
82
83    /// Get the global environment.
84    pub fn global() -> Self {
85        unsafe { Robj::from_sexp(R_GlobalEnv).try_into().unwrap() }
86    }
87
88    /// Get the base environment.
89    pub fn base() -> Self {
90        unsafe { Robj::from_sexp(R_BaseEnv).try_into().unwrap() }
91    }
92
93    /// Get the empty environment.
94    pub fn empty() -> Self {
95        unsafe { Robj::from_sexp(R_EmptyEnv).try_into().unwrap() }
96    }
97
98    /// Get the call associated with this environment.
99    /// Mimics rlang's `frame_call()`: evaluates `sys.frames()` in `self`, finds the matching
100    /// frame index, then returns `sys.call(i)`.
101    /// Returns `None` if not called from a function frame or if the call cannot be determined.
102    pub fn call(&self) -> Option<Robj> {
103        use crate::robj::Eval;
104        // Evaluate sys.frames() in self to get the call stack visible from that frame
105        // sys.frames() returns a pairlist of all active frames
106        let frames = lang!("sys.frames").eval_with_env(self).ok()?;
107        let frames_list = frames.as_pairlist()?;
108        // Find the index (1-based) of self in the frames list
109        for (i, frame) in frames_list.values().enumerate() {
110            let frame_env: Environment = match frame.try_into() {
111                Ok(e) => e,
112                Err(_) => continue,
113            };
114            if frame_env == *self {
115                let idx = (i + 1) as i32;
116                return lang!("sys.call", idx).eval_with_env(self).ok();
117            }
118        }
119        None
120    }
121
122    /// Get the caller's environment (i.e. `parent.frame()`).
123    pub fn caller() -> Self {
124        lang!("parent.frame")
125            .eval_with_env(&Environment::current())
126            .ok()
127            .and_then(|r| r.try_into().ok())
128            .unwrap_or_else(Environment::global)
129    }
130
131    /// Get the enclosing (parent) environment.
132    pub fn parent(&self) -> Option<Environment> {
133        unsafe {
134            let sexp = self.robj.get();
135            let robj = Robj::from_sexp(get_parent_env(sexp));
136            robj.try_into().ok()
137        }
138    }
139
140    #[cfg(feature = "non-api")]
141    /// Set the enclosing (parent) environment.
142    pub fn set_parent(&mut self, parent: Environment) -> &mut Self {
143        single_threaded(|| unsafe {
144            let sexp = self.robj.get_mut();
145            extendr_ffi::SET_ENCLOS(sexp, parent.robj.get());
146        });
147        self
148    }
149
150    #[cfg(feature = "non-api")]
151    /// Get the environment flags.
152    pub fn envflags(&self) -> i32 {
153        unsafe {
154            let sexp = self.robj.get();
155            extendr_ffi::ENVFLAGS(sexp)
156        }
157    }
158
159    #[cfg(feature = "non-api")]
160    /// Set the environment flags.
161    /// # Safety
162    ///
163    /// Setting environment flag uses R's C API and is inherently unsafe.
164    pub unsafe fn set_envflags(&mut self, flags: i32) -> &mut Self {
165        single_threaded(|| unsafe {
166            let sexp = self.robj.get_mut();
167            extendr_ffi::SET_ENVFLAGS(sexp, flags);
168        });
169        self
170    }
171
172    #[cfg(feature = "non-api")]
173    /// Iterate over an environment.
174    pub fn iter(&self) -> EnvIter {
175        unsafe {
176            let hashtab = Robj::from_sexp(extendr_ffi::HASHTAB(self.get()));
177            let frame = Robj::from_sexp(extendr_ffi::FRAME(self.get()));
178            if hashtab.is_null() && frame.is_pairlist() {
179                EnvIter {
180                    hash_table: ListIter::new(),
181                    pairlist: frame.as_pairlist().unwrap().iter(),
182                }
183            } else {
184                EnvIter {
185                    hash_table: hashtab.as_list().unwrap().values(),
186                    pairlist: PairlistIter::new(),
187                }
188            }
189        }
190    }
191
192    /// Get the names in an environment, sorted.
193    /// ```
194    /// use extendr_api::prelude::*;
195    /// test! {
196    ///    let names_and_values : std::collections::HashMap<_, _> = (0..4).map(|i| (format!("n{}", i), r!(i))).collect();
197    ///    let env = Environment::from_pairs(Environment::global(), names_and_values);
198    ///    assert_eq!(env.names().collect::<Vec<_>>(), vec!["n0", "n1", "n2", "n3"]);
199    /// }
200    /// ```
201    pub fn names(&self) -> impl Iterator<Item = &'static str> {
202        let names =
203            unsafe { Robj::from_sexp(R_lsInternal3(self.get(), Rboolean::TRUE, Rboolean::TRUE)) };
204        names.as_str_iter().into_iter().flatten()
205    }
206
207    /// An alias for [`Environment::names`].
208    pub fn keys(&self) -> impl Iterator<Item = &'static str> {
209        self.names()
210    }
211
212    /// Get the values in an environment, ordered by name.
213    /// ```
214    /// use extendr_api::prelude::*;
215    /// test! {
216    ///    let env = Environment::from_pairs(Environment::global(), vec![("b", r!(2)), ("a", r!(1))]);
217    ///    assert_eq!(env.values().collect::<Vec<_>>(), vec![r!(1), r!(2)]);
218    /// }
219    /// ```
220    pub fn values(&self) -> impl Iterator<Item = Robj> + '_ {
221        self.names()
222            .filter_map(move |name| get_in_frame(self, &Symbol::from_string(name)))
223    }
224
225    /// Returns `true` if the environment, but not its ancestors, has a binding for `key`.
226    /// ```
227    /// use extendr_api::prelude::*;
228    /// test! {
229    ///     let mut env = Environment::new_with_parent(Environment::global());
230    ///     env.insert("x", 1)?;
231    ///     assert!(env.contains_key("x"));
232    ///     assert!(!env.contains_key("y"));
233    /// }
234    /// ```
235    pub fn contains_key<K: AsRef<str>>(&self, key: K) -> bool {
236        let key = Symbol::from_string(key);
237        unsafe { R_existsVarInFrame(self.get(), key.get()) != Rboolean::FALSE }
238    }
239
240    /// Bind `value` to `key` in the environment, returning the previous value if there was one.
241    ///
242    /// Fails if the binding is locked, or if the environment is locked and `key` is new.
243    /// ```
244    /// use extendr_api::prelude::*;
245    /// test! {
246    ///     let mut env = Environment::new_with_parent(Environment::global());
247    ///     assert_eq!(env.insert("x", "harry"), Ok(None));
248    ///     assert_eq!(env.insert("x", "fred"), Ok(Some(r!("harry"))));
249    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
250    /// }
251    /// ```
252    pub fn insert<K: AsRef<str>, V: Into<Robj>>(
253        &mut self,
254        key: K,
255        value: V,
256    ) -> Result<Option<Robj>> {
257        let key = Symbol::from_string(key);
258        let value = value.into();
259        let old = get_in_frame(self, &key);
260        unsafe {
261            if old.is_some() && R_BindingIsLocked(key.get(), self.get()) != Rboolean::FALSE {
262                return Err(Error::Other(format!(
263                    "cannot change value of locked binding for '{}'",
264                    key.as_str()
265                )));
266            }
267            if old.is_none() && R_EnvironmentIsLocked(self.get()) != Rboolean::FALSE {
268                return Err(Error::Other(format!(
269                    "cannot add binding of '{}' to a locked environment",
270                    key.as_str()
271                )));
272            }
273            Rf_defineVar(key.get(), value.get(), self.get());
274        }
275        Ok(old)
276    }
277
278    /// Remove `key` from the environment, returning its value if there was one.
279    ///
280    /// Fails if the environment is locked.
281    /// ```
282    /// use extendr_api::prelude::*;
283    /// test! {
284    ///     let mut env = Environment::new_with_parent(Environment::global());
285    ///     env.insert("x", 1)?;
286    ///     assert_eq!(env.remove("x"), Ok(Some(r!(1))));
287    ///     assert_eq!(env.remove("x"), Ok(None));
288    /// }
289    /// ```
290    pub fn remove<K: AsRef<str>>(&mut self, key: K) -> Result<Option<Robj>> {
291        let key = Symbol::from_string(key);
292        let Some(old) = get_in_frame(self, &key) else {
293            return Ok(None);
294        };
295        unsafe {
296            if R_EnvironmentIsLocked(self.get()) != Rboolean::FALSE {
297                return Err(Error::Other(format!(
298                    "cannot remove '{}' from a locked environment",
299                    key.as_str()
300                )));
301            }
302            R_removeVarFromFrame(key.get(), self.get());
303        }
304        Ok(Some(old))
305    }
306
307    /// Set or define a variable in an environment.
308    /// ```
309    /// use extendr_api::prelude::*;
310    /// test! {
311    ///     let env = Environment::new_with_parent(Environment::global());
312    ///     env.set_local(sym!(x), "harry");
313    ///     env.set_local(sym!(x), "fred");
314    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
315    /// }
316    /// ```
317    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
318        let key = key.into();
319        let value = value.into();
320        if key.is_symbol() {
321            single_threaded(|| unsafe {
322                Rf_defineVar(key.get(), value.get(), self.get());
323            })
324        }
325    }
326
327    /// Get a variable from an environment, but not its ancestors.
328    /// ```
329    /// use extendr_api::prelude::*;
330    /// test! {
331    ///     let env = Environment::new_with_parent(Environment::global());
332    ///     env.set_local(sym!(x), "fred");
333    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
334    /// }
335    /// ```
336    pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
337        let key = key.into();
338        if key.is_symbol() {
339            unsafe { Ok(Robj::from_sexp(get_var_in_frame(self.get(), key.get()))) }
340        } else {
341            Err(Error::NotFound(key))
342        }
343    }
344}
345
346fn get_in_frame(env: &Environment, key: &Symbol) -> Option<Robj> {
347    unsafe {
348        if R_existsVarInFrame(env.get(), key.get()) == Rboolean::FALSE {
349            return None;
350        }
351        Some(Robj::from_sexp(get_var_in_frame(env.get(), key.get())))
352    }
353}
354
355/// Iterator over the names and values of an environment
356///
357#[derive(Clone)]
358pub struct EnvIter {
359    hash_table: ListIter,
360    pairlist: PairlistIter,
361}
362
363impl Iterator for EnvIter {
364    type Item = (&'static str, Robj);
365
366    fn next(&mut self) -> Option<Self::Item> {
367        loop {
368            // Environments are a hash table (list) or pair lists (pairlist)
369            // Get the first available value from the pair list.
370            for (key, value) in &mut self.pairlist {
371                // if the key and value are valid, return a pair.
372                #[cfg(not(r_4_5))]
373                if key.is_na() || value.is_unbound_value() {
374                    continue;
375                }
376                #[cfg(r_4_5)]
377                if key.is_na() {
378                    continue;
379                }
380                return Some((key, value));
381            }
382
383            // Get the first pairlist from the hash table.
384            loop {
385                // The hash table is empty, end of iteration.
386                let obj = self.hash_table.next()?;
387                if !obj.is_null() && obj.is_pairlist() {
388                    self.pairlist = obj.as_pairlist().unwrap().iter();
389                    break;
390                }
391                // continue hash table loop.
392            }
393        }
394    }
395}
396
397impl std::fmt::Debug for Environment {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        unsafe {
400            let sexp = self.get();
401            if sexp == R_GlobalEnv {
402                write!(f, "Environment::global()")
403            } else if sexp == R_BaseEnv {
404                write!(f, "Environment::base()")
405            } else if sexp == R_EmptyEnv {
406                write!(f, "Environment::empty()")
407            } else {
408                write!(f, "{}", self.deparse().unwrap())
409            }
410        }
411    }
412}