1#[cfg(creusot)]
4use creusot_std::prelude::{Int, Seq, invariant, logic};
5use creusot_std::prelude::{ensures, requires};
6
7#[cfg(creusot)]
11#[cfg_attr(test, mutants::skip)]
12#[logic]
13pub fn sorted_disjoint(bases: Seq<i64>, lasts: Seq<i64>) -> bool {
14 pearlite! {
15 bases.len() == lasts.len()
16 && (forall<i: Int> 0 <= i && i < bases.len() ==> bases[i]@ <= lasts[i]@)
17 && (forall<i: Int> 1 <= i && i < bases.len() ==> lasts[i - 1]@ < bases[i]@)
18 }
19}
20
21#[ensures(match result {
24 Some((base, last)) => index@ < [email protected]() && index@ < [email protected]()
25 && base == bases@[index@] && last == lasts@[index@],
26 None => [email protected]() <= index@ || [email protected]() <= index@,
27})]
28fn stamp_range(bases: &[i64], lasts: &[i64], index: usize) -> Option<(i64, i64)> {
29 match (bases.get(index), lasts.get(index)) {
30 (Some(&base), Some(&last)) => Some((base, last)),
31 _ => None,
32 }
33}
34
35#[ensures(result == sorted_disjoint(bases@, lasts@))]
38#[must_use]
39pub fn stamp_ranges_valid(bases: &[i64], lasts: &[i64]) -> bool {
40 if bases.len() != lasts.len() {
41 return false;
42 }
43 let mut index = 0usize;
44 #[invariant(stamp_cursor_in_bounds(bases@, lasts@, index@))]
45 #[invariant(forall<i: Int> 0 <= i && i < index@ ==> bases@[i]@ <= lasts@[i]@)]
46 #[invariant(forall<i: Int> 1 <= i && i < index@ ==> lasts@[i - 1]@ < bases@[i]@)]
47 #[variant([email protected]() - index@)]
48 while index < bases.len() {
49 let Some((base, last)) = stamp_range(bases, lasts, index) else {
50 return false;
51 };
52 if base > last {
53 return false;
54 }
55 if index > 0 {
56 let Some(&previous_last) = lasts.get(index - 1) else {
57 return false;
58 };
59 if previous_last >= base {
60 return false;
61 }
62 }
63 index += 1;
64 }
65 true
66}
67
68#[requires(sorted_disjoint(bases@, lasts@))]
78#[ensures(match result {
79 Some(index) => index@ <= [email protected]()
80 && new_base@ <= new_last@
81 && (forall<i: Int> 0 <= i && i < index@ ==> lasts@[i]@ < new_base@)
82 && (index@ == [email protected]() || new_last@ < bases@[index@]@),
83 None => new_base@ > new_last@
84 || (exists<i: Int> 0 <= i && i < [email protected]()
85 && bases@[i]@ <= new_last@ && new_base@ <= lasts@[i]@),
86})]
87#[must_use]
88pub fn stamp_range_insertion_index(
89 bases: &[i64],
90 lasts: &[i64],
91 new_base: i64,
92 new_last: i64,
93) -> Option<usize> {
94 if new_base > new_last {
95 return None;
96 }
97 let mut index = 0usize;
98 #[invariant(stamp_cursor_in_bounds(bases@, lasts@, index@))]
99 #[invariant(forall<i: Int> 0 <= i && i < index@ ==> lasts@[i]@ < new_base@)]
100 #[variant([email protected]() - index@)]
101 while index < bases.len() {
102 let (base, last) = stamp_range(bases, lasts, index)?;
103 if new_last < base {
104 return Some(index);
105 }
106 if new_base <= last {
107 return None;
108 }
109 index += 1;
110 }
111 Some(index)
112}
113
114#[requires(stamp_range_arrays_parallel(bases@, lasts@))]
121#[ensures(stamp_lookup_result(
122 result,
123 bases@,
124 lasts@,
125 StampLookupQuery::Exact { base: target_base, last: target_last },
126))]
127#[must_use]
128pub fn exact_stamp_range_index(
129 bases: &[i64],
130 lasts: &[i64],
131 target_base: i64,
132 target_last: i64,
133) -> Option<usize> {
134 let mut index = 0usize;
135 #[invariant(stamp_lookup_cursor_valid(
136 bases@,
137 lasts@,
138 index@,
139 StampLookupQuery::Exact { base: target_base, last: target_last },
140 ))]
141 #[variant([email protected]() - index@)]
142 while index < bases.len() {
143 let (base, last) = stamp_range(bases, lasts, index)?;
144 if base == target_base && last == target_last {
145 return Some(index);
146 }
147 index += 1;
148 }
149 None
150}
151
152#[requires(stamp_range_arrays_parallel(bases@, lasts@))]
157#[ensures(stamp_lookup_result(
158 result,
159 bases@,
160 lasts@,
161 StampLookupQuery::Covering { offset },
162))]
163#[must_use]
164pub fn covering_stamp_range_index(bases: &[i64], lasts: &[i64], offset: i64) -> Option<usize> {
165 let mut index = 0usize;
166 #[invariant(stamp_lookup_cursor_valid(
167 bases@,
168 lasts@,
169 index@,
170 StampLookupQuery::Covering { offset },
171 ))]
172 #[variant([email protected]() - index@)]
173 while index < bases.len() {
174 let (base, last) = stamp_range(bases, lasts, index)?;
175 if base <= offset && offset <= last {
176 return Some(index);
177 }
178 index += 1;
179 }
180 None
181}
182
183#[cfg(creusot)]
185#[cfg_attr(test, mutants::skip)]
186#[logic(open(crate))]
187pub(crate) fn stamp_cursor_in_bounds(bases: Seq<i64>, lasts: Seq<i64>, index: Int) -> bool {
189 pearlite! { index <= bases.len() && stamp_range_arrays_parallel(bases, lasts) }
190}
191
192#[cfg(creusot)]
194#[cfg_attr(test, mutants::skip)]
195#[logic(open)]
196pub fn stamp_range_arrays_parallel(bases: Seq<i64>, lasts: Seq<i64>) -> bool {
198 pearlite! { bases.len() == lasts.len() }
199}
200
201#[cfg(creusot)]
202#[derive(Clone, Copy)]
203pub(crate) enum StampLookupQuery {
204 Exact { base: i64, last: i64 },
205 Covering { offset: i64 },
206}
207
208#[cfg(creusot)]
210#[cfg_attr(test, mutants::skip)]
211#[logic(open(crate))]
212pub(crate) fn stamp_lookup_matches(query: StampLookupQuery, base: i64, last: i64) -> bool {
213 pearlite! { match query {
214 StampLookupQuery::Exact { base: target_base, last: target_last } =>
215 base@ == target_base@ && last@ == target_last@,
216 StampLookupQuery::Covering { offset } => base@ <= offset@ && offset@ <= last@,
217 } }
218}
219
220#[cfg(creusot)]
222#[cfg_attr(test, mutants::skip)]
223#[logic(open(crate))]
224pub(crate) fn no_stamp_lookup_before(
225 bases: Seq<i64>,
226 lasts: Seq<i64>,
227 count: Int,
228 query: StampLookupQuery,
229) -> bool {
230 pearlite! { forall<i: Int> 0 <= i && i < count
231 ==> !stamp_lookup_matches(query, bases[i], lasts[i]) }
232}
233
234#[cfg(creusot)]
236#[cfg_attr(test, mutants::skip)]
237#[logic(open(crate))]
238pub(crate) fn stamp_lookup_cursor_valid(
239 bases: Seq<i64>,
240 lasts: Seq<i64>,
241 index: Int,
242 query: StampLookupQuery,
243) -> bool {
244 pearlite! {
245 stamp_cursor_in_bounds(bases, lasts, index)
246 && no_stamp_lookup_before(bases, lasts, index, query)
247 }
248}
249
250#[cfg(creusot)]
252#[cfg_attr(test, mutants::skip)]
253#[logic(open(crate))]
254pub(crate) fn stamp_lookup_result(
255 result: Option<usize>,
256 bases: Seq<i64>,
257 lasts: Seq<i64>,
258 query: StampLookupQuery,
259) -> bool {
260 pearlite! { match result {
261 Some(index) => index@ < bases.len()
262 && stamp_lookup_matches(query, bases[index@], lasts[index@])
263 && no_stamp_lookup_before(bases, lasts, index@, query),
264 None => no_stamp_lookup_before(bases, lasts, bases.len(), query),
265 } }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::{
271 covering_stamp_range_index, exact_stamp_range_index, stamp_range_insertion_index,
272 stamp_ranges_valid,
273 };
274
275 #[test]
276 fn range_decisions_cover_boundaries_retries_and_malformed_inputs() {
277 let bases = [0, 5, 10];
278 let lasts = [2, 7, 12];
279 assert2::assert!(stamp_ranges_valid(&bases, &lasts));
280 assert2::assert!(!stamp_ranges_valid(&[0, 5], &[2]));
281 assert2::assert!(!stamp_ranges_valid(&[3], &[2]));
282 assert2::assert!(!stamp_ranges_valid(&[0, 2], &[2, 4]));
283 assert2::assert!(stamp_range_insertion_index(&bases, &lasts, 3, 4) == Some(1));
284 assert2::assert!(stamp_range_insertion_index(&bases, &lasts, 8, 9) == Some(2));
285 assert2::assert!(stamp_range_insertion_index(&bases, &lasts, 2, 3).is_none());
286 assert2::assert!(stamp_range_insertion_index(&bases, &lasts, 4, 3).is_none());
287 assert2::assert!(exact_stamp_range_index(&bases, &lasts, 5, 7) == Some(1));
288 assert2::assert!(exact_stamp_range_index(&bases, &lasts, 5, 8).is_none());
289 assert2::assert!(covering_stamp_range_index(&bases, &lasts, 7) == Some(1));
290 assert2::assert!(covering_stamp_range_index(&bases, &lasts, 8).is_none());
291 assert2::assert!(stamp_ranges_valid(&[1], &[1]));
292 assert2::assert!(stamp_range_insertion_index(&[5], &[7], 1, 1) == Some(0));
293 assert2::assert!(stamp_range_insertion_index(&[5], &[7], 10, 10) == Some(1));
294 assert2::assert!(stamp_range_insertion_index(&[5], &[7], 5, 5).is_none());
295 assert2::assert!(stamp_range_insertion_index(&[5], &[7], 4, 5).is_none());
296 }
297}