Skip to main content

bimm_contracts/
bindings.rs

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