sylvia/
utils.rs

1#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2enum State {
3    // Ongoing arrays can be compared to other arrays.
4    Ongoing(usize),
5    // Finished arrays can only be used by other arrays to be compared to.
6    Finished(usize),
7    // There can be empty arrays provided and we want to secure ourselves from "index out of bounds"
8    Empty,
9}
10
11/// # Examples
12///
13/// Compile time intersection assert.
14/// Will panic! in case duplicated messages were provided.
15/// Requires sorted arrays to work.
16/// ```
17///     const _: () = {
18///         let msgs: [&[&str]; 2] = [&["msg_a", "msg_b"], &["msg_c", "msg_d"]];
19///         sylvia::utils::assert_no_intersection(msgs);
20///     };
21/// ```
22pub const fn assert_no_intersection<const N: usize>(msgs: [&[&str]; N]) {
23    let mut states = init_states(&msgs);
24
25    while !should_end(&states) {
26        // Get index of array with alphabetically smallest value.
27        // This will always be one which state is Ongoing.
28        let index = get_next_alphabetical_index(&msgs, &states);
29
30        // Compare all elements at current indexes
31        verify_no_collissions(&msgs, &states, &index);
32
33        // Increment index of alphabetically first element
34        states[index] = match states[index] {
35            State::Ongoing(wi) => {
36                if msgs[index].len() == wi + 1 {
37                    State::Finished(wi)
38                } else {
39                    State::Ongoing(wi + 1)
40                }
41            }
42            _ => unreachable!(),
43        };
44    }
45}
46
47const fn init_states<const N: usize>(msgs: &[&[&str]; N]) -> [State; N] {
48    let mut states = [State::Ongoing(0); N];
49    konst::for_range! {i in 0..N =>
50        if msgs[i].is_empty() {
51            states[i] = State::Empty;
52        }
53
54    }
55    states
56}
57
58// Finds index of array which is Ongoing and which current value
59// is alphabetically smallest
60const fn get_next_alphabetical_index<const N: usize>(
61    msgs: &[&[&str]; N],
62    states: &[State; N],
63) -> usize {
64    let mut output_index = 0;
65    konst::for_range! {i in 0..N =>
66        if let State::Ongoing(outer_i) = states[i] {
67            match states[output_index] {
68                State::Ongoing(inner_i) => {
69                    if let std::cmp::Ordering::Greater =
70                        konst::cmp_str(msgs[output_index][inner_i], msgs[i][outer_i])
71                    {
72                        output_index = i;
73                    }
74                }
75                _ => output_index = i,
76            }
77        }
78    }
79    output_index
80}
81
82// Compare values at current indexes saved in states.
83// All comparisons are made with value at index which point to alphabetically smallest
84// and values in each other arrays at their current position.
85//
86// Because arrays are sorted we don't have to compare each value with each other
87// and can just compare values in alphabetical ordering.
88const fn verify_no_collissions<const N: usize>(
89    msgs: &[&[&str]; N],
90    states: &[State; N],
91    index: &usize,
92) {
93    let mut i = 0;
94    while i < N {
95        if i == *index {
96            i += 1;
97            continue;
98        }
99        match states[i] {
100            State::Ongoing(outer_i) | State::Finished(outer_i) => {
101                if let State::Ongoing(inner_i) = states[*index] {
102                    if konst::eq_str(msgs[i][outer_i], msgs[*index][inner_i]) {
103                        panic!("Message overlaps between interface and contract impl!");
104                    }
105                }
106            }
107            _ => (),
108        }
109        i += 1;
110    }
111}
112
113const fn should_end<const N: usize>(states: &[State; N]) -> bool {
114    konst::for_range! {i in 0..N =>
115        if let State::Ongoing(..) = states[i] {
116            return false;
117        }
118    }
119    true
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn should_not_end() {
128        let states = [State::Empty, State::Ongoing(3), State::Finished(5)];
129        assert!(!super::should_end(&states));
130    }
131
132    #[test]
133    fn should_end() {
134        let states = [State::Empty, State::Finished(3), State::Finished(5)];
135        assert!(super::should_end(&states));
136    }
137
138    #[test]
139    fn init_states() {
140        let msgs: [&[&str]; 3] = [&["msg", "msg"], &[], &["msg"]];
141        let states = [State::Ongoing(0), State::Empty, State::Ongoing(0)];
142        assert_eq!(super::init_states(&msgs), states);
143    }
144
145    #[test]
146    fn aquire_index_when_two_states_ongoing() {
147        let msgs: [&[&str]; 3] = [&["msg_b", "msg_c"], &[], &["msg_a"]];
148        let states = [State::Ongoing(1), State::Empty, State::Ongoing(0)];
149        assert_eq!(get_next_alphabetical_index(&msgs, &states), 2);
150    }
151
152    #[test]
153    fn aquire_index_when_mixed_state() {
154        let msgs: [&[&str]; 3] = [&["msg_b", "msg_c"], &[], &["msg_a"]];
155        let states = [State::Ongoing(1), State::Empty, State::Finished(0)];
156        assert_eq!(get_next_alphabetical_index(&msgs, &states), 0);
157    }
158
159    #[test]
160    fn aquire_index_when_first_array_empty() {
161        let msgs: [&[&str]; 3] = [&[], &["msg_b", "msg_c"], &["msg_a"]];
162        let states = [State::Empty, State::Ongoing(1), State::Finished(0)];
163        assert_eq!(get_next_alphabetical_index(&msgs, &states), 1);
164    }
165
166    #[test]
167    fn verify_no_collissions() {
168        let msgs: [&[&str]; 4] = [&[], &["msg_b", "msg_c"], &["msg_a"], &["msg_d", "msg_a"]];
169        let states = [
170            State::Empty,
171            State::Ongoing(1),
172            State::Finished(0),
173            State::Ongoing(0),
174        ];
175
176        super::verify_no_collissions(&msgs, &states, &1);
177        super::verify_no_collissions(&msgs, &states, &3);
178
179        let states = [
180            State::Empty,
181            State::Ongoing(1),
182            State::Finished(0),
183            State::Ongoing(1),
184        ];
185
186        super::verify_no_collissions(&msgs, &states, &1);
187    }
188
189    #[test]
190    #[should_panic]
191    fn verify_collissions() {
192        let msgs: [&[&str]; 4] = [&[], &["msg_b", "msg_c"], &["msg_a"], &["msg_d", "msg_a"]];
193        let states = [
194            State::Empty,
195            State::Ongoing(1),
196            State::Finished(0),
197            State::Ongoing(1),
198        ];
199
200        super::verify_no_collissions(&msgs, &states, &3);
201    }
202
203    #[test]
204    fn no_intersection() {
205        let msgs: [&[&str]; 5] = [
206            &["msg_b", "msg_c"],
207            &["msg_d", "msg_e", "msg_f"],
208            &["msg_a"],
209            &["msg_g", "msg_h", "msg_i", "msg_j"],
210            &[],
211        ];
212
213        assert_no_intersection(msgs);
214    }
215
216    #[test]
217    #[should_panic]
218    fn intersection() {
219        let msgs: [&[&str]; 5] = [
220            &["msg_b", "msg_c", "msg_i"],
221            &["msg_d", "msg_e", "msg_f"],
222            &["msg_a"],
223            &["msg_g", "msg_h", "msg_i", "msg_j"],
224            &[],
225        ];
226
227        assert_no_intersection(msgs);
228    }
229
230    #[test]
231    fn single_interface_with_no_contract_msgs() {
232        let msgs: [&[&str]; 2] = [&["msg_a", "msg_b"], &[]];
233
234        assert_no_intersection(msgs);
235    }
236}