conch_runtime_pshaw/env/
func.rs

1use crate::env::SubEnvironment;
2use std::collections::HashMap;
3use std::fmt;
4use std::hash::Hash;
5use std::sync::Arc;
6
7/// An interface for setting and getting shell functions.
8pub trait FunctionEnvironment {
9    /// The name to be associated with a function.
10    type FnName;
11    /// The type of the function.
12    type Fn;
13
14    /// Get a particularly named function if it was registered.
15    fn function(&self, name: &Self::FnName) -> Option<&Self::Fn>;
16    /// Register a shell function with a given name.
17    fn set_function(&mut self, name: Self::FnName, func: Self::Fn);
18
19    /// Check if a particularly named function was registered.
20    fn has_function(&self, name: &Self::FnName) -> bool {
21        self.function(name).is_some()
22    }
23}
24
25impl<'a, T: ?Sized + FunctionEnvironment> FunctionEnvironment for &'a mut T {
26    type FnName = T::FnName;
27    type Fn = T::Fn;
28
29    fn function(&self, name: &Self::FnName) -> Option<&Self::Fn> {
30        (**self).function(name)
31    }
32
33    fn set_function(&mut self, name: Self::FnName, func: Self::Fn) {
34        (**self).set_function(name, func);
35    }
36
37    fn has_function(&self, name: &Self::FnName) -> bool {
38        (**self).has_function(name)
39    }
40}
41
42/// An interface for unsetting shell functions.
43pub trait UnsetFunctionEnvironment: FunctionEnvironment {
44    /// Removes the definition of a function if it was registered.
45    fn unset_function(&mut self, name: &Self::FnName);
46}
47
48impl<'a, T: ?Sized + UnsetFunctionEnvironment> UnsetFunctionEnvironment for &'a mut T {
49    fn unset_function(&mut self, name: &Self::FnName) {
50        (**self).unset_function(name);
51    }
52}
53
54/// An interface for tracking the current stack of functions being executed.
55pub trait FunctionFrameEnvironment {
56    /// Denote that a new function has been invoked and is currently executing.
57    fn push_fn_frame(&mut self);
58    /// Denote that a function has completed and is no longer executing.
59    fn pop_fn_frame(&mut self);
60    /// Determines if there is at least one function being currently executed.
61    fn is_fn_running(&self) -> bool;
62}
63
64impl<'a, T: ?Sized + FunctionFrameEnvironment> FunctionFrameEnvironment for &'a mut T {
65    fn push_fn_frame(&mut self) {
66        (**self).push_fn_frame()
67    }
68
69    fn pop_fn_frame(&mut self) {
70        (**self).pop_fn_frame()
71    }
72
73    fn is_fn_running(&self) -> bool {
74        (**self).is_fn_running()
75    }
76}
77
78/// An implementation of `FunctionFrameEnvironment`
79#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80pub struct FnFrameEnv {
81    num_frames: usize,
82}
83
84impl FnFrameEnv {
85    /// Create a new environment instance.
86    pub fn new() -> Self {
87        Self { num_frames: 0 }
88    }
89}
90
91impl FunctionFrameEnvironment for FnFrameEnv {
92    /// Denote that a new function has been invoked and is currently executing.
93    ///
94    /// # Panics
95    ///
96    /// Panics if the number of pushed frames overflows a `usize`.
97    fn push_fn_frame(&mut self) {
98        self.num_frames = self
99            .num_frames
100            .checked_add(1)
101            .expect("function frame overflow");
102    }
103
104    fn pop_fn_frame(&mut self) {
105        self.num_frames = self.num_frames.saturating_sub(1);
106    }
107
108    fn is_fn_running(&self) -> bool {
109        self.num_frames > 0
110    }
111}
112
113impl SubEnvironment for FnFrameEnv {
114    fn sub_env(&self) -> Self {
115        *self
116    }
117}
118
119/// An environment module for setting and getting shell functions.
120#[derive(PartialEq, Eq)]
121pub struct FnEnv<N: Hash + Eq, F> {
122    functions: Arc<HashMap<N, F>>,
123}
124
125impl<N: Hash + Eq, F> FnEnv<N, F> {
126    /// Constructs a new `FnEnv` with no defined functions.
127    pub fn new() -> Self {
128        Self {
129            functions: HashMap::new().into(),
130        }
131    }
132
133    pub(crate) fn fn_names(&self) -> ::std::collections::hash_map::Keys<'_, N, F> {
134        self.functions.keys()
135    }
136}
137
138impl<N, F> fmt::Debug for FnEnv<N, F>
139where
140    N: Hash + Eq + fmt::Debug + Ord,
141    F: fmt::Debug,
142{
143    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
144        use std::collections::BTreeMap;
145        use std::iter::FromIterator;
146
147        fmt.debug_struct(stringify!(FnEnv))
148            .field("functions", &BTreeMap::from_iter(self.functions.iter()))
149            .finish()
150    }
151}
152
153impl<N: Hash + Eq, F> Default for FnEnv<N, F> {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl<N: Hash + Eq, F> Clone for FnEnv<N, F> {
160    fn clone(&self) -> Self {
161        Self {
162            functions: self.functions.clone(),
163        }
164    }
165}
166
167impl<N: Hash + Eq, F> SubEnvironment for FnEnv<N, F> {
168    fn sub_env(&self) -> Self {
169        self.clone()
170    }
171}
172
173impl<N, F> FunctionEnvironment for FnEnv<N, F>
174where
175    N: Clone + Hash + Eq,
176    F: Clone,
177{
178    type FnName = N;
179    type Fn = F;
180
181    fn function(&self, name: &Self::FnName) -> Option<&Self::Fn> {
182        self.functions.get(name)
183    }
184
185    fn set_function(&mut self, name: Self::FnName, func: Self::Fn) {
186        Arc::make_mut(&mut self.functions).insert(name, func);
187    }
188}
189
190impl<N, F> UnsetFunctionEnvironment for FnEnv<N, F>
191where
192    N: Clone + Hash + Eq,
193    F: Clone,
194{
195    fn unset_function(&mut self, name: &Self::FnName) {
196        if self.has_function(name) {
197            Arc::make_mut(&mut self.functions).remove(name);
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::env::SubEnvironment;
206    use crate::RefCounted;
207
208    #[test]
209    fn test_set_get_unset_function() {
210        let name = "var";
211        let func = "some func";
212        let mut env = FnEnv::new();
213        assert_eq!(env.function(&name), None);
214        env.set_function(name, func);
215        assert_eq!(env.function(&name), Some(&func));
216        env.unset_function(&name);
217        assert_eq!(env.function(&name), None);
218    }
219
220    #[test]
221    fn test_sub_env_no_needless_clone() {
222        let not_set = "not set";
223        let name = "var";
224        let func = "some function";
225        let mut env = FnEnv::new();
226        env.set_function(name, func);
227
228        let mut env = env.sub_env();
229
230        env.unset_function(&not_set);
231        if env.functions.get_mut().is_some() {
232            panic!("needles clone!");
233        }
234    }
235
236    #[test]
237    fn test_set_function_in_parent_visible_in_child() {
238        let fn_name = "foo";
239        let func = 42;
240        let mut parent = FnEnv::new();
241        parent.set_function(fn_name, func);
242
243        {
244            let child = parent.sub_env();
245            assert_eq!(child.has_function(&fn_name), true);
246            assert_eq!(child.function(&fn_name), Some(&func));
247        }
248    }
249
250    #[test]
251    fn test_set_and_unset_function_in_child_should_not_affect_parent() {
252        let fn_name_parent = "parent fn";
253        let fn_name_child = "child fn";
254
255        let fn_parent = 42;
256        let fn_child = 5;
257
258        let mut parent = FnEnv::new();
259        parent.set_function(fn_name_parent, fn_parent);
260
261        {
262            let mut child = parent.sub_env();
263            child.set_function(fn_name_parent, fn_child);
264            child.set_function(fn_name_child, fn_child);
265
266            assert_eq!(child.has_function(&fn_name_parent), true);
267            assert_eq!(child.has_function(&fn_name_child), true);
268            assert_eq!(child.function(&fn_name_parent), Some(&fn_child));
269            assert_eq!(child.function(&fn_name_child), Some(&fn_child));
270        }
271
272        assert_eq!(parent.has_function(&fn_name_parent), true);
273        assert_eq!(parent.has_function(&fn_name_child), false);
274        assert_eq!(parent.function(&fn_name_parent), Some(&fn_parent));
275        assert_eq!(parent.function(&fn_name_child), None);
276    }
277
278    #[test]
279    fn test_fn_frame_smoke() {
280        let mut env = FnFrameEnv::new();
281        assert_eq!(env.is_fn_running(), false);
282
283        // Extra pops don't do anything
284        env.pop_fn_frame();
285        assert_eq!(env.is_fn_running(), false);
286
287        env.push_fn_frame();
288        assert_eq!(env.is_fn_running(), true);
289
290        env.push_fn_frame();
291        assert_eq!(env.is_fn_running(), true);
292
293        env.pop_fn_frame();
294        assert_eq!(env.is_fn_running(), true);
295
296        env.pop_fn_frame();
297        assert_eq!(env.is_fn_running(), false);
298    }
299
300    #[test]
301    #[should_panic(expected = "function frame overflow")]
302    fn test_fn_frame_overflow() {
303        let mut env = FnFrameEnv::new();
304        env.num_frames = usize::max_value();
305
306        env.push_fn_frame();
307    }
308}