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}