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}