Skip to main content

aorist_extendr_api/wrapper/
environment.rs

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