use vstd::prelude::*;
verus! {
pub open spec fn carries<T>(
complete: Seq<T>,
accumulated: Seq<T>,
pending: Seq<T>,
) -> bool {
complete == accumulated.add(pending)
}
pub proof fn mismatched_partition_rejected<T>(
complete: Seq<T>,
accumulated: Seq<T>,
pending: Seq<T>,
)
requires complete != accumulated.add(pending),
ensures !carries(complete, accumulated, pending),
{
}
pub open spec fn append<T>(before: Seq<T>, after: Seq<T>, value: T) -> bool {
after == before.push(value)
}
pub open spec fn stutter<T>(before: Seq<T>, after: Seq<T>) -> bool {
after == before
}
pub proof fn append_pending<T>(prefix: Seq<T>, pending: Seq<T>, value: T)
ensures
prefix.add(pending).push(value) == prefix.add(pending.push(value)),
{
Seq::push_distributes_over_add(prefix, pending, value);
}
pub proof fn consume_pending_head<T>(accumulated: Seq<T>, pending: Seq<T>)
requires pending.len() > 0,
ensures
accumulated.add(pending)
== accumulated.push(pending[0]).add(pending.skip(1)),
{
assert(pending =~= seq![pending[0]].add(pending.skip(1)));
assert(accumulated.push(pending[0]) =~= accumulated.add(seq![pending[0]]));
assert(accumulated.add(pending)
=~= accumulated.push(pending[0]).add(pending.skip(1)));
}
pub proof fn move_pending_head<T>(
prefix: Seq<T>,
destination: Seq<T>,
source: Seq<T>,
)
requires source.len() > 0,
ensures
prefix.add(destination).add(source)
== prefix.add(destination.push(source[0])).add(source.skip(1)),
{
assert(source =~= seq![source[0]].add(source.skip(1)));
assert(destination.push(source[0]) =~= destination.add(seq![source[0]]));
assert(prefix.add(destination).add(source)
=~= prefix.add(destination.push(source[0])).add(source.skip(1)));
}
pub proof fn move_pending_head_with_suffix<T>(
prefix: Seq<T>,
destination: Seq<T>,
source: Seq<T>,
suffix: Seq<T>,
)
requires source.len() > 0,
ensures
prefix.add(destination).add(source).add(suffix)
== prefix
.add(destination.push(source[0]))
.add(source.skip(1))
.add(suffix),
{
move_pending_head(prefix, destination, source);
}
pub proof fn consume_pending_head_with_suffix<T>(
accumulated: Seq<T>,
source: Seq<T>,
suffix: Seq<T>,
)
requires source.len() > 0,
ensures
accumulated.add(source.add(suffix))
== accumulated.push(source[0]).add(source.skip(1).add(suffix)),
{
consume_pending_head(accumulated, source);
assert(accumulated.add(source.add(suffix))
=~= accumulated.add(source).add(suffix));
assert(accumulated.push(source[0]).add(source.skip(1).add(suffix))
=~= accumulated.push(source[0]).add(source.skip(1)).add(suffix));
}
pub proof fn skip_pending_head_with_suffix<T>(source: Seq<T>, suffix: Seq<T>)
requires source.len() > 0,
ensures
source.add(suffix).skip(1) == source.skip(1).add(suffix),
{
assert(source.add(suffix).skip(1) =~= source.skip(1).add(suffix));
}
pub struct Accumulator<T: Copy> {
pub original: Ghost<Seq<T>>,
pub accumulated: Vec<T>,
pub pending: Vec<T>,
}
impl<T: Copy> Accumulator<T> {
pub closed spec fn well_formed(&self) -> bool {
carries(self.original@, self.accumulated@, self.pending@)
}
pub fn new(values: Vec<T>) -> (accumulator: Self)
ensures
accumulator.well_formed(),
accumulator.original@ == values@,
accumulator.accumulated@.len() == 0,
accumulator.pending@ == values@,
{
let ghost original = values@;
Self { original: Ghost(original), accumulated: Vec::new(), pending: values }
}
pub fn from_accumulated(values: Vec<T>) -> (accumulator: Self)
ensures
accumulator.well_formed(),
accumulator.original@ == values@,
accumulator.accumulated@ == values@,
accumulator.pending@.len() == 0,
{
let ghost original = values@;
Self { original: Ghost(original), accumulated: values, pending: Vec::new() }
}
pub fn accumulated_len(&self) -> (length: usize)
ensures length == self.accumulated@.len(),
{
self.accumulated.len()
}
pub fn len(&self) -> (length: usize)
ensures length == self.accumulated@.len(),
{
self.accumulated.len()
}
pub fn is_empty(&self) -> (empty: bool)
ensures empty == (self.accumulated@.len() == 0),
{
self.accumulated.is_empty()
}
pub fn pending_len(&self) -> (length: usize)
ensures length == self.pending@.len(),
{
self.pending.len()
}
pub fn is_complete(&self) -> (complete: bool)
ensures complete == (self.pending@.len() == 0),
{
self.pending.is_empty()
}
#[expect(clippy::indexing_slicing, reason = "the branch proves the accumulated index is in bounds")]
pub fn accumulated(&self, index: usize) -> (value: Option<T>)
ensures value == if index < self.accumulated@.len() {
Some(self.accumulated@[index as int])
} else {
None
},
{
if index < self.accumulated.len() { Some(self.accumulated[index]) } else { None }
}
pub fn value(&self, index: usize) -> (value: Option<T>)
ensures value == if index < self.accumulated@.len() {
Some(self.accumulated@[index as int])
} else {
None
},
{
self.accumulated(index)
}
#[expect(clippy::indexing_slicing, reason = "the branch proves the pending index is in bounds")]
pub fn pending(&self, index: usize) -> (value: Option<T>)
ensures value == if index < self.pending@.len() {
Some(self.pending@[index as int])
} else {
None
},
{
if index < self.pending.len() { Some(self.pending[index]) } else { None }
}
#[expect(clippy::indexing_slicing, reason = "the nonempty guard proves the pending head exists")]
pub fn advance(&mut self) -> (value: Option<T>)
requires old(self).well_formed(),
ensures
final(self).well_formed(),
final(self).original@ == old(self).original@,
value == if old(self).pending@.len() > 0 {
Some(old(self).pending@[0])
} else {
None
},
final(self).accumulated@ == if old(self).pending@.len() > 0 {
old(self).accumulated@.push(old(self).pending@[0])
} else {
old(self).accumulated@
},
final(self).pending@ == if old(self).pending@.len() > 0 {
old(self).pending@.skip(1)
} else {
old(self).pending@
},
{
if self.pending.is_empty() { return None; }
let ghost old_accumulated = self.accumulated@;
let ghost old_pending = self.pending@;
let value = self.pending[0];
self.pending.remove(0);
self.accumulated.push(value);
proof {
consume_pending_head(old_accumulated, old_pending);
assert(self.accumulated@ =~= old_accumulated.push(old_pending[0]));
assert(self.pending@ =~= old_pending.skip(1));
}
Some(value)
}
pub fn append(&mut self, value: T)
requires
old(self).well_formed(),
old(self).pending@.len() == 0,
ensures
final(self).well_formed(),
final(self).original@ == old(self).original@.push(value),
final(self).accumulated@ == old(self).accumulated@.push(value),
final(self).pending@ == old(self).pending@,
{
let ghost old_original = self.original@;
let ghost old_accumulated = self.accumulated@;
let ghost old_pending = self.pending@;
self.accumulated.push(value);
self.original = Ghost(old_original.push(value));
proof {
assert(old_pending =~= Seq::<T>::empty());
assert(old_original == old_accumulated.add(old_pending));
assert(old_original =~= old_accumulated);
assert(self.pending@ =~= Seq::<T>::empty());
assert(self.original@ == self.accumulated@.add(self.pending@));
}
}
}
}
impl<T: Copy + core::fmt::Debug> core::fmt::Debug for Accumulator<T> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("Accumulator")
.field("accumulated", &self.accumulated)
.field("pending", &self.pending)
.finish()
}
}