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    /// # Safety
107    ///
108    /// Setting environment flag uses R's C API and is inherently unsafe.
109    pub unsafe fn set_envflags(&mut self, flags: i32) -> &mut Self {
110        single_threaded(|| unsafe {
111            let sexp = self.robj.get_mut();
112            extendr_ffi::SET_ENVFLAGS(sexp, flags);
113        });
114        self
115    }
116
117    #[cfg(feature = "non-api")]
118    /// Iterate over an environment.
119    pub fn iter(&self) -> EnvIter {
120        unsafe {
121            let hashtab = Robj::from_sexp(extendr_ffi::HASHTAB(self.get()));
122            let frame = Robj::from_sexp(extendr_ffi::FRAME(self.get()));
123            if hashtab.is_null() && frame.is_pairlist() {
124                EnvIter {
125                    hash_table: ListIter::new(),
126                    pairlist: frame.as_pairlist().unwrap().iter(),
127                }
128            } else {
129                EnvIter {
130                    hash_table: hashtab.as_list().unwrap().values(),
131                    pairlist: PairlistIter::new(),
132                }
133            }
134        }
135    }
136
137    #[cfg(feature = "non-api")]
138    /// Get the names in an environment.
139    /// ```
140    /// use extendr_api::prelude::*;
141    /// test! {
142    ///    let names_and_values : std::collections::HashMap<_, _> = (0..4).map(|i| (format!("n{}", i), r!(i))).collect();
143    ///    let env = Environment::from_pairs(global_env(), names_and_values);
144    ///    assert_eq!(env.names().collect::<Vec<_>>(), vec!["n0", "n1", "n2", "n3"]);
145    /// }
146    /// ```
147    pub fn names(&self) -> impl Iterator<Item = &str> {
148        self.iter().map(|(k, _)| k)
149    }
150
151    /// Set or define a variable in an environment.
152    /// ```
153    /// use extendr_api::prelude::*;
154    /// test! {
155    ///     let env = Environment::new_with_parent(global_env());
156    ///     env.set_local(sym!(x), "harry");
157    ///     env.set_local(sym!(x), "fred");
158    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
159    /// }
160    /// ```
161    pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
162        let key = key.into();
163        let value = value.into();
164        if key.is_symbol() {
165            single_threaded(|| unsafe {
166                Rf_defineVar(key.get(), value.get(), self.get());
167            })
168        }
169    }
170
171    /// Get a variable from an environment, but not its ancestors.
172    /// ```
173    /// use extendr_api::prelude::*;
174    /// test! {
175    ///     let env = Environment::new_with_parent(global_env());
176    ///     env.set_local(sym!(x), "fred");
177    ///     assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
178    /// }
179    /// ```
180    pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
181        let key = key.into();
182        if key.is_symbol() {
183            unsafe { Ok(Robj::from_sexp(get_var_in_frame(self.get(), key.get()))) }
184        } else {
185            Err(Error::NotFound(key))
186        }
187    }
188}
189
190/// Iterator over the names and values of an environment
191///
192#[derive(Clone)]
193pub struct EnvIter {
194    hash_table: ListIter,
195    pairlist: PairlistIter,
196}
197
198impl Iterator for EnvIter {
199    type Item = (&'static str, Robj);
200
201    fn next(&mut self) -> Option<Self::Item> {
202        loop {
203            // Environments are a hash table (list) or pair lists (pairlist)
204            // Get the first available value from the pair list.
205            for (key, value) in &mut self.pairlist {
206                // if the key and value are valid, return a pair.
207                if !key.is_na() && !value.is_unbound_value() {
208                    return Some((key, value));
209                }
210            }
211
212            // Get the first pairlist from the hash table.
213            loop {
214                if let Some(obj) = self.hash_table.next() {
215                    if !obj.is_null() && obj.is_pairlist() {
216                        self.pairlist = obj.as_pairlist().unwrap().iter();
217                        break;
218                    }
219                // continue hash table loop.
220                } else {
221                    // The hash table is empty, end of iteration.
222                    return None;
223                }
224            }
225        }
226    }
227}
228
229impl std::fmt::Debug for Environment {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        unsafe {
232            let sexp = self.get();
233            if sexp == R_GlobalEnv {
234                write!(f, "global_env()")
235            } else if sexp == R_BaseEnv {
236                write!(f, "base_env()")
237            } else if sexp == R_EmptyEnv {
238                write!(f, "empty_env()")
239            } else {
240                write!(f, "{}", self.deparse().unwrap())
241            }
242        }
243    }
244}