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_EmptyEnv, R_GetCurrentEnv, R_GlobalEnv,
4    Rf_defineVar,
5};
6#[derive(PartialEq, Clone)]
7pub struct Environment {
8    pub(crate) robj: Robj,
9}
10
11impl Environment {
12    /// Create a new, empty environment.
13    /// ```
14    /// use extendr_api::prelude::*;
15    /// test! {
16    ///     let env = Environment::new_with_parent(Environment::global());
17    ///     assert_eq!(env.len(), 0);
18    /// }
19    /// ```
20    pub fn new_with_parent(parent: Environment) -> Self {
21        // 14 is a reasonable default.
22        Environment::new_with_capacity(parent, 14)
23    }
24
25    /// Create a new, empty environment with a reserved size.
26    ///
27    /// This function will guess the hash table size if required.
28    /// Use the Env{} wrapper for more detail.
29    /// ```
30    /// use extendr_api::prelude::*;
31    /// test! {
32    ///     let env = Environment::new_with_capacity(Environment::global(), 5);
33    ///     env.set_local(sym!(a), 1);
34    ///     env.set_local(sym!(b), 2);
35    ///     assert_eq!(env.len(), 2);
36    /// }
37    /// ```
38    pub fn new_with_capacity(parent: Environment, capacity: usize) -> Self {
39        if capacity <= 5 {
40            // Unhashed envirnment
41            new_env(parent, false, 0)
42        } else {
43            // Hashed environment for larger hashmaps.
44            new_env(parent, true, capacity as i32 * 2 + 1)
45        }
46    }
47
48    /// Make an R environment object.
49    /// ```
50    /// use extendr_api::prelude::*;
51    /// use std::convert::TryInto;
52    /// test! {
53    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
54    ///     let mut env = Environment::from_pairs(Environment::global(), names_and_values);
55    ///     assert_eq!(env.len(), 100);
56    /// }
57    /// ```
58    #[allow(clippy::wrong_self_convention)]
59    pub fn from_pairs<NV>(parent: Environment, names_and_values: NV) -> Self
60    where
61        NV: IntoIterator,
62        NV::Item: SymPair,
63    {
64        single_threaded(|| {
65            let dict_len = 29;
66            let env = new_env(parent, true, dict_len);
67            for nv in names_and_values {
68                let (n, v) = nv.sym_pair();
69                if let Some(n) = n {
70                    unsafe { Rf_defineVar(n.get(), v.get(), env.get()) }
71                }
72            }
73            env
74        })
75    }
76
77    /// Get the current evaluation environment.
78    pub fn current() -> Self {
79        unsafe { Robj::from_sexp(R_GetCurrentEnv()).try_into().unwrap() }
80    }
81
82    /// Get the global environment.
83    pub fn global() -> Self {
84        unsafe { Robj::from_sexp(R_GlobalEnv).try_into().unwrap() }
85    }
86
87    /// Get the base environment.
88    pub fn base() -> Self {
89        unsafe { Robj::from_sexp(R_BaseEnv).try_into().unwrap() }
90    }
91
92    /// Get the empty environment.
93    pub fn empty() -> Self {
94        unsafe { Robj::from_sexp(R_EmptyEnv).try_into().unwrap() }
95    }
96
97    /// Get the call associated with this environment.
98    /// Mimics rlang's `frame_call()`: evaluates `sys.frames()` in `self`, finds the matching
99    /// frame index, then returns `sys.call(i)`.
100    /// Returns `None` if not called from a function frame or if the call cannot be determined.
101    pub fn call(&self) -> Option<Robj> {
102        use crate::robj::Eval;
103        // Evaluate sys.frames() in self to get the call stack visible from that frame
104        // sys.frames() returns a pairlist of all active frames
105        let frames = lang!("sys.frames").eval_with_env(self).ok()?;
106        let frames_list = frames.as_pairlist()?;
107        // Find the index (1-based) of self in the frames list
108        for (i, frame) in frames_list.values().enumerate() {
109            let frame_env: Environment = match frame.try_into() {
110                Ok(e) => e,
111                Err(_) => continue,
112            };
113            if frame_env == *self {
114                let idx = (i + 1) as i32;
115                return lang!("sys.call", idx).eval_with_env(self).ok();
116            }
117        }
118        None
119    }
120
121    /// Get the caller's environment (i.e. `parent.frame()`).
122    pub fn caller() -> Self {
123        lang!("parent.frame")
124            .eval_with_env(&Environment::current())
125            .ok()
126            .and_then(|r| r.try_into().ok())
127            .unwrap_or_else(Environment::global)
128    }
129
130    /// Get the enclosing (parent) environment.
131    pub fn parent(&self) -> Option<Environment> {
132        unsafe {
133            let sexp = self.robj.get();
134            let robj = Robj::from_sexp(get_parent_env(sexp));
135            robj.try_into().ok()
136        }
137    }
138
139    #[cfg(feature = "non-api")]
140    /// Set the enclosing (parent) environment.
141    pub fn set_parent(&mut self, parent: Environment) -> &mut Self {
142        single_threaded(|| unsafe {
143            let sexp = self.robj.get_mut();
144            extendr_ffi::SET_ENCLOS(sexp, parent.robj.get());
145        });
146        self
147    }
148
149    #[cfg(feature = "non-api")]
150    /// Get the environment flags.
151    pub fn envflags(&self) -> i32 {
152        unsafe {
153            let sexp = self.robj.get();
154            extendr_ffi::ENVFLAGS(sexp)
155        }
156    }
157
158    #[cfg(feature = "non-api")]
159    /// Set the environment flags.
160    /// # Safety
161    ///
162    /// Setting environment flag uses R's C API and is inherently unsafe.
163    pub unsafe fn set_envflags(&mut self, flags: i32) -> &mut Self {
164        single_threaded(|| unsafe {
165            let sexp = self.robj.get_mut();
166            extendr_ffi::SET_ENVFLAGS(sexp, flags);
167        });
168        self
169    }
170
171    #[cfg(feature = "non-api")]
172    /// Iterate over an environment.
173    pub fn iter(&self) -> EnvIter {
174        unsafe {
175            let hashtab = Robj::from_sexp(extendr_ffi::HASHTAB(self.get()));
176            let frame = Robj::from_sexp(extendr_ffi::FRAME(self.get()));
177            if hashtab.is_null() && frame.is_pairlist() {
178                EnvIter {
179                    hash_table: ListIter::new(),
180                    pairlist: frame.as_pairlist().unwrap().iter(),
181                }
182            } else {
183                EnvIter {
184                    hash_table: hashtab.as_list().unwrap().values(),
185                    pairlist: PairlistIter::new(),
186                }
187            }
188        }
189    }
190
191    #[cfg(feature = "non-api")]
192    /// Get the names in an environment.
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 = &str> {
202        self.iter().map(|(k, _)| k)
203    }
204
205    /// Set or define a variable in an environment.
206    /// ```
207    /// use extendr_api::prelude::*;
208    /// test! {
209    ///     let env = Environment::new_with_parent(Environment::global());
210    ///     env.set_local(sym!(x), "harry");
211    ///     env.set_local(sym!(x), "fred");
212    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
213    /// }
214    /// ```
215    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
216        let key = key.into();
217        let value = value.into();
218        if key.is_symbol() {
219            single_threaded(|| unsafe {
220                Rf_defineVar(key.get(), value.get(), self.get());
221            })
222        }
223    }
224
225    /// Get a variable from an environment, but not its ancestors.
226    /// ```
227    /// use extendr_api::prelude::*;
228    /// test! {
229    ///     let env = Environment::new_with_parent(Environment::global());
230    ///     env.set_local(sym!(x), "fred");
231    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
232    /// }
233    /// ```
234    pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
235        let key = key.into();
236        if key.is_symbol() {
237            unsafe { Ok(Robj::from_sexp(get_var_in_frame(self.get(), key.get()))) }
238        } else {
239            Err(Error::NotFound(key))
240        }
241    }
242}
243
244/// Iterator over the names and values of an environment
245///
246#[derive(Clone)]
247pub struct EnvIter {
248    hash_table: ListIter,
249    pairlist: PairlistIter,
250}
251
252impl Iterator for EnvIter {
253    type Item = (&'static str, Robj);
254
255    fn next(&mut self) -> Option<Self::Item> {
256        loop {
257            // Environments are a hash table (list) or pair lists (pairlist)
258            // Get the first available value from the pair list.
259            for (key, value) in &mut self.pairlist {
260                // if the key and value are valid, return a pair.
261                #[cfg(not(r_4_5))]
262                if key.is_na() || value.is_unbound_value() {
263                    continue;
264                }
265                #[cfg(r_4_5)]
266                if key.is_na() {
267                    continue;
268                }
269                return Some((key, value));
270            }
271
272            // Get the first pairlist from the hash table.
273            loop {
274                if let Some(obj) = self.hash_table.next() {
275                    if !obj.is_null() && obj.is_pairlist() {
276                        self.pairlist = obj.as_pairlist().unwrap().iter();
277                        break;
278                    }
279                // continue hash table loop.
280                } else {
281                    // The hash table is empty, end of iteration.
282                    return None;
283                }
284            }
285        }
286    }
287}
288
289impl std::fmt::Debug for Environment {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        unsafe {
292            let sexp = self.get();
293            if sexp == R_GlobalEnv {
294                write!(f, "Environment::global()")
295            } else if sexp == R_BaseEnv {
296                write!(f, "Environment::base()")
297            } else if sexp == R_EmptyEnv {
298                write!(f, "Environment::empty()")
299            } else {
300                write!(f, "{}", self.deparse().unwrap())
301            }
302        }
303    }
304}