extendr_api/wrapper/environment.rs
1use super::*;
2use extendr_ffi::{
3 get_parent_env, get_var_in_frame, R_BaseEnv, R_BindingIsLocked, R_EmptyEnv,
4 R_EnvironmentIsLocked, R_GetCurrentEnv, R_GlobalEnv, R_existsVarInFrame, R_lsInternal3,
5 R_removeVarFromFrame, Rboolean, Rf_defineVar,
6};
7#[derive(PartialEq, Clone)]
8pub struct Environment {
9 pub(crate) robj: Robj,
10}
11
12impl Environment {
13 /// Create a new, empty environment.
14 /// ```
15 /// use extendr_api::prelude::*;
16 /// test! {
17 /// let env = Environment::new_with_parent(Environment::global());
18 /// assert_eq!(env.len(), 0);
19 /// }
20 /// ```
21 pub fn new_with_parent(parent: Environment) -> Self {
22 // 14 is a reasonable default.
23 Environment::new_with_capacity(parent, 14)
24 }
25
26 /// Create a new, empty environment with a reserved size.
27 ///
28 /// This function will guess the hash table size if required.
29 /// Use the Env{} wrapper for more detail.
30 /// ```
31 /// use extendr_api::prelude::*;
32 /// test! {
33 /// let env = Environment::new_with_capacity(Environment::global(), 5);
34 /// env.set_local(sym!(a), 1);
35 /// env.set_local(sym!(b), 2);
36 /// assert_eq!(env.len(), 2);
37 /// }
38 /// ```
39 pub fn new_with_capacity(parent: Environment, capacity: usize) -> Self {
40 if capacity <= 5 {
41 // Unhashed envirnment
42 new_env(parent, false, 0)
43 } else {
44 // Hashed environment for larger hashmaps.
45 new_env(parent, true, capacity as i32 * 2 + 1)
46 }
47 }
48
49 /// Make an R environment object.
50 /// ```
51 /// use extendr_api::prelude::*;
52 /// use std::convert::TryInto;
53 /// test! {
54 /// let names_and_values = (0..100).map(|i| (format!("n{}", i), i));
55 /// let mut env = Environment::from_pairs(Environment::global(), names_and_values);
56 /// assert_eq!(env.len(), 100);
57 /// }
58 /// ```
59 #[allow(clippy::wrong_self_convention)]
60 pub fn from_pairs<NV>(parent: Environment, names_and_values: NV) -> Self
61 where
62 NV: IntoIterator,
63 NV::Item: SymPair,
64 {
65 single_threaded(|| {
66 let dict_len = 29;
67 let env = new_env(parent, true, dict_len);
68 for nv in names_and_values {
69 let (n, v) = nv.sym_pair();
70 if let Some(n) = n {
71 unsafe { Rf_defineVar(n.get(), v.get(), env.get()) }
72 }
73 }
74 env
75 })
76 }
77
78 /// Get the current evaluation environment.
79 pub fn current() -> Self {
80 unsafe { Robj::from_sexp(R_GetCurrentEnv()).try_into().unwrap() }
81 }
82
83 /// Get the global environment.
84 pub fn global() -> Self {
85 unsafe { Robj::from_sexp(R_GlobalEnv).try_into().unwrap() }
86 }
87
88 /// Get the base environment.
89 pub fn base() -> Self {
90 unsafe { Robj::from_sexp(R_BaseEnv).try_into().unwrap() }
91 }
92
93 /// Get the empty environment.
94 pub fn empty() -> Self {
95 unsafe { Robj::from_sexp(R_EmptyEnv).try_into().unwrap() }
96 }
97
98 /// Get the call associated with this environment.
99 /// Mimics rlang's `frame_call()`: evaluates `sys.frames()` in `self`, finds the matching
100 /// frame index, then returns `sys.call(i)`.
101 /// Returns `None` if not called from a function frame or if the call cannot be determined.
102 pub fn call(&self) -> Option<Robj> {
103 use crate::robj::Eval;
104 // Evaluate sys.frames() in self to get the call stack visible from that frame
105 // sys.frames() returns a pairlist of all active frames
106 let frames = lang!("sys.frames").eval_with_env(self).ok()?;
107 let frames_list = frames.as_pairlist()?;
108 // Find the index (1-based) of self in the frames list
109 for (i, frame) in frames_list.values().enumerate() {
110 let frame_env: Environment = match frame.try_into() {
111 Ok(e) => e,
112 Err(_) => continue,
113 };
114 if frame_env == *self {
115 let idx = (i + 1) as i32;
116 return lang!("sys.call", idx).eval_with_env(self).ok();
117 }
118 }
119 None
120 }
121
122 /// Get the caller's environment (i.e. `parent.frame()`).
123 pub fn caller() -> Self {
124 lang!("parent.frame")
125 .eval_with_env(&Environment::current())
126 .ok()
127 .and_then(|r| r.try_into().ok())
128 .unwrap_or_else(Environment::global)
129 }
130
131 /// Get the enclosing (parent) environment.
132 pub fn parent(&self) -> Option<Environment> {
133 unsafe {
134 let sexp = self.robj.get();
135 let robj = Robj::from_sexp(get_parent_env(sexp));
136 robj.try_into().ok()
137 }
138 }
139
140 #[cfg(feature = "non-api")]
141 /// Set the enclosing (parent) environment.
142 pub fn set_parent(&mut self, parent: Environment) -> &mut Self {
143 single_threaded(|| unsafe {
144 let sexp = self.robj.get_mut();
145 extendr_ffi::SET_ENCLOS(sexp, parent.robj.get());
146 });
147 self
148 }
149
150 #[cfg(feature = "non-api")]
151 /// Get the environment flags.
152 pub fn envflags(&self) -> i32 {
153 unsafe {
154 let sexp = self.robj.get();
155 extendr_ffi::ENVFLAGS(sexp)
156 }
157 }
158
159 #[cfg(feature = "non-api")]
160 /// Set the environment flags.
161 /// # Safety
162 ///
163 /// Setting environment flag uses R's C API and is inherently unsafe.
164 pub unsafe fn set_envflags(&mut self, flags: i32) -> &mut Self {
165 single_threaded(|| unsafe {
166 let sexp = self.robj.get_mut();
167 extendr_ffi::SET_ENVFLAGS(sexp, flags);
168 });
169 self
170 }
171
172 #[cfg(feature = "non-api")]
173 /// Iterate over an environment.
174 pub fn iter(&self) -> EnvIter {
175 unsafe {
176 let hashtab = Robj::from_sexp(extendr_ffi::HASHTAB(self.get()));
177 let frame = Robj::from_sexp(extendr_ffi::FRAME(self.get()));
178 if hashtab.is_null() && frame.is_pairlist() {
179 EnvIter {
180 hash_table: ListIter::new(),
181 pairlist: frame.as_pairlist().unwrap().iter(),
182 }
183 } else {
184 EnvIter {
185 hash_table: hashtab.as_list().unwrap().values(),
186 pairlist: PairlistIter::new(),
187 }
188 }
189 }
190 }
191
192 /// Get the names in an environment, sorted.
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 = &'static str> {
202 let names =
203 unsafe { Robj::from_sexp(R_lsInternal3(self.get(), Rboolean::TRUE, Rboolean::TRUE)) };
204 names.as_str_iter().into_iter().flatten()
205 }
206
207 /// An alias for [`Environment::names`].
208 pub fn keys(&self) -> impl Iterator<Item = &'static str> {
209 self.names()
210 }
211
212 /// Get the values in an environment, ordered by name.
213 /// ```
214 /// use extendr_api::prelude::*;
215 /// test! {
216 /// let env = Environment::from_pairs(Environment::global(), vec![("b", r!(2)), ("a", r!(1))]);
217 /// assert_eq!(env.values().collect::<Vec<_>>(), vec![r!(1), r!(2)]);
218 /// }
219 /// ```
220 pub fn values(&self) -> impl Iterator<Item = Robj> + '_ {
221 self.names()
222 .filter_map(move |name| get_in_frame(self, &Symbol::from_string(name)))
223 }
224
225 /// Returns `true` if the environment, but not its ancestors, has a binding for `key`.
226 /// ```
227 /// use extendr_api::prelude::*;
228 /// test! {
229 /// let mut env = Environment::new_with_parent(Environment::global());
230 /// env.insert("x", 1)?;
231 /// assert!(env.contains_key("x"));
232 /// assert!(!env.contains_key("y"));
233 /// }
234 /// ```
235 pub fn contains_key<K: AsRef<str>>(&self, key: K) -> bool {
236 let key = Symbol::from_string(key);
237 unsafe { R_existsVarInFrame(self.get(), key.get()) != Rboolean::FALSE }
238 }
239
240 /// Bind `value` to `key` in the environment, returning the previous value if there was one.
241 ///
242 /// Fails if the binding is locked, or if the environment is locked and `key` is new.
243 /// ```
244 /// use extendr_api::prelude::*;
245 /// test! {
246 /// let mut env = Environment::new_with_parent(Environment::global());
247 /// assert_eq!(env.insert("x", "harry"), Ok(None));
248 /// assert_eq!(env.insert("x", "fred"), Ok(Some(r!("harry"))));
249 /// assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
250 /// }
251 /// ```
252 pub fn insert<K: AsRef<str>, V: Into<Robj>>(
253 &mut self,
254 key: K,
255 value: V,
256 ) -> Result<Option<Robj>> {
257 let key = Symbol::from_string(key);
258 let value = value.into();
259 let old = get_in_frame(self, &key);
260 unsafe {
261 if old.is_some() && R_BindingIsLocked(key.get(), self.get()) != Rboolean::FALSE {
262 return Err(Error::Other(format!(
263 "cannot change value of locked binding for '{}'",
264 key.as_str()
265 )));
266 }
267 if old.is_none() && R_EnvironmentIsLocked(self.get()) != Rboolean::FALSE {
268 return Err(Error::Other(format!(
269 "cannot add binding of '{}' to a locked environment",
270 key.as_str()
271 )));
272 }
273 Rf_defineVar(key.get(), value.get(), self.get());
274 }
275 Ok(old)
276 }
277
278 /// Remove `key` from the environment, returning its value if there was one.
279 ///
280 /// Fails if the environment is locked.
281 /// ```
282 /// use extendr_api::prelude::*;
283 /// test! {
284 /// let mut env = Environment::new_with_parent(Environment::global());
285 /// env.insert("x", 1)?;
286 /// assert_eq!(env.remove("x"), Ok(Some(r!(1))));
287 /// assert_eq!(env.remove("x"), Ok(None));
288 /// }
289 /// ```
290 pub fn remove<K: AsRef<str>>(&mut self, key: K) -> Result<Option<Robj>> {
291 let key = Symbol::from_string(key);
292 let Some(old) = get_in_frame(self, &key) else {
293 return Ok(None);
294 };
295 unsafe {
296 if R_EnvironmentIsLocked(self.get()) != Rboolean::FALSE {
297 return Err(Error::Other(format!(
298 "cannot remove '{}' from a locked environment",
299 key.as_str()
300 )));
301 }
302 R_removeVarFromFrame(key.get(), self.get());
303 }
304 Ok(Some(old))
305 }
306
307 /// Set or define a variable in an environment.
308 /// ```
309 /// use extendr_api::prelude::*;
310 /// test! {
311 /// let env = Environment::new_with_parent(Environment::global());
312 /// env.set_local(sym!(x), "harry");
313 /// env.set_local(sym!(x), "fred");
314 /// assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
315 /// }
316 /// ```
317 pub fn set_local<K: Into<Robj>, V: Into<Robj>>(&self, key: K, value: V) {
318 let key = key.into();
319 let value = value.into();
320 if key.is_symbol() {
321 single_threaded(|| unsafe {
322 Rf_defineVar(key.get(), value.get(), self.get());
323 })
324 }
325 }
326
327 /// Get a variable from an environment, but not its ancestors.
328 /// ```
329 /// use extendr_api::prelude::*;
330 /// test! {
331 /// let env = Environment::new_with_parent(Environment::global());
332 /// env.set_local(sym!(x), "fred");
333 /// assert_eq!(env.local(sym!(x)), Ok(r!("fred")));
334 /// }
335 /// ```
336 pub fn local<K: Into<Robj>>(&self, key: K) -> Result<Robj> {
337 let key = key.into();
338 if key.is_symbol() {
339 unsafe { Ok(Robj::from_sexp(get_var_in_frame(self.get(), key.get()))) }
340 } else {
341 Err(Error::NotFound(key))
342 }
343 }
344}
345
346fn get_in_frame(env: &Environment, key: &Symbol) -> Option<Robj> {
347 unsafe {
348 if R_existsVarInFrame(env.get(), key.get()) == Rboolean::FALSE {
349 return None;
350 }
351 Some(Robj::from_sexp(get_var_in_frame(env.get(), key.get())))
352 }
353}
354
355/// Iterator over the names and values of an environment
356///
357#[derive(Clone)]
358pub struct EnvIter {
359 hash_table: ListIter,
360 pairlist: PairlistIter,
361}
362
363impl Iterator for EnvIter {
364 type Item = (&'static str, Robj);
365
366 fn next(&mut self) -> Option<Self::Item> {
367 loop {
368 // Environments are a hash table (list) or pair lists (pairlist)
369 // Get the first available value from the pair list.
370 for (key, value) in &mut self.pairlist {
371 // if the key and value are valid, return a pair.
372 #[cfg(not(r_4_5))]
373 if key.is_na() || value.is_unbound_value() {
374 continue;
375 }
376 #[cfg(r_4_5)]
377 if key.is_na() {
378 continue;
379 }
380 return Some((key, value));
381 }
382
383 // Get the first pairlist from the hash table.
384 loop {
385 // The hash table is empty, end of iteration.
386 let obj = self.hash_table.next()?;
387 if !obj.is_null() && obj.is_pairlist() {
388 self.pairlist = obj.as_pairlist().unwrap().iter();
389 break;
390 }
391 // continue hash table loop.
392 }
393 }
394 }
395}
396
397impl std::fmt::Debug for Environment {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399 unsafe {
400 let sexp = self.get();
401 if sexp == R_GlobalEnv {
402 write!(f, "Environment::global()")
403 } else if sexp == R_BaseEnv {
404 write!(f, "Environment::base()")
405 } else if sexp == R_EmptyEnv {
406 write!(f, "Environment::empty()")
407 } else {
408 write!(f, "{}", self.deparse().unwrap())
409 }
410 }
411 }
412}