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}