bimm_contracts/
bindings.rs

1/// A trait for looking up parameters in a stack-like environment.
2pub trait StackMap<'a, V>
3where
4    V: Default + Copy,
5{
6    /// Looks up a value associated with the given key in the stack.
7    ///
8    /// ## Arguments
9    ///
10    /// * `key`: The key to look up.
11    ///
12    /// ## Returns
13    ///
14    /// An `Option<V>` containing the value if found, or `None` if not found.
15    #[must_use]
16    fn lookup(
17        &self,
18        key: &'a str,
19    ) -> Option<V>;
20
21    /// Exports the values for the given keys from the local bindings.
22    ///
23    /// ## Arguments
24    ///
25    /// * `keys`: An array of keys to look up in the local bindings.
26    ///
27    /// ## Returns
28    ///
29    /// An array of values corresponding to the keys. If a key is not found,
30    ///
31    /// ## Panics
32    ///
33    /// Panics if any key is not found in the local bindings.
34    #[must_use]
35    fn export_key_values<const K: usize>(
36        &self,
37        keys: &'a [&str; K],
38    ) -> [V; K] {
39        let mut values: [V; K] = [Default::default(); K];
40        for i in 0..K {
41            let key = keys[i];
42            values[i] = match self.lookup(key) {
43                Some(v) => v,
44                None => panic!("No value for key \"{key}\""),
45            };
46        }
47        values
48    }
49}
50
51/// Type alias for static/stack compatible bindings.
52pub type StackEnvironment<'a> = &'a [(&'a str, usize)];
53
54impl<'a> StackMap<'a, usize> for StackEnvironment<'a> {
55    #[inline]
56    fn lookup(
57        &self,
58        key: &'a str,
59    ) -> Option<usize> {
60        for &(k, v) in self.iter() {
61            if k == key {
62                return Some(v);
63            }
64        }
65        None
66    }
67}
68
69/// A trait for mutable stack-like environments that allows inserting key-value pairs.
70///
71/// Provides no support for removing keys.
72pub trait MutableStackMap<'a, V>: StackMap<'a, V>
73where
74    V: Default + Copy,
75{
76    /// Inserts a key-value pair into the stack.
77    ///
78    /// It is an error to bind a key which is already present;
79    /// but this is only checked when `debug_assertions` are on.
80    ///
81    /// ## Arguments
82    ///
83    /// * `key`: The key to insert.
84    /// * `value`: The value to associate with the key.
85    ///
86    /// ## Returns
87    ///
88    /// This method does not return a value,
89    /// but modifies the stack by inserting the key-value pair.
90    fn bind(
91        &mut self,
92        key: &'a str,
93        value: V,
94    );
95}
96
97/// A mutable stack environment, backed by a static stack environment.
98pub struct MutableStackEnvironment<'a> {
99    /// The backing stack environment that contains the original bindings.
100    pub backing: StackEnvironment<'a>,
101
102    /// The updates made to the stack environment.
103    pub updates: Vec<(&'a str, usize)>,
104}
105
106impl<'a> MutableStackEnvironment<'a> {
107    /// Looks up a value associated with the given key in the updates first.
108    ///
109    /// This is useful when exporting values, as the newly bound values
110    /// tend to be the keys being exported.
111    ///
112    /// ## Arguments
113    ///
114    /// * `key`: The key to look up.
115    ///
116    /// ## Returns
117    ///
118    /// An `Option<usize>` containing the value if found in updates,
119    /// or `None` if not found.
120    #[inline]
121    #[must_use]
122    fn updates_first_lookup(
123        &self,
124        key: &'a str,
125    ) -> Option<usize> {
126        self.updates
127            .as_slice()
128            .lookup(key)
129            .or_else(|| self.backing.lookup(key))
130    }
131}
132
133impl<'a> StackMap<'a, usize> for MutableStackEnvironment<'a> {
134    #[inline]
135    fn lookup(
136        &self,
137        key: &str,
138    ) -> Option<usize> {
139        self.backing
140            .lookup(key)
141            .or_else(|| self.updates.as_slice().lookup(key))
142    }
143
144    #[inline]
145    fn export_key_values<const K: usize>(
146        &self,
147        keys: &'a [&str; K],
148    ) -> [usize; K] {
149        let mut values: [usize; K] = [Default::default(); K];
150        for i in 0..K {
151            let key = keys[i];
152            values[i] = match self.updates_first_lookup(key) {
153                Some(v) => v,
154                None => panic!("No value for key \"{key}\""),
155            };
156        }
157        values
158    }
159}
160
161impl<'a> MutableStackMap<'a, usize> for MutableStackEnvironment<'a> {
162    #[inline]
163    fn bind(
164        &mut self,
165        key: &'a str,
166        value: usize,
167    ) {
168        #[cfg(debug_assertions)]
169        assert!(self.lookup(key).is_none(), "double-bind: {key}");
170
171        self.updates.push((key, value))
172    }
173}
174
175impl<'a> MutableStackEnvironment<'a> {
176    /// Creates a new `MutableStackEnvironment` with the given backing bindings.
177    #[inline(always)]
178    pub fn new(bindings: StackEnvironment<'a>) -> Self {
179        MutableStackEnvironment {
180            backing: bindings,
181            updates: Vec::new(),
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_stack_bindings() {
192        {
193            // stack bindings.
194            let env: StackEnvironment = &[("a", 1), ("b", 2)];
195
196            assert_eq!(env.lookup("a"), Some(1));
197            assert_eq!(env.lookup("b"), Some(2));
198            assert_eq!(env.lookup("c"), None);
199        }
200        {
201            // static bindings.
202            static ENV: StackEnvironment = &[("a", 1), ("b", 2)];
203
204            assert_eq!(ENV.lookup("a"), Some(1));
205            assert_eq!(ENV.lookup("b"), Some(2));
206            assert_eq!(ENV.lookup("c"), None);
207
208            // Exporting values
209            let keys = ["b", "a"];
210            assert_eq!(ENV.export_key_values(&keys), [2, 1]);
211        }
212    }
213
214    #[should_panic(expected = "No value for key \"d\"")]
215    #[test]
216    fn test_stack_env_export_key_values_panic() {
217        let env: StackEnvironment = &[("a", 1), ("b", 2)];
218        let keys = ["a", "b", "d"]; // 'd' does not exist
219        let _ = env.export_key_values(&keys); // This should panic
220    }
221
222    #[test]
223    fn test_mutable_stack_bindings() {
224        let mut env = MutableStackEnvironment::new(&[("a", 1), ("b", 2)]);
225
226        assert_eq!(env.lookup("a"), Some(1));
227        assert_eq!(env.lookup("b"), Some(2));
228        assert_eq!(env.lookup("c"), None);
229
230        env.bind("c", 3);
231        assert_eq!(env.lookup("c"), Some(3));
232
233        // Exporting values
234        let keys = ["a", "b", "c"];
235        let values = env.export_key_values(&keys);
236        assert_eq!(values, [1, 2, 3]);
237    }
238
239    #[should_panic(expected = "No value for key \"d\"")]
240    #[test]
241    fn test_muttable_stack_env_export_key_values_panic() {
242        let env = MutableStackEnvironment::new(&[("a", 1), ("b", 2)]);
243        let keys = ["a", "b", "d"]; // 'd' does not exist
244        let _ = env.export_key_values(&keys); // This should panic
245    }
246
247    #[should_panic(expected = "double-bind: a")]
248    #[test]
249    #[cfg(debug_assertions)]
250    fn test_double_bind_panic() {
251        let mut env = MutableStackEnvironment::new(&[("a", 1), ("b", 2)]);
252        env.bind("a", 3);
253    }
254}