extendr_api/wrapper/
environment.rs

1use super::*;
2use extendr_ffi::{
3    get_parent_env, get_var_in_frame, R_BaseEnv, R_EmptyEnv, R_GlobalEnv, Rf_defineVar,
4};
5#[derive(PartialEq, Clone)]
6pub struct Environment {
7    pub(crate) robj: Robj,
8}
9
10impl Environment {
11    /// Create a new, empty environment.
12    /// ```
13    /// use extendr_api::prelude::*;
14    /// test! {
15    ///     let env = Environment::new_with_parent(global_env());
16    ///     assert_eq!(env.len(), 0);
17    /// }
18    /// ```
19    pub fn new_with_parent(parent: Environment) -> Self {
20        // 14 is a reasonable default.
21        Environment::new_with_capacity(parent, 14)
22    }
23
24    /// Create a new, empty environment with a reserved size.
25    ///
26    /// This function will guess the hash table size if required.
27    /// Use the Env{} wrapper for more detail.
28    /// ```
29    /// use extendr_api::prelude::*;
30    /// test! {
31    ///     let env = Environment::new_with_capacity(global_env(), 5);
32    ///     env.set_local(sym!(a), 1);
33    ///     env.set_local(sym!(b), 2);
34    ///     assert_eq!(env.len(), 2);
35    /// }
36    /// ```
37    pub fn new_with_capacity(parent: Environment, capacity: usize) -> Self {
38        if capacity <= 5 {
39            // Unhashed envirnment
40            new_env(parent, false, 0)
41        } else {
42            // Hashed environment for larger hashmaps.
43            new_env(parent, true, capacity as i32 * 2 + 1)
44        }
45    }
46
47    /// Make an R environment object.
48    /// ```
49    /// use extendr_api::prelude::*;
50    /// use std::convert::TryInto;
51    /// test! {
52    ///     let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
53    ///     let mut env = Environment::from_pairs(global_env(), names_and_values);
54    ///     assert_eq!(env.len(), 100);
55    /// }
56    /// ```
57    #[allow(clippy::wrong_self_convention)]
58    pub fn from_pairs<NV>(parent: Environment, names_and_values: NV) -> Self
59    where
60        NV: IntoIterator,
61        NV::Item: SymPair,
62    {
63        single_threaded(|| {
64            let dict_len = 29;
65            let env = new_env(parent, true, dict_len);
66            for nv in names_and_values {
67                let (n, v) = nv.sym_pair();
68                if let Some(n) = n {
69                    unsafe { Rf_defineVar(n.get(), v.get(), env.get()) }
70                }
71            }
72            env
73        })
74    }
75
76    /// Get the enclosing (parent) environment.
77    pub fn parent(&self) -> Option<Environment> {
78        unsafe {
79            let sexp = self.robj.get();
80            let robj = Robj::from_sexp(get_parent_env(sexp));
81            robj.try_into().ok()
82        }
83    }
84
85    #[cfg(feature = "non-api")]
86    /// Set the enclosing (parent) environment.
87    pub fn set_parent(&mut self, parent: Environment) -> &mut Self {
88        single_threaded(|| unsafe {
89            let sexp = self.robj.get_mut();
90            extendr_ffi::SET_ENCLOS(sexp, parent.robj.get());
91        });
92        self
93    }
94
95    #[cfg(feature = "non-api")]
96    /// Get the environment flags.
97    pub fn envflags(&self) -> i32 {
98        unsafe {
99            let sexp = self.robj.get();
100            extendr_ffi::ENVFLAGS(sexp)
101        }
102    }
103
104    #[cfg(feature = "non-api")]
105    /// Set the environment flags.
106    pub unsafe fn set_envflags(&mut self, flags: i32) -> &mut Self {
107        single_threaded(|| unsafe {
108            let sexp = self.robj.get_mut();
109            extendr_ffi::SET_ENVFLAGS(sexp, flags);
110        });
111        self
112    }
113
114    #[cfg(feature = "non-api")]
115    /// Iterate over an environment.
116    pub fn iter(&self) -> EnvIter {
117        unsafe {
118            let hashtab = Robj::from_sexp(extendr_ffi::HASHTAB(self.get()));
119            let frame = Robj::from_sexp(extendr_ffi::FRAME(self.get()));
120            if hashtab.is_null() && frame.is_pairlist() {
121                EnvIter {
122                    hash_table: ListIter::new(),
123                    pairlist: frame.as_pairlist().unwrap().iter(),
124                }
125            } else {
126                EnvIter {
127                    hash_table: hashtab.as_list().unwrap().values(),
128                    pairlist: PairlistIter::new(),
129                }
130            }
131        }
132    }
133
134    #[cfg(feature = "non-api")]
135    /// Get the names in an environment.
136    /// ```
137    /// use extendr_api::prelude::*;
138    /// test! {
139    ///    let names_and_values : std::collections::HashMap<_, _> = (0..4).map(|i| (format!("n{}", i), r!(i))).collect();
140    ///    let env = Environment::from_pairs(global_env(), names_and_values);
141    ///    assert_eq!(env.names().collect::<Vec<_>>(), vec!["n0", "n1", "n2", "n3"]);
142    /// }
143    /// ```
144    pub fn names(&self) -> impl Iterator<Item = &str> {
145        self.iter().map(|(k, _)| k)
146    }
147
148    /// Set or define a variable in an environment.
149    /// ```
150    /// use extendr_api::prelude::*;
151    /// test! {
152    ///     let env = Environment::new_with_parent(global_env());
153    ///     env.set_local(sym!(x), "harry");
154    ///     env.set_local(sym!(x), "fred");
155    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
156    /// }
157    /// ```
158    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
159        let key = key.into();
160        let value = value.into();
161        if key.is_symbol() {
162            single_threaded(|| unsafe {
163                Rf_defineVar(key.get(), value.get(), self.get());
164            })
165        }
166    }
167
168    /// Get a variable from an environment, but not its ancestors.
169    /// ```
170    /// use extendr_api::prelude::*;
171    /// test! {
172    ///     let env = Environment::new_with_parent(global_env());
173    ///     env.set_local(sym!(x), "fred");
174    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
175    /// }
176    /// ```
177    pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
178        let key = key.into();
179        if key.is_symbol() {
180            unsafe { Ok(Robj::from_sexp(get_var_in_frame(self.get(), key.get()))) }
181        } else {
182            Err(Error::NotFound(key))
183        }
184    }
185}
186
187/// Iterator over the names and values of an environment
188///
189#[derive(Clone)]
190pub struct EnvIter {
191    hash_table: ListIter,
192    pairlist: PairlistIter,
193}
194
195impl Iterator for EnvIter {
196    type Item = (&'static str, Robj);
197
198    fn next(&mut self) -> Option<Self::Item> {
199        loop {
200            // Environments are a hash table (list) or pair lists (pairlist)
201            // Get the first available value from the pair list.
202            for (key, value) in &mut self.pairlist {
203                // if the key and value are valid, return a pair.
204                if !key.is_na() && !value.is_unbound_value() {
205                    return Some((key, value));
206                }
207            }
208
209            // Get the first pairlist from the hash table.
210            loop {
211                if let Some(obj) = self.hash_table.next() {
212                    if !obj.is_null() && obj.is_pairlist() {
213                        self.pairlist = obj.as_pairlist().unwrap().iter();
214                        break;
215                    }
216                // continue hash table loop.
217                } else {
218                    // The hash table is empty, end of iteration.
219                    return None;
220                }
221            }
222        }
223    }
224}
225
226impl std::fmt::Debug for Environment {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        unsafe {
229            let sexp = self.get();
230            if sexp == R_GlobalEnv {
231                write!(f, "global_env()")
232            } else if sexp == R_BaseEnv {
233                write!(f, "base_env()")
234            } else if sexp == R_EmptyEnv {
235                write!(f, "empty_env()")
236            } else {
237                write!(f, "{}", self.deparse().unwrap())
238            }
239        }
240    }
241}