From 6a32b8664ab75447a55d88a5a8887dcf6908408b Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 2 Feb 2022 11:58:01 -0800 Subject: [PATCH 01/68] Partial work, almost verifying --- src/Actions.dfy | 135 +++++++++++++++++++++++++++++++++ src/Enumeration/Loopers.dfy | 147 ++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 src/Actions.dfy create mode 100644 src/Enumeration/Loopers.dfy diff --git a/src/Actions.dfy b/src/Actions.dfy new file mode 100644 index 00000000..da11f325 --- /dev/null +++ b/src/Actions.dfy @@ -0,0 +1,135 @@ +include "Wrappers.dfy" + +module Actions { + + import opened Wrappers + + trait {:termination false} Action + { + method Invoke(a: A) returns (r: R) + requires Requires(a) + modifies Modifies(a) + ensures old(allocated(r)) && Ensures(a, r) + + predicate Requires(a: A) + function Modifies(a: A): set + twostate predicate Ensures(a: A, r: R) + } + + type IEnumeration = Action<(), T> + + // predicate Enumerates(e: IEnumeration, elements: seq) { + // e.Ensures((), elements[0]) + // } + + trait IEnumerator { + ghost var pending: seq + ghost var Repr: set + + predicate Valid() reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + + method Next() returns (t: T) + requires Valid() + requires |pending| > 0 + modifies Repr + ensures Valid() + ensures t == old(pending)[0] + ensures pending == old(pending)[1..] + } + + trait Enumerator { + ghost var pending: seq + ghost var Repr: set + + predicate Valid() reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + + method Next() returns (t: Option) + requires Valid() + modifies Repr + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures |old(pending)| > 0 ==> + && t.Some? + && t.value == old(pending)[0] + && pending == old(pending)[1..] + ensures |old(pending)| == 0 ==> + && t.None? + && unchanged(this) + } + + trait MyEnumerator extends IEnumerator { + + } + + iterator SeqIterator(s: seq) yields (t: T) { + for i := 0 to |s| { + yield s[i]; + } + } + + class SeqEnumerator extends MyEnumerator { + + var elements: seq + + constructor(s: seq) { + pending := s; + elements := s; + Repr := {this}; + } + + predicate Valid() reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + && pending == elements + } + + method Next() returns (t: T) + requires Valid() + requires |pending| > 0 + modifies Repr + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures t == old(pending)[0] + ensures pending == old(pending)[1..] + ensures |pending| == |old(pending)| + 1 + { + t := elements[0]; + elements := elements[1..]; + pending := elements; + } + } + + method Count(e: Enumerator) returns (count: int) + requires e.Valid() + modifies e.Repr + decreases * + ensures count == |e.pending| + { + count := 0; + var next := e.Next(); + if next.None? { + return; + } + count := count + 1; + while next.Some? + invariant e.Valid() + invariant fresh(e.Repr - old(e.Repr)) + invariant count + |e.pending| == |old(e.pending)| + decreases * + { + label before: next := e.Next(); + + // assert |old@before(e.pending)| + 1 == |e.pending|; + if next.Some? { + count := count + 1; + } + } + assert |e.pending| == 0; + } +} \ No newline at end of file diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy new file mode 100644 index 00000000..c645be0d --- /dev/null +++ b/src/Enumeration/Loopers.dfy @@ -0,0 +1,147 @@ + +include "../Actions.dfy" + +module Loopers { + + import opened Actions + + trait Looper { + predicate Invariant() + method Step() + requires Invariant() + ensures Invariant() + } + + method DoLoopStar(l: Looper) + requires l.Invariant() + decreases * + { + while (true) decreases * { + l.Step(); + } + } + + trait TerminatingLooper { + ghost var Repr: set + predicate Invariant() + reads this, Repr + ensures Invariant() ==> this in Repr + + // Will need to be arbitrary termination clause somehow instead + // https://github.com/dafny-lang/dafny/issues/762 + // TODO: split up the ghost version from the version called at runtime? + function method Decreases(): nat + reads Repr + decreases Repr + requires Invariant() + predicate method Done() + reads Repr + requires Invariant() + { + Decreases() == 0 + } + + method Step() + requires Invariant() + requires !Done() + modifies Repr + ensures Invariant() + ensures fresh(Repr - old(Repr)) + ensures Decreases() < old(Decreases()) + } + + method DoLoop(l: TerminatingLooper) + requires l.Invariant() + modifies l.Repr + ensures l.Invariant() + ensures l.Done() + { + while (l.Decreases() > 0) + invariant l.Invariant() + invariant fresh(l.Repr - old(l.Repr)) + { + l.Step(); + } + } + + class SeqLooper extends TerminatingLooper { + + const elements: seq + var index: nat + + constructor(s: seq) { + elements := s; + index := 0; + Repr := {this}; + } + + predicate Invariant() + reads this, Repr + ensures Invariant() ==> this in Repr + decreases Repr + { + && this in Repr + && 0 <= index <= |elements| + } + + function method Decreases(): nat + reads Repr + requires Invariant() + { + |elements| - index + } + + method Step() + requires Invariant() + requires !Done() + modifies Repr + ensures Invariant() + ensures fresh(Repr - old(Repr)) + ensures Decreases() < old(Decreases()) + { + index := index + 1; + } + } + + class ForEach extends TerminatingLooper { + const iter: TerminatingLooper + const body: Action + + constructor(iter: TerminatingLooper, body: Action) { + this.iter := iter; + this.body := body; + } + + predicate Invariant() + reads this, Repr + decreases Repr + ensures Invariant() ==> this in Repr + { + && this in Repr + && iter in Repr + && iter.Repr <= Repr + && this !in iter.Repr + && iter.Invariant() && (iter.Invariant() ==> body.Requires(iter)) + } + + function method Decreases(): nat + reads Repr + requires Invariant() + { + iter.Decreases() + } + + method Step() + requires Invariant() + requires Decreases() > 0 + modifies Repr + decreases Repr + ensures Invariant() + ensures fresh(Repr - old(Repr)) + ensures Decreases() < old(Decreases()) + { + iter.Step(); + var _ := body.Invoke(iter); + } + } +} \ No newline at end of file From 0be025117e37cdd53025b0f27258711c918e640e Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 5 Feb 2022 08:58:18 -0800 Subject: [PATCH 02/68] Got an iterator -> enumerator adaptor to verify --- src/Actions.dfy | 92 +++++++++++++++++++++- src/Enumeration/Iterators.dfy | 141 ++++++++++++++++++++++++++++++++++ src/Enumeration/Loopers.dfy | 104 +++++++++++++------------ src/Frames.dfy | 34 ++++++++ 4 files changed, 317 insertions(+), 54 deletions(-) create mode 100644 src/Enumeration/Iterators.dfy create mode 100644 src/Frames.dfy diff --git a/src/Actions.dfy b/src/Actions.dfy index da11f325..ec479e2f 100644 --- a/src/Actions.dfy +++ b/src/Actions.dfy @@ -1,21 +1,105 @@ include "Wrappers.dfy" +include "Frames.dfy" module Actions { import opened Wrappers - - trait {:termination false} Action + import opened Frames + + // TODO: Unfortunately, would be good to have Action0 up to ActionN for + // some small N. Maybe also M for number of returns. + // Using () is SUPER awkward with Dafny syntax. An action with + // no arguments or return using unit looks like: + // var _ := runnable.Invoke(()); + trait {:termination false} Action extends Validatable { method Invoke(a: A) returns (r: R) + requires Valid() requires Requires(a) modifies Modifies(a) + decreases Decreases(a) + ensures ValidAndDisjoint() ensures old(allocated(r)) && Ensures(a, r) predicate Requires(a: A) + // Need this separately from Repr for callers + // Repr is the frame for Valid(), but callers + // need to know what ELSE gets modified. function Modifies(a: A): set + function Decreases(a: A): nat twostate predicate Ensures(a: A, r: R) } + class SeqCollector extends Action { + + var elements: seq + + predicate Valid() reads this, Repr { + true + } + + method Invoke(t: T) returns (nothing: ()) + requires Valid() + requires Requires(t) + modifies Modifies(t) + decreases Decreases(t) + ensures ValidAndDisjoint() + ensures old(allocated(())) && Ensures(t, ()) + { + elements := elements + [t]; + } + + predicate Requires(t: T) { + true + } + + function Modifies(t: T): set { + {this} + } + + function Decreases(t: T): nat { + 0 + } + + twostate predicate Ensures(t: T, nothing: ()) { + true + } + } + + class InvokeAction extends Action, ()> { + + predicate Valid() reads this, Repr { + true + } + + method Invoke(a: Action) returns (nothing: ()) + requires Valid() + requires Requires(a) + modifies Modifies(a) + decreases Decreases(a) + ensures ValidAndDisjoint() + ensures Ensures(a, ()) + { + var _ := a.Invoke(42); + } + + predicate Requires(a: Action) { + a.Decreases(42) < Decreases(a) + } + + function Modifies(a: Action): set { + a.Modifies(42) + } + + function Decreases(a: Action): nat { + 0 + } + + twostate predicate Ensures(a: Action, nothing: ()) { + true + } + } + type IEnumeration = Action<(), T> // predicate Enumerates(e: IEnumeration, elements: seq) { @@ -97,7 +181,7 @@ module Actions { ensures fresh(Repr - old(Repr)) ensures t == old(pending)[0] ensures pending == old(pending)[1..] - ensures |pending| == |old(pending)| + 1 + ensures |pending| == |old(pending)| - 1 { t := elements[0]; elements := elements[1..]; @@ -121,7 +205,7 @@ module Actions { invariant e.Valid() invariant fresh(e.Repr - old(e.Repr)) invariant count + |e.pending| == |old(e.pending)| - decreases * + decreases |e.pending| { label before: next := e.Next(); diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy new file mode 100644 index 00000000..867bcf8e --- /dev/null +++ b/src/Enumeration/Iterators.dfy @@ -0,0 +1,141 @@ + +include "../Frames.dfy" + +module IteratorExperiments { + + import opened Frames + + iterator ForEachWithIndex(s: seq) yields (element: T, index: nat) + ensures elements <= s + yield ensures elements <= s + { + for i := 0 to |s| + invariant i == |elements| + invariant elements <= s + { + yield s[i], i; + } + } + + trait Enumerator extends Validatable { + + method MoveNext() returns (more: bool) + requires Valid() + modifies Repr + ensures fresh(Repr - old(Repr)) + ensures more ==> Valid() + ensures more ==> Decreases() < old(Decreases()) + + function method Current(): T + reads this, Repr + requires Valid() + + function Decreases(): nat + reads this, Repr + requires Valid() + } + + class ForEachWithIndexEnumerator extends Enumerator<(T, nat)> { + + const iter: ForEachWithIndex + ghost var remaining: nat + + constructor(s: seq) ensures Valid() ensures fresh(Repr) { + iter := new ForEachWithIndex(s); + new; + remaining := |iter.s|; + Repr := {this, iter}; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && iter in Repr + && this !in iter._modifies + && this !in iter._reads + && this !in iter._new + && iter._modifies <= Repr + && iter._reads <= Repr + && iter._new <= Repr + && iter.Valid() + && remaining == |iter.s| - |iter.elements| + } + + method MoveNext() returns (more: bool) + requires Valid() + modifies Repr + ensures fresh(Repr - old(Repr)) + ensures more ==> Valid() + ensures more ==> Decreases() < old(Decreases()) + { + more := iter.MoveNext(); + if (more) { + assert old(iter.elements) < iter.elements; + } + remaining := |iter.s| - |iter.elements|; + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + } + + function method Current(): (T, nat) + reads this, Repr + requires Valid() + { + (iter.element, iter.index) + } + + function Decreases(): nat reads this, Repr requires Valid() { + remaining + } + } + + method Main() { + var s: seq := [1, 1, 2, 3, 5, 8]; + var e: Enumerator<(nat, nat)> := new ForEachWithIndexEnumerator(s); + assert fresh(e.Repr); + label start: + var more := true; + while (true) + invariant e.Valid() + invariant old@start(allocated(e)) && fresh(e.Repr) + decreases e.Decreases() + { + more := e.MoveNext(); + if (!more) { break; } + + var (element, index) := e.Current(); + print "Index: ", index, ", Element: ", element; + } + } + + // iterator Iter(s: set) yields (x: T) + // yield ensures x in s && x !in xs[..|xs|-1]; + // ensures s == set z | z in xs; + // { + // var r := s; + // while (r != {}) + // invariant forall z :: z in xs ==> x !in r; + // // r and xs are disjoint + // invariant s == r + set z | z in xs; + // { + // var y :| y in r; + // r, x := r - {y}, y; + // yield; + // assert y == xs[|xs|-1]; // a lemma to help prove loop invariant + // } + // } + + // method UseIterToCopy(s: set) returns (t: set) + // ensures s == t; + // { + // t := {}; + // var m := new Iter(s); + // while (true) + // invariant m.Valid() && fresh(m._new); + // invariant t == set z | z in m.xs; + // decreases s - t; + // { + // var more := m.MoveNext(); + // if (!more) { break; } + // t := t + {m.x}; + // } + // } +} \ No newline at end of file diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index c645be0d..299a2941 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -1,15 +1,17 @@ include "../Actions.dfy" +include "../Frames.dfy" module Loopers { import opened Actions + import opened Frames trait Looper { - predicate Invariant() - method Step() - requires Invariant() - ensures Invariant() + predicate Invariant() + method Step() + requires Invariant() + ensures Invariant() } method DoLoopStar(l: Looper) @@ -21,43 +23,40 @@ module Loopers { } } - trait TerminatingLooper { - ghost var Repr: set - predicate Invariant() - reads this, Repr - ensures Invariant() ==> this in Repr - - // Will need to be arbitrary termination clause somehow instead - // https://github.com/dafny-lang/dafny/issues/762 - // TODO: split up the ghost version from the version called at runtime? - function method Decreases(): nat - reads Repr - decreases Repr - requires Invariant() - predicate method Done() - reads Repr - requires Invariant() - { - Decreases() == 0 - } - - method Step() - requires Invariant() - requires !Done() - modifies Repr - ensures Invariant() - ensures fresh(Repr - old(Repr)) - ensures Decreases() < old(Decreases()) + trait TerminatingLooper extends Validatable { + // Will need to be arbitrary termination clause somehow instead + // https://github.com/dafny-lang/dafny/issues/762 + // TODO: split up the ghost version from the version called at runtime? + // Implementor needs to use this to prove termination, but caller + // only needs Done() + function method Decreases(): nat + reads Repr + decreases Repr + requires Valid() + predicate method Done() + reads Repr + requires Valid() + { + Decreases() == 0 + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) } method DoLoop(l: TerminatingLooper) - requires l.Invariant() + requires l.Valid() modifies l.Repr - ensures l.Invariant() + ensures l.Valid() ensures l.Done() { while (l.Decreases() > 0) - invariant l.Invariant() + invariant l.Valid() invariant fresh(l.Repr - old(l.Repr)) { l.Step(); @@ -75,9 +74,9 @@ module Loopers { Repr := {this}; } - predicate Invariant() + predicate Valid() reads this, Repr - ensures Invariant() ==> this in Repr + ensures Valid() ==> this in Repr decreases Repr { && this in Repr @@ -86,17 +85,17 @@ module Loopers { function method Decreases(): nat reads Repr - requires Invariant() + requires Valid() { |elements| - index } method Step() - requires Invariant() + requires Valid() requires !Done() modifies Repr - ensures Invariant() - ensures fresh(Repr - old(Repr)) + decreases Repr + ensures ValidAndDisjoint() ensures Decreases() < old(Decreases()) { index := index + 1; @@ -112,36 +111,41 @@ module Loopers { this.body := body; } - predicate Invariant() + predicate Valid() reads this, Repr decreases Repr - ensures Invariant() ==> this in Repr + ensures Valid() ==> this in Repr { && this in Repr - && iter in Repr - && iter.Repr <= Repr - && this !in iter.Repr - && iter.Invariant() && (iter.Invariant() ==> body.Requires(iter)) + && ValidComponent(iter) + && ValidComponent(body) + && iter.Repr !! body.Repr + && (iter.Valid() ==> body.Requires(iter)) + && (iter.Valid() ==> body.Modifies(iter) == {}) } function method Decreases(): nat reads Repr - requires Invariant() + requires Valid() { iter.Decreases() } method Step() - requires Invariant() + requires Valid() requires Decreases() > 0 modifies Repr decreases Repr - ensures Invariant() - ensures fresh(Repr - old(Repr)) + ensures ValidAndDisjoint() ensures Decreases() < old(Decreases()) { iter.Step(); + Repr := Repr + iter.Repr; + var _ := body.Invoke(iter); + Repr := Repr + body.Repr; } } + + } \ No newline at end of file diff --git a/src/Frames.dfy b/src/Frames.dfy new file mode 100644 index 00000000..a95fc8f7 --- /dev/null +++ b/src/Frames.dfy @@ -0,0 +1,34 @@ +module Frames { + + // A trait for objects with a Valid() predicate. Necessary in order to + // generalize some proofs, but also useful for reducing the boilerplate + // that most such objects need to include. + trait {:termination false} Validatable { + // Ghost state tracking the common set of objects most + // methods need to read. + ghost var Repr: set + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + + // Convenience predicate for when your object's validity depends on one + // or more other objects. + predicate ValidComponent(component: Validatable) + reads this, Repr + { + && component in Repr + && component.Repr <= Repr + && this !in component.Repr + && component.Valid() + } + + // Convenience predicate, since you often want to assert that + // new objects in Repr are fresh as well in most postconditions. + twostate predicate ValidAndDisjoint() + reads this, Repr + { + Valid() && fresh(Repr - old(Repr)) + } + } +} \ No newline at end of file From 6c2088ad010ca6dd70c840edab6ad095013329d8 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 8 Feb 2022 13:00:53 -0800 Subject: [PATCH 03/68] Add Enumerator.Done() --- src/Actions.dfy | 96 +++++++++++++++++++-- src/Enumeration/Iterators.dfy | 153 ++++++++++++++++++++++++++++++---- src/Enumeration/JavaStyle.dfy | 55 ++++++++++++ src/Enumeration/Loopers.dfy | 140 +++++++++++++++++++++++++++---- 4 files changed, 406 insertions(+), 38 deletions(-) create mode 100644 src/Enumeration/JavaStyle.dfy diff --git a/src/Actions.dfy b/src/Actions.dfy index ec479e2f..55ba67c3 100644 --- a/src/Actions.dfy +++ b/src/Actions.dfy @@ -25,11 +25,12 @@ module Actions { // Need this separately from Repr for callers // Repr is the frame for Valid(), but callers // need to know what ELSE gets modified. - function Modifies(a: A): set + function Modifies(a: A): set requires Requires(a) function Decreases(a: A): nat - twostate predicate Ensures(a: A, r: R) + predicate Ensures(a: A, r: R) } + // TODO: ArrayCollector will be more interesting, to track capacity class SeqCollector extends Action { var elements: seq @@ -61,11 +62,96 @@ module Actions { 0 } - twostate predicate Ensures(t: T, nothing: ()) { + predicate Ensures(t: T, nothing: ()) { true } } + class FunctionAsAction extends Action { + const f: T ~> R + const e: (T, R) ~> bool + + constructor(f: T ~> R, e: (T, R) ~> bool) { + this.f := f; + this.e := e; + } + + predicate Valid() reads this, Repr { + // TODO: This is what we want but can't because we're not using (!new) + // forall t: T :: e(t, f(t)) + true + } + + method Invoke(t: T) returns (r: R) + requires Valid() + requires Requires(t) + modifies Modifies(t) + decreases Decreases(t) + ensures ValidAndDisjoint() + ensures old(allocated(r)) && Ensures(t, r) + { + r := f(t); + } + + predicate Requires(t: T) { + f.requires(t) + } + + function Modifies(t: T): set { + {} + } + + function Decreases(t: T): nat { + 0 + } + + predicate Ensures(t: T, r: R) { + e(t, r) + } + } + + class ComposedAction extends Action { + const inner: Action + const outer: Action + + constructor(inner: Action, outer: Action) { + this.inner := inner; + this.outer := outer; + } + + predicate Valid() reads this, Repr { + true + } + + method Invoke(t: T) returns (r: R) + requires Valid() + requires Requires(t) + modifies Modifies(t) + decreases Decreases(t) + ensures ValidAndDisjoint() + ensures old(allocated(r)) && Ensures(t, r) + { + var m := inner.Invoke(t); + r := outer.Invoke(m); + } + + predicate Requires(t: T) { + inner.Requires(t) + } + + function Modifies(t: T): set { + set m: M, o: object | inner.Ensures(t, m) && o in outer.Modifies(m) :: o + } + + function Decreases(t: T): nat { + inner.Decreases(t) + } + + predicate Ensures(t: T, r: R) { + exists m: M :: inner.Ensures(t, m) && outer.Ensures(m, r) + } + } + class InvokeAction extends Action, ()> { predicate Valid() reads this, Repr { @@ -87,7 +173,7 @@ module Actions { a.Decreases(42) < Decreases(a) } - function Modifies(a: Action): set { + function Modifies(a: Action): set requires Requires(a) { a.Modifies(42) } @@ -95,7 +181,7 @@ module Actions { 0 } - twostate predicate Ensures(a: Action, nothing: ()) { + predicate Ensures(a: Action, nothing: ()) { true } } diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy index 867bcf8e..33864592 100644 --- a/src/Enumeration/Iterators.dfy +++ b/src/Enumeration/Iterators.dfy @@ -6,7 +6,7 @@ module IteratorExperiments { import opened Frames iterator ForEachWithIndex(s: seq) yields (element: T, index: nat) - ensures elements <= s + ensures elements == s yield ensures elements <= s { for i := 0 to |s| @@ -21,29 +21,51 @@ module IteratorExperiments { method MoveNext() returns (more: bool) requires Valid() + requires !Done() modifies Repr - ensures fresh(Repr - old(Repr)) - ensures more ==> Valid() - ensures more ==> Decreases() < old(Decreases()) + decreases Repr + ensures ValidAndDisjoint() + ensures more <==> !Done() + ensures !Done() ==> Decreases() < old(Decreases()) function method Current(): T reads this, Repr requires Valid() + requires !Done() + predicate Done() + requires Valid() + reads this, Repr + + // Needs a better name, it's only a measure that is USED + // in decreases clauses but not the same thing. + // Although decreases is already overloaded to mean slightly + // different things for a method spec vs. a while loop spec. + // NOT what decreases means on an iterator though. function Decreases(): nat reads this, Repr requires Valid() + + // Should this be forced on every enumerator? Or provided on + // an Enumerator wrapper instead, since it's implementable generically? + // ghost var enumerated: set } class ForEachWithIndexEnumerator extends Enumerator<(T, nat)> { const iter: ForEachWithIndex ghost var remaining: nat + ghost var done: bool - constructor(s: seq) ensures Valid() ensures fresh(Repr) { + constructor(s: seq) + ensures Valid() + ensures fresh(Repr) + ensures !Done() + { iter := new ForEachWithIndex(s); new; - remaining := |iter.s|; + done := false; + remaining := |iter.s| - |iter.elements|; Repr := {this, iter}; } @@ -56,21 +78,28 @@ module IteratorExperiments { && iter._modifies <= Repr && iter._reads <= Repr && iter._new <= Repr - && iter.Valid() + && (!done ==> iter.Valid()) && remaining == |iter.s| - |iter.elements| } + predicate Done() + requires Valid() + reads this, Repr + { + done + } + method MoveNext() returns (more: bool) requires Valid() + requires !Done() modifies Repr - ensures fresh(Repr - old(Repr)) - ensures more ==> Valid() - ensures more ==> Decreases() < old(Decreases()) + decreases Repr + ensures ValidAndDisjoint() + ensures more <==> !Done() + ensures !Done() ==> Decreases() < old(Decreases()) { more := iter.MoveNext(); - if (more) { - assert old(iter.elements) < iter.elements; - } + done := !more; remaining := |iter.s| - |iter.elements|; Repr := {this, iter} + iter._modifies + iter._reads + iter._new; } @@ -82,11 +111,84 @@ module IteratorExperiments { (iter.element, iter.index) } - function Decreases(): nat reads this, Repr requires Valid() { + function Decreases(): nat + reads this, Repr + requires Valid() + { remaining } } + // class Filter extends Enumerator { + // const wrapped: Enumerator + // const filter: T -> bool + // var current: T + + // constructor(wrapped: Enumerator, filter: T -> bool) + // requires wrapped.Valid() + // ensures Valid() + // { + // this.wrapped := wrapped; + // this.filter := filter; + // Repr := {this} + wrapped.Repr; + // } + + // predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + // && this in Repr + // && ValidComponent(wrapped) + // } + + // predicate Done() + // requires Valid() + // reads this, Repr + // { + // wrapped.Done() + // } + + // method MoveNext() returns (more: bool) + // requires Valid() + // requires !Done() + // modifies Repr + // decreases Repr + // ensures ValidAndDisjoint() + // ensures more <==> !Done() + // ensures !Done() ==> Decreases() < old(Decreases()) + // { + // more := true; + // while (true) + // invariant Valid() + // invariant old(allocated(wrapped)) && fresh(wrapped.Repr - old(wrapped.Repr)) + // decreases more, wrapped.Decreases() + // { + // assert wrapped.Repr < Repr; + // assert !wrapped.Done(); + // more := wrapped.MoveNext(); + // Repr := Repr + wrapped.Repr; + + // if (!more) { break; } + + // current := wrapped.Current(); + // if (filter(current)) { + // break; + // } + // } + // } + + // function method Current(): T + // reads this, Repr + // requires Valid() + // { + // current + // } + + // function Decreases(): nat + // reads this, Repr + // requires Valid() + // { + // wrapped.Decreases() + // } + // } + method Main() { var s: seq := [1, 1, 2, 3, 5, 8]; var e: Enumerator<(nat, nat)> := new ForEachWithIndexEnumerator(s); @@ -95,17 +197,38 @@ module IteratorExperiments { var more := true; while (true) invariant e.Valid() + invariant more <==> !e.Done() invariant old@start(allocated(e)) && fresh(e.Repr) decreases e.Decreases() { more := e.MoveNext(); if (!more) { break; } - var (element, index) := e.Current(); + print "Index: ", index, ", Element: ", element; } } + datatype E = Done | Next(T, () -> E) + + function OneTwoThree(): E { + Next(1, () => Next(2, () => Next(3, () => Done))) + } + + function CountdownFrom(n: nat): E { + if n > 0 then + Next(n, () => CountdownFrom(n - 1)) + else + Done + } + + function CountupFrom(n: nat): E { + if n > 0 then + Next(n, () => CountdownFrom(n + 1)) + else + Done + } + // iterator Iter(s: set) yields (x: T) // yield ensures x in s && x !in xs[..|xs|-1]; // ensures s == set z | z in xs; diff --git a/src/Enumeration/JavaStyle.dfy b/src/Enumeration/JavaStyle.dfy new file mode 100644 index 00000000..2ee3837b --- /dev/null +++ b/src/Enumeration/JavaStyle.dfy @@ -0,0 +1,55 @@ + +include "../Frames.dfy" + +module JavaStyle { + + import opened Frames + + class JavaStyleIterator { + const s: seq + var index: nat + + constructor(s: seq) + ensures Valid() + { + this.s := s; + this.index := 0; + } + + predicate Valid() + reads this + { + && 0 <= index <= |s| + } + + function method HasNext(): (res: bool) + reads this + ensures res == (index < |s|) + { + index < |s| + } + + method Next() returns (res: T) + requires Valid() + requires HasNext() + modifies this + // ensures Valid() + ensures index == old(index) + 1 + { + res := s[index]; + index := index + 1; + } + } + + method Main() { + var s := [1, 2, 3, 4, 5]; + var iter := new JavaStyleIterator(s); + while (iter.HasNext()) + decreases |iter.s| - iter.index + { + var n := iter.Next(); + print n; + } + } + +} \ No newline at end of file diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index 299a2941..28e41faf 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -24,21 +24,16 @@ module Loopers { } trait TerminatingLooper extends Validatable { - // Will need to be arbitrary termination clause somehow instead + // Would be better as an arbitrary termination clause somehow instead // https://github.com/dafny-lang/dafny/issues/762 - // TODO: split up the ghost version from the version called at runtime? - // Implementor needs to use this to prove termination, but caller - // only needs Done() - function method Decreases(): nat + function Decreases(): nat reads Repr decreases Repr requires Valid() predicate method Done() reads Repr requires Valid() - { - Decreases() == 0 - } + ensures Decreases() == 0 ==> Done() method Step() requires Valid() @@ -55,9 +50,11 @@ module Loopers { ensures l.Valid() ensures l.Done() { - while (l.Decreases() > 0) + while (!l.Done()) invariant l.Valid() + // Could this be a more general Ensures or Invariant? invariant fresh(l.Repr - old(l.Repr)) + decreases l.Decreases() { l.Step(); } @@ -68,7 +65,10 @@ module Loopers { const elements: seq var index: nat - constructor(s: seq) { + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + { elements := s; index := 0; Repr := {this}; @@ -83,13 +83,21 @@ module Loopers { && 0 <= index <= |elements| } - function method Decreases(): nat + function Decreases(): nat reads Repr requires Valid() { |elements| - index } + predicate method Done() + reads Repr + requires Valid() + ensures Decreases() == 0 ==> Done() + { + index == |elements| + } + method Step() requires Valid() requires !Done() @@ -102,13 +110,33 @@ module Loopers { } } - class ForEach extends TerminatingLooper { + // TODO: Some similarities between this and composing + // two Actions together. + // One is: + // var r := action1.Invoke(a); + // var r2 := action2.Invoke(r); + // Other is: + // var r := looper.Step(); + // var _ := action.Invoke(r); + // var r2 := looper.Step(); + // var _ := action.Invoke(r2); + // ... + + class ForEach extends TerminatingLooper { const iter: TerminatingLooper const body: Action - constructor(iter: TerminatingLooper, body: Action) { + constructor(iter: TerminatingLooper, body: Action) + requires iter.Valid() + requires body.Valid() + requires iter.Repr !! body.Repr + requires iter.Valid() ==> body.Requires(iter) + requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr + ensures Valid() + { this.iter := iter; this.body := body; + Repr := {this} + iter.Repr + body.Repr; } predicate Valid() @@ -121,19 +149,28 @@ module Loopers { && ValidComponent(body) && iter.Repr !! body.Repr && (iter.Valid() ==> body.Requires(iter)) - && (iter.Valid() ==> body.Modifies(iter) == {}) + // TODO: This needs to be a forall somehow + && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) } - function method Decreases(): nat + function Decreases(): nat reads Repr requires Valid() { iter.Decreases() } + predicate method Done() + reads Repr + requires Valid() + ensures Decreases() == 0 ==> Done() + { + iter.Done() + } + method Step() requires Valid() - requires Decreases() > 0 + requires !Done() modifies Repr decreases Repr ensures ValidAndDisjoint() @@ -141,11 +178,78 @@ module Loopers { { iter.Step(); Repr := Repr + iter.Repr; + + assert iter.Valid(); + assert this in Repr; + assert ValidComponent(iter); + assert ValidComponent(body); + assert iter.Repr !! body.Repr; + assert (iter.Valid() ==> body.Requires(iter)); + assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); + assert Valid(); var _ := body.Invoke(iter); - Repr := Repr + body.Repr; + Repr := Repr + body.Repr; } } - + class SeqCollector extends Action { + + var elements: seq + + constructor() + ensures Valid() + ensures fresh(Repr - {this}) + { + elements := []; + Repr := {this}; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + } + + method Invoke(t: T) returns (nothing: ()) + requires Valid() + requires Requires(t) + modifies Modifies(t) + decreases Decreases(t) + ensures ValidAndDisjoint() + ensures old(allocated(())) && Ensures(t, ()) + { + elements := elements + [t]; + } + + predicate Requires(t: T) { + true + } + + // Need this separately from Repr for callers + // Repr is the frame for Valid(), but callers + // need to know what ELSE gets modified. + function Modifies(t: T): set requires Requires(t) { + {this} + } + + function Decreases(t: T): nat { + 0 + } + + twostate predicate Ensures(t: T, nothing: ()) { + true + } + } + + + method Main() { + var numbers := [1, 2, 3, 4, 5]; + var numbersIter := new SeqLooper(numbers); + var numbersPrinterLooper := new SeqCollector(); + var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); + DoLoop(forEachLoop); + } } \ No newline at end of file From 2545eaea97423f824bd15197aabf2345c9480515 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 9 Feb 2022 11:32:50 -0800 Subject: [PATCH 04/68] More partial work --- src/Enumeration/DecreasesWeirdness.dfy | 49 ++++++++++ src/Enumeration/Iterators.dfy | 129 +++++++++++++------------ 2 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 src/Enumeration/DecreasesWeirdness.dfy diff --git a/src/Enumeration/DecreasesWeirdness.dfy b/src/Enumeration/DecreasesWeirdness.dfy new file mode 100644 index 00000000..0ea3582c --- /dev/null +++ b/src/Enumeration/DecreasesWeirdness.dfy @@ -0,0 +1,49 @@ +module DecreasesWeirdness { + + trait Thing { + var Repr: set + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + method Foo() + requires Valid() + decreases Repr + } + + class MyThing extends Thing { + var d: int + var wrapped: Thing + constructor(wrapped: Thing) + { + this.wrapped := wrapped; + Repr := {this} + wrapped.Repr; + } + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + { + && this in Repr + && wrapped in Repr + && wrapped.Repr <= Repr + && this !in wrapped.Repr + && wrapped.Valid() + } + + method Foo() + requires Valid() + decreases Repr + { + var n := 5; + while (n > 0) decreases n + { + assert wrapped.Repr < Repr; + wrapped.Foo(); + print n; + n := n - 1; + } + } + } + + + +} \ No newline at end of file diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy index 33864592..bff9ab4b 100644 --- a/src/Enumeration/Iterators.dfy +++ b/src/Enumeration/Iterators.dfy @@ -28,6 +28,8 @@ module IteratorExperiments { ensures more <==> !Done() ensures !Done() ==> Decreases() < old(Decreases()) + // TODO: Still a problem here with calling `Current` + // on a fresh enumerator without first calling `MoveNext` function method Current(): T reads this, Repr requires Valid() @@ -119,75 +121,74 @@ module IteratorExperiments { } } - // class Filter extends Enumerator { - // const wrapped: Enumerator - // const filter: T -> bool - // var current: T + class Filter extends Enumerator { + const wrapped: Enumerator + const filter: T -> bool + var current: T - // constructor(wrapped: Enumerator, filter: T -> bool) - // requires wrapped.Valid() - // ensures Valid() - // { - // this.wrapped := wrapped; - // this.filter := filter; - // Repr := {this} + wrapped.Repr; - // } + constructor(wrapped: Enumerator, filter: T -> bool) + requires wrapped.Valid() + ensures Valid() + { + this.wrapped := wrapped; + this.filter := filter; + Repr := {this} + wrapped.Repr; + } - // predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - // && this in Repr - // && ValidComponent(wrapped) - // } + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && ValidComponent(wrapped) + } - // predicate Done() - // requires Valid() - // reads this, Repr - // { - // wrapped.Done() - // } + predicate Done() + requires Valid() + reads this, Repr + { + wrapped.Done() + } - // method MoveNext() returns (more: bool) - // requires Valid() - // requires !Done() - // modifies Repr - // decreases Repr - // ensures ValidAndDisjoint() - // ensures more <==> !Done() - // ensures !Done() ==> Decreases() < old(Decreases()) - // { - // more := true; - // while (true) - // invariant Valid() - // invariant old(allocated(wrapped)) && fresh(wrapped.Repr - old(wrapped.Repr)) - // decreases more, wrapped.Decreases() - // { - // assert wrapped.Repr < Repr; - // assert !wrapped.Done(); - // more := wrapped.MoveNext(); - // Repr := Repr + wrapped.Repr; - - // if (!more) { break; } - - // current := wrapped.Current(); - // if (filter(current)) { - // break; - // } - // } - // } + method MoveNext() returns (more: bool) + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures more <==> !Done() + ensures !Done() ==> Decreases() < old(Decreases()) + { + more := true; + while (true) + invariant Valid() + invariant more <==> !wrapped.Done() + invariant old(allocated(wrapped)) && fresh(wrapped.Repr - old(wrapped.Repr)) + decreases more, wrapped.Decreases() + { + assert wrapped.Repr < Repr; // should satify the method decreases clause... + more := wrapped.MoveNext(); // wrong decreases clauses applied? + Repr := Repr + wrapped.Repr; + if (!more) { break; } + + current := wrapped.Current(); + if (filter(current)) { + break; + } + } + } - // function method Current(): T - // reads this, Repr - // requires Valid() - // { - // current - // } + function method Current(): T + reads this, Repr + requires Valid() + { + current + } - // function Decreases(): nat - // reads this, Repr - // requires Valid() - // { - // wrapped.Decreases() - // } - // } + function Decreases(): nat + reads this, Repr + requires Valid() + { + wrapped.Decreases() + } + } method Main() { var s: seq := [1, 1, 2, 3, 5, 8]; @@ -205,7 +206,7 @@ module IteratorExperiments { if (!more) { break; } var (element, index) := e.Current(); - print "Index: ", index, ", Element: ", element; + print "Index: ", index, ", Element: ", element, "\n"; } } From 6baa11f5bdad57bb2f1e5c185c2fbbedd890c277 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Fri, 11 Feb 2022 16:29:32 -0800 Subject: [PATCH 05/68] Stepper approach seems to work better! --- src/Enumeration/Iterators.dfy | 328 ++++++++++++++++++++++++----- src/Enumeration/Loopers.dfy | 383 +++++++++++++++++++++++----------- 2 files changed, 540 insertions(+), 171 deletions(-) diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy index bff9ab4b..7378729f 100644 --- a/src/Enumeration/Iterators.dfy +++ b/src/Enumeration/Iterators.dfy @@ -1,24 +1,30 @@ include "../Frames.dfy" +include "../Wrappers.dfy" +include "../Collections/Sequences/Seq.dfy" module IteratorExperiments { import opened Frames + import opened Wrappers - iterator ForEachWithIndex(s: seq) yields (element: T, index: nat) - ensures elements == s - yield ensures elements <= s - { - for i := 0 to |s| - invariant i == |elements| - invariant elements <= s - { - yield s[i], i; - } - } + import Seq + // Would like to make this extend the even more general Looper + // concept, but tough to use without bounded polymorphism. trait Enumerator extends Validatable { + // Any enumerator that produces one value at a time + // and provably terminates is equivalent to an enumerator + // that produces a specific seq. This value may be underspecified + // such that it is not known, even its length, until after all + // values have been produced. + // Dafny doesn't let you pass around an underspecified value though, + // so we don't define a "to be enumerated" field or function. + ghost var enumerated: seq + + // Valid() is used as the enumeration invariant + method MoveNext() returns (more: bool) requires Valid() requires !Done() @@ -26,38 +32,98 @@ module IteratorExperiments { decreases Repr ensures ValidAndDisjoint() ensures more <==> !Done() + ensures !Done() ==> HasCurrent() ensures !Done() ==> Decreases() < old(Decreases()) + ensures !Done() ==> enumerated == old(enumerated) + [Current()] - // TODO: Still a problem here with calling `Current` - // on a fresh enumerator without first calling `MoveNext` - function method Current(): T + predicate HasCurrent() reads this, Repr requires Valid() - requires !Done() - predicate Done() - requires Valid() + function method Current(): T reads this, Repr + requires Valid() + requires HasCurrent() - // Needs a better name, it's only a measure that is USED - // in decreases clauses but not the same thing. - // Although decreases is already overloaded to mean slightly - // different things for a method spec vs. a while loop spec. - // NOT what decreases means on an iterator though. function Decreases(): nat reads this, Repr requires Valid() - // Should this be forced on every enumerator? Or provided on - // an Enumerator wrapper instead, since it's implementable generically? - // ghost var enumerated: set + predicate method Done() + requires Valid() + reads this, Repr + } + + class SeqLooper extends TerminatingLooper { + + const elements: seq + var index: nat + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + { + elements := s; + index := 0; + Repr := {this}; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + && 0 <= index <= |elements| + } + + function Decreases(): nat + reads Repr + requires Valid() + { + |elements| - index + } + + predicate method Done() + reads Repr + requires Valid() + ensures Decreases() == 0 ==> Done() + { + index == |elements| + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + { + index := index + 1; + } + } + } + + iterator ForEachWithIndex(s: seq) yields (element: T, index: nat) + yield ensures elements <= s + ensures elements == s + { + for i := 0 to |s| + invariant i == |elements| + invariant elements <= s + { + yield s[i], i; + } } class ForEachWithIndexEnumerator extends Enumerator<(T, nat)> { const iter: ForEachWithIndex - ghost var remaining: nat - ghost var done: bool + var done: bool + + ghost const original: seq + ghost var hasCurrent: bool constructor(s: seq) ensures Valid() @@ -65,9 +131,10 @@ module IteratorExperiments { ensures !Done() { iter := new ForEachWithIndex(s); + original := s; new; done := false; - remaining := |iter.s| - |iter.elements|; + hasCurrent := false; Repr := {this, iter}; } @@ -81,12 +148,13 @@ module IteratorExperiments { && iter._reads <= Repr && iter._new <= Repr && (!done ==> iter.Valid()) - && remaining == |iter.s| - |iter.elements| + && enumerated == Seq.Zip(original[0..|enumerated|], Range(0, |enumerated|)) } - predicate Done() + predicate method Done() requires Valid() reads this, Repr + ensures Done() ==> enumerated == Seq.Zip(original, Range(0, |original|)) { done } @@ -98,12 +166,25 @@ module IteratorExperiments { decreases Repr ensures ValidAndDisjoint() ensures more <==> !Done() + ensures !Done() ==> HasCurrent() ensures !Done() ==> Decreases() < old(Decreases()) + ensures !Done() ==> enumerated == old(enumerated) + [Current()] { more := iter.MoveNext(); done := !more; - remaining := |iter.s| - |iter.elements|; + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + if more { + hasCurrent := true; + enumerated := enumerated + [Current()]; + } + } + + predicate HasCurrent() + reads this, Repr + requires Valid() + { + hasCurrent } function method Current(): (T, nat) @@ -117,13 +198,24 @@ module IteratorExperiments { reads this, Repr requires Valid() { - remaining + |original| - |enumerated| } } + // TODO: move to Seq.dfy? + function Range(start: int, end: int): seq + requires start <= end + decreases end - start + { + var length := end - start; + seq(length, i requires 0 <= i < length => start + i) + } + + // TODO: Prove the semantics! class Filter extends Enumerator { const wrapped: Enumerator const filter: T -> bool + var hasCurrent: bool var current: T constructor(wrapped: Enumerator, filter: T -> bool) @@ -140,7 +232,7 @@ module IteratorExperiments { && ValidComponent(wrapped) } - predicate Done() + predicate method Done() requires Valid() reads this, Repr { @@ -154,27 +246,40 @@ module IteratorExperiments { decreases Repr ensures ValidAndDisjoint() ensures more <==> !Done() + ensures !Done() ==> HasCurrent() ensures !Done() ==> Decreases() < old(Decreases()) + ensures !Done() ==> enumerated == old(enumerated) + [Current()] { + assert wrapped.Repr < Repr; more := true; while (true) invariant Valid() invariant more <==> !wrapped.Done() - invariant old(allocated(wrapped)) && fresh(wrapped.Repr - old(wrapped.Repr)) + modifies Repr decreases more, wrapped.Decreases() { - assert wrapped.Repr < Repr; // should satify the method decreases clause... - more := wrapped.MoveNext(); // wrong decreases clauses applied? + assert wrapped.Repr < old(Repr); + more := wrapped.MoveNext(); Repr := Repr + wrapped.Repr; if (!more) { break; } + assert Valid(); + assert wrapped.HasCurrent(); current := wrapped.Current(); if (filter(current)) { + enumerated := enumerated + [Current()]; break; } } } + predicate HasCurrent() + reads this, Repr + requires Valid() + { + hasCurrent + } + function method Current(): T reads this, Repr requires Valid() @@ -188,8 +293,131 @@ module IteratorExperiments { { wrapped.Decreases() } + + predicate DoneProperty() + requires Valid() + requires Done() + reads this, Repr + { + enumerated == wrapped.enumerated + } + } + + // (0) is necessary because we can't just use an Option to + // hold the current value - the spec doesn't prevent you from constructing + // this and then calling Current() before calling MoveNext() + // TODO: multiset version as well? Instead? + class SetEnumerator extends Enumerator { + ghost const original: set + var remaining: set + var current: T + var hasCurrent: bool + var done: bool + + constructor(s: set) + ensures Valid() + ensures fresh(Repr) + { + this.original := s; + this.remaining := s; + this.done := false; + + enumerated := []; + Repr := {this}; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + } + + predicate method Done() + requires Valid() + reads this, Repr + { + done + } + + method MoveNext() returns (more: bool) + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures more <==> !Done() + ensures more ==> HasCurrent() + ensures !Done() ==> Decreases() < old(Decreases()) + ensures !Done() ==> enumerated == old(enumerated) + [Current()] + { + if |remaining| == 0 { + done := true; + more := false; + } else { + var c :| c in remaining; + current := c; + hasCurrent := true; + enumerated := enumerated + [current]; + remaining := remaining - {c}; + more := true; + } + } + + predicate HasCurrent() + reads this, Repr + requires Valid() + { + hasCurrent + } + + function method Current(): T + reads this, Repr + requires Valid() + requires HasCurrent() + { + current + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + |remaining| + } + + predicate DoneProperty() + requires Valid() + requires Done() + reads this, Repr + { + multiset(enumerated) == multiset(original) + } } + // method Max(s: set) returns (max: int) + // requires |s| > 0 + // ensures max in s + // ensures forall x | x in s :: max >= x + // { + // var first: int :| first in s; + // max := first; + // var sEnum: SetEnumerator := new SetEnumerator(s - {max}); + // assert fresh(sEnum.Repr); + // label start: + // var more := true; + // while (!sEnum.Done()) + // invariant sEnum.Valid() + // invariant fresh(sEnum.Repr) + // invariant max == Seq.Max([first] + sEnum.enumerated) + // decreases more, sEnum.Decreases() + // { + // var more := sEnum.MoveNext(); + // if !more { break; } + + // if sEnum.Current() > max { + // max := sEnum.Current(); + // } + // } + // } + method Main() { var s: seq := [1, 1, 2, 3, 5, 8]; var e: Enumerator<(nat, nat)> := new ForEachWithIndexEnumerator(s); @@ -210,25 +438,25 @@ module IteratorExperiments { } } - datatype E = Done | Next(T, () -> E) + datatype E = Done | Next(T, Enum) + type Enum = () -> E - function OneTwoThree(): E { - Next(1, () => Next(2, () => Next(3, () => Done))) + function OneTwoThree(): Enum { + () => Next(1, () => Next(2, () => Next(3, () => Done))) } - function CountdownFrom(n: nat): E { - if n > 0 then - Next(n, () => CountdownFrom(n - 1)) - else - Done + function CountdownFrom(n: nat): Enum { + () => + if n > 0 then + Next(n, CountdownFrom(n - 1)) + else + Done } - function CountupFrom(n: nat): E { - if n > 0 then - Next(n, () => CountdownFrom(n + 1)) - else - Done - } + // Doesn't terminate so you can't do this + // function CountupFrom(n: nat): Enum { + // () => Next(n, CountupFrom(n + 1)) + // } // iterator Iter(s: set) yields (x: T) // yield ensures x in s && x !in xs[..|xs|-1]; diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index 28e41faf..e0704dff 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -1,21 +1,22 @@ -include "../Actions.dfy" +// include "../Actions.dfy" include "../Frames.dfy" +include "../Wrappers.dfy" module Loopers { - import opened Actions + // import opened Actions import opened Frames + import opened Wrappers - trait Looper { - predicate Invariant() + trait InfiniteStepper extends Validatable { method Step() - requires Invariant() - ensures Invariant() + requires Valid() + ensures Valid() } - method DoLoopStar(l: Looper) - requires l.Invariant() + method DoLoopStar(l: InfiniteStepper) + requires l.Valid() decreases * { while (true) decreases * { @@ -23,44 +24,71 @@ module Loopers { } } - trait TerminatingLooper extends Validatable { + trait Stepper extends Validatable { + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + // Would be better as an arbitrary termination clause somehow instead // https://github.com/dafny-lang/dafny/issues/762 function Decreases(): nat reads Repr decreases Repr requires Valid() + predicate method Done() reads Repr requires Valid() + decreases Repr, 0 ensures Decreases() == 0 ==> Done() - - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures Decreases() < old(Decreases()) } - method DoLoop(l: TerminatingLooper) + method DoLoop(l: Stepper) requires l.Valid() modifies l.Repr ensures l.Valid() ensures l.Done() { while (!l.Done()) - invariant l.Valid() - // Could this be a more general Ensures or Invariant? - invariant fresh(l.Repr - old(l.Repr)) + invariant l.ValidAndDisjoint() decreases l.Decreases() { l.Step(); } } - class SeqLooper extends TerminatingLooper { + trait Enumerator extends Stepper { + + // Any enumerator that produces one value at a time + // and provably terminates is equivalent to an enumerator + // that produces a specific seq. This value may be underspecified + // such that it is not known, even its length, until after all + // values have been produced. + // Dafny doesn't let you pass around an underspecified value though, + // so we don't define a "to be enumerated" field or function. + + ghost var enumerated: seq + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + } + + class SeqEnumerator extends Enumerator { const elements: seq var index: nat @@ -71,6 +99,8 @@ module Loopers { { elements := s; index := 0; + + enumerated := []; Repr := {this}; } @@ -81,6 +111,7 @@ module Loopers { { && this in Repr && 0 <= index <= |elements| + && enumerated == elements[0..index] } function Decreases(): nat @@ -93,7 +124,9 @@ module Loopers { predicate method Done() reads Repr requires Valid() + decreases Repr, 0 ensures Decreases() == 0 ==> Done() + ensures Done() ==> enumerated == elements { index == |elements| } @@ -105,151 +138,259 @@ module Loopers { decreases Repr ensures ValidAndDisjoint() ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] { + enumerated := enumerated + [Current()]; index := index + 1; } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + elements[index] + } } - // TODO: Some similarities between this and composing - // two Actions together. - // One is: - // var r := action1.Invoke(a); - // var r2 := action2.Invoke(r); - // Other is: - // var r := looper.Step(); - // var _ := action.Invoke(r); - // var r2 := looper.Step(); - // var _ := action.Invoke(r2); - // ... - - class ForEach extends TerminatingLooper { - const iter: TerminatingLooper - const body: Action - - constructor(iter: TerminatingLooper, body: Action) - requires iter.Valid() - requires body.Valid() - requires iter.Repr !! body.Repr - requires iter.Valid() ==> body.Requires(iter) - requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr + // Wraps an enumerator that doesn't know if it has another value + // until it tries to get the next value. + // TODO: Equivalent to Filter? + class TerminatedEnumerator extends Enumerator { + const wrapped: Enumerator> + + constructor(wrapped: Enumerator>) + requires wrapped.Valid() ensures Valid() + ensures fresh(Repr - {this} - wrapped.Repr) { - this.iter := iter; - this.body := body; - Repr := {this} + iter.Repr + body.Repr; + this.wrapped := wrapped; + enumerated := []; + Repr := {this} + wrapped.Repr; } predicate Valid() - reads this, Repr + reads this, Repr + ensures Valid() ==> this in Repr decreases Repr - ensures Valid() ==> this in Repr { && this in Repr - && ValidComponent(iter) - && ValidComponent(body) - && iter.Repr !! body.Repr - && (iter.Valid() ==> body.Requires(iter)) - // TODO: This needs to be a forall somehow - && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) + && ValidComponent(wrapped) } - function Decreases(): nat + function Decreases(): nat reads Repr requires Valid() { - iter.Decreases() + wrapped.Decreases() } predicate method Done() reads Repr requires Valid() + decreases Repr, 0 ensures Decreases() == 0 ==> Done() { - iter.Done() + wrapped.Done() || wrapped.Current().None? } - method Step() + method Step() requires Valid() requires !Done() modifies Repr decreases Repr ensures ValidAndDisjoint() ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] { - iter.Step(); - Repr := Repr + iter.Repr; - - assert iter.Valid(); - assert this in Repr; - assert ValidComponent(iter); - assert ValidComponent(body); - assert iter.Repr !! body.Repr; - assert (iter.Valid() ==> body.Requires(iter)); - assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); - assert Valid(); - - var _ := body.Invoke(iter); - Repr := Repr + body.Repr; + enumerated := enumerated + [Current()]; + wrapped.Step(); + Repr := {this} + wrapped.Repr; } - } - - class SeqCollector extends Action { - var elements: seq - - constructor() - ensures Valid() - ensures fresh(Repr - {this}) + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() { - elements := []; - Repr := {this}; + wrapped.Current().value } + } - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr + method Example1() { + var numbers := [1, 2, 3, 4, 5]; + var e: Enumerator := new SeqEnumerator(numbers); + label start: + while (!e.Done()) + invariant e.Valid() + invariant old@start(allocated(e)) && fresh(e.Repr) + decreases e.Decreases() { - && this in Repr + print e.Current(), "\n"; + e.Step(); } + } - method Invoke(t: T) returns (nothing: ()) - requires Valid() - requires Requires(t) - modifies Modifies(t) - decreases Decreases(t) - ensures ValidAndDisjoint() - ensures old(allocated(())) && Ensures(t, ()) + method Example2() { + var maybeNumbers := [Some(1), Some(2), Some(3), None, None]; + var e: Enumerator> := new SeqEnumerator(maybeNumbers); + var e': Enumerator := new TerminatedEnumerator(e); + label start: + while (!e'.Done()) + invariant e'.Valid() + invariant old@start(allocated(e')) && fresh(e'.Repr) + decreases e'.Decreases() { - elements := elements + [t]; - } - - predicate Requires(t: T) { - true - } - - // Need this separately from Repr for callers - // Repr is the frame for Valid(), but callers - // need to know what ELSE gets modified. - function Modifies(t: T): set requires Requires(t) { - {this} - } - - function Decreases(t: T): nat { - 0 - } - - twostate predicate Ensures(t: T, nothing: ()) { - true + print e'.Current(), "\n"; + e'.Step(); } } - method Main() { - var numbers := [1, 2, 3, 4, 5]; - var numbersIter := new SeqLooper(numbers); - var numbersPrinterLooper := new SeqCollector(); - var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); - DoLoop(forEachLoop); + Example1(); + Example2(); } + + // TODO: Some similarities between this and composing + // two Actions together. + // One is: + // var r := action1.Invoke(a); + // var r2 := action2.Invoke(r); + // Other is: + // var r := looper.Step(); + // var _ := action.Invoke(r); + // var r2 := looper.Step(); + // var _ := action.Invoke(r2); + // ... + + // class ForEach extends TerminatingLooper { + // const iter: TerminatingLooper + // const body: Action + + // constructor(iter: TerminatingLooper, body: Action) + // requires iter.Valid() + // requires body.Valid() + // requires iter.Repr !! body.Repr + // requires iter.Valid() ==> body.Requires(iter) + // requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr + // ensures Valid() + // { + // this.iter := iter; + // this.body := body; + // Repr := {this} + iter.Repr + body.Repr; + // } + + // predicate Valid() + // reads this, Repr + // decreases Repr + // ensures Valid() ==> this in Repr + // { + // && this in Repr + // && ValidComponent(iter) + // && ValidComponent(body) + // && iter.Repr !! body.Repr + // && (iter.Valid() ==> body.Requires(iter)) + // // TODO: This needs to be a forall somehow + // && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) + // } + + // function Decreases(): nat + // reads Repr + // requires Valid() + // { + // iter.Decreases() + // } + + // predicate method Done() + // reads Repr + // requires Valid() + // ensures Decreases() == 0 ==> Done() + // { + // iter.Done() + // } + + // method Step() + // requires Valid() + // requires !Done() + // modifies Repr + // decreases Repr + // ensures ValidAndDisjoint() + // ensures Decreases() < old(Decreases()) + // { + // iter.Step(); + // Repr := Repr + iter.Repr; + + // assert iter.Valid(); + // assert this in Repr; + // assert ValidComponent(iter); + // assert ValidComponent(body); + // assert iter.Repr !! body.Repr; + // assert (iter.Valid() ==> body.Requires(iter)); + // assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); + // assert Valid(); + + // var _ := body.Invoke(iter); + // Repr := Repr + body.Repr; + // } + // } + + // class SeqCollector extends Action { + + // var elements: seq + + // constructor() + // ensures Valid() + // ensures fresh(Repr - {this}) + // { + // elements := []; + // Repr := {this}; + // } + + // predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // decreases Repr + // { + // && this in Repr + // } + + // method Invoke(t: T) returns (nothing: ()) + // requires Valid() + // requires Requires(t) + // modifies Modifies(t) + // decreases Decreases(t) + // ensures ValidAndDisjoint() + // ensures old(allocated(())) && Ensures(t, ()) + // { + // elements := elements + [t]; + // } + + // predicate Requires(t: T) { + // true + // } + + // // Need this separately from Repr for callers + // // Repr is the frame for Valid(), but callers + // // need to know what ELSE gets modified. + // function Modifies(t: T): set requires Requires(t) { + // {this} + // } + + // function Decreases(t: T): nat { + // 0 + // } + + // twostate predicate Ensures(t: T, nothing: ()) { + // true + // } + // } + + + // method Main() { + // var numbers := [1, 2, 3, 4, 5]; + // var numbersIter := new SeqLooper(numbers); + // var numbersPrinterLooper := new SeqCollector(); + // var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); + // DoLoop(forEachLoop); + // } } \ No newline at end of file From 73cc542a337c9ac3233126c6155a1bc56be98e69 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 13 Feb 2022 10:39:06 -0800 Subject: [PATCH 06/68] More partially-implemented enumerators --- src/Enumeration/Iterators.dfy | 83 ++++++----- src/Enumeration/Loopers.dfy | 260 +++++++++++++++++++++++++++++++++- 2 files changed, 298 insertions(+), 45 deletions(-) diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy index 7378729f..57cd98cc 100644 --- a/src/Enumeration/Iterators.dfy +++ b/src/Enumeration/Iterators.dfy @@ -54,54 +54,53 @@ module IteratorExperiments { reads this, Repr } - class SeqLooper extends TerminatingLooper { + class SeqEnumerator extends Enumerator { - const elements: seq - var index: nat + const elements: seq + var index: nat - constructor(s: seq) - ensures Valid() - ensures fresh(Repr - {this}) - { - elements := s; - index := 0; - Repr := {this}; - } + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + { + elements := s; + index := 0; + Repr := {this}; + } - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr - { - && this in Repr - && 0 <= index <= |elements| - } + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + && 0 <= index <= |elements| + } - function Decreases(): nat - reads Repr - requires Valid() - { - |elements| - index - } + function Decreases(): nat + reads Repr + requires Valid() + { + |elements| - index + } - predicate method Done() - reads Repr - requires Valid() - ensures Decreases() == 0 ==> Done() - { - index == |elements| - } + predicate method Done() + reads Repr + requires Valid() + ensures Decreases() == 0 ==> Done() + { + index == |elements| + } - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures Decreases() < old(Decreases()) - { - index := index + 1; - } + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + { + index := index + 1; } } diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index e0704dff..f5abfd4f 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -2,6 +2,7 @@ // include "../Actions.dfy" include "../Frames.dfy" include "../Wrappers.dfy" +include "../Collections/Sequences/Seq.dfy" module Loopers { @@ -9,6 +10,8 @@ module Loopers { import opened Frames import opened Wrappers + import opened Seq + trait InfiniteStepper extends Validatable { method Step() requires Valid() @@ -24,7 +27,9 @@ module Loopers { } } - trait Stepper extends Validatable { + // TODO: Merge this with Enumerator - no point in having it separate + // since you can always define Current() to be `this`. + trait Stepper extends Validatable { method Step() requires Valid() requires !Done() @@ -217,6 +222,237 @@ module Loopers { } } + iterator SeqIterator(s: seq) yields (element: T) + yield ensures elements <= s + ensures elements == s + { + for i := 0 to |s| + invariant i == |elements| + invariant elements <= s + { + yield s[i]; + } + } + + class SeqIteratorEnumerator extends Enumerator { + + const iter: SeqIterator + var done: bool + + ghost const original: seq + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr) + { + iter := new SeqIterator(s); + original := s; + enumerated := []; + + new; + + // Calling MoveNext() right away ensures we only enumerated yielded state. + // Another version of this adaptor could not do this, and by consequence + // enumerate the initial state of the iterator as well. + var more := iter.MoveNext(); + done := !more; + + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && iter in Repr + && this !in iter._modifies + && this !in iter._reads + && this !in iter._new + && iter._modifies <= Repr + && iter._reads <= Repr + && iter._new <= Repr + && (!done ==> iter.Valid()) + && iter.elements < iter.s + && |enumerated| <= |original| + && enumerated == original[0..|enumerated|] + } + + predicate method Done() + requires Valid() + reads this, Repr + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + // ensures Done() ==> enumerated == original + { + done + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; + + var more := iter.MoveNext(); + done := !more; + + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + iter.element + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + assert iter.elements < iter.s; + (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) + } + } + + class ConcatEnumerator extends Enumerator { + + const first: Enumerator + const second: Enumerator + + constructor(first: Enumerator, second: Enumerator) + requires first.Valid() + requires second.Valid() + requires first.Repr !! second.Repr + ensures Valid() + ensures fresh(Repr - first.Repr - second.Repr) + { + this.first := first; + this.second := second; + enumerated := []; + + Repr := {this} + first.Repr + second.Repr; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && ValidComponent(first) + && ValidComponent(second) + && first.Repr !! second.Repr + } + + predicate method Done() + requires Valid() + reads this, Repr + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + first.Done() && second.Done() + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; + + if !first.Done() { + first.Step(); + } else { + second.Step(); + } + Repr := {this} + first.Repr + second.Repr; + } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + if !first.Done() then first.Current() else second.Current() + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + first.Decreases() + second.Decreases() + } + } + + // TODO: Prove the semantics! + class Filter extends Enumerator { + const wrapped: Enumerator + const filter: T -> bool + + constructor(wrapped: Enumerator, filter: T -> bool) + requires wrapped.Valid() + ensures Valid() + { + this.wrapped := wrapped; + this.filter := filter; + Repr := {this} + wrapped.Repr; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && ValidComponent(wrapped) + } + + predicate method Done() + reads Repr + requires Valid() + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + wrapped.Done() + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; + while (!wrapped.Done() && !filter(wrapped.Current())) + invariant wrapped.Valid() + modifies Repr + decreases wrapped.Decreases() + { + wrapped.Step(); + } + Repr := Repr + wrapped.Repr; + } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + wrapped.Current() + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + wrapped.Decreases() + } + } + method Example1() { var numbers := [1, 2, 3, 4, 5]; var e: Enumerator := new SeqEnumerator(numbers); @@ -246,9 +482,27 @@ module Loopers { } } + method Example3() { + var first := [1, 2, 3, 4, 5]; + var second := [6, 7, 8]; + var e1 := new SeqEnumerator(first); + var e2 := new SeqEnumerator(second); + var e := new ConcatEnumerator(e1, e2); + + label start: + while (!e.Done()) + invariant e.Valid() + invariant fresh(e.Repr) + decreases e.Decreases() + { + print e.Current(), "\n"; + + e.Step(); + } + } + method Main() { - Example1(); - Example2(); + Example3(); } // TODO: Some similarities between this and composing From 768df4e318f2c207a11b31e2c28fecf697b0f92e Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 13 Feb 2022 13:54:13 -0800 Subject: [PATCH 07/68] Restricted Step post-condition and got Filter to verify! --- src/Enumeration/Loopers.dfy | 171 +++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 80 deletions(-) diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index f5abfd4f..cd2b2f20 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -83,7 +83,8 @@ module Loopers { requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [old(Current())] @@ -141,7 +142,8 @@ module Loopers { requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [old(Current())] { @@ -204,7 +206,8 @@ module Loopers { requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [old(Current())] { @@ -234,90 +237,90 @@ module Loopers { } } - class SeqIteratorEnumerator extends Enumerator { + // class SeqIteratorEnumerator extends Enumerator { - const iter: SeqIterator - var done: bool + // const iter: SeqIterator + // var done: bool - ghost const original: seq + // ghost const original: seq - constructor(s: seq) - ensures Valid() - ensures fresh(Repr) - { - iter := new SeqIterator(s); - original := s; - enumerated := []; + // constructor(s: seq) + // ensures Valid() + // ensures fresh(Repr) + // { + // iter := new SeqIterator(s); + // original := s; + // enumerated := []; - new; + // new; - // Calling MoveNext() right away ensures we only enumerated yielded state. - // Another version of this adaptor could not do this, and by consequence - // enumerate the initial state of the iterator as well. - var more := iter.MoveNext(); - done := !more; + // // Calling MoveNext() right away ensures we only enumerated yielded state. + // // Another version of this adaptor could not do this, and by consequence + // // enumerate the initial state of the iterator as well. + // var more := iter.MoveNext(); + // done := !more; - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - } + // Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + // } - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - && this in Repr - && iter in Repr - && this !in iter._modifies - && this !in iter._reads - && this !in iter._new - && iter._modifies <= Repr - && iter._reads <= Repr - && iter._new <= Repr - && (!done ==> iter.Valid()) - && iter.elements < iter.s - && |enumerated| <= |original| - && enumerated == original[0..|enumerated|] - } + // predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + // && this in Repr + // && iter in Repr + // && this !in iter._modifies + // && this !in iter._reads + // && this !in iter._new + // && iter._modifies <= Repr + // && iter._reads <= Repr + // && iter._new <= Repr + // && (!done ==> iter.Valid()) + // && iter.elements < iter.s + // && |enumerated| <= |original| + // && enumerated == original[0..|enumerated|] + // } - predicate method Done() - requires Valid() - reads this, Repr - decreases Repr, 0 - ensures Decreases() == 0 ==> Done() - // ensures Done() ==> enumerated == original - { - done - } + // predicate method Done() + // requires Valid() + // reads this, Repr + // decreases Repr, 0 + // ensures Decreases() == 0 ==> Done() + // // ensures Done() ==> enumerated == original + // { + // done + // } - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] - { - enumerated := enumerated + [Current()]; + // method Step() + // requires Valid() + // requires !Done() + // modifies Repr + // decreases Repr + // ensures ValidAndDisjoint() + // ensures Decreases() < old(Decreases()) + // ensures enumerated == old(enumerated) + [old(Current())] + // { + // enumerated := enumerated + [Current()]; - var more := iter.MoveNext(); - done := !more; + // var more := iter.MoveNext(); + // done := !more; - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - } + // Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + // } - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - iter.element - } + // function method Current(): T + // reads this, Repr + // requires Valid() + // requires !Done() + // { + // iter.element + // } - function Decreases(): nat - reads this, Repr - requires Valid() - { - assert iter.elements < iter.s; - (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) - } - } + // function Decreases(): nat + // reads this, Repr + // requires Valid() + // { + // assert iter.elements < iter.s; + // (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) + // } + // } class ConcatEnumerator extends Enumerator { @@ -359,7 +362,8 @@ module Loopers { requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [old(Current())] { @@ -422,19 +426,26 @@ module Loopers { requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [old(Current())] { enumerated := enumerated + [Current()]; + + wrapped.Step(); while (!wrapped.Done() && !filter(wrapped.Current())) - invariant wrapped.Valid() - modifies Repr + invariant Valid() + // Necessary to allow the recursive call to Step + invariant wrapped.Repr < old(Repr) + // Necessary to ensure Repr <= old(Repr) + invariant Repr == old(Repr) + // Apparently even this is necessary :) + invariant enumerated == old(enumerated) + [old(Current())] decreases wrapped.Decreases() { wrapped.Step(); - } - Repr := Repr + wrapped.Repr; + } } function method Current(): T From c7aaacca8184c41f9f528a9811a75748829ea8c1 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 14 Feb 2022 10:28:54 -0800 Subject: [PATCH 08/68] Defined Rust-style Iterator --- src/Enumeration/Loopers.dfy | 147 +++++++++++++++++----------------- src/Enumeration/RustStyle.dfy | 145 +++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 72 deletions(-) create mode 100644 src/Enumeration/RustStyle.dfy diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index cd2b2f20..96a22609 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -66,7 +66,7 @@ module Loopers { } } - trait Enumerator extends Stepper { + trait {:termination false} Enumerator extends Stepper { // Any enumerator that produces one value at a time // and provably terminates is equivalent to an enumerator @@ -237,90 +237,93 @@ module Loopers { } } - // class SeqIteratorEnumerator extends Enumerator { + class SeqIteratorEnumerator extends Enumerator { - // const iter: SeqIterator - // var done: bool + const iter: SeqIterator + var done: bool - // ghost const original: seq + ghost const original: seq - // constructor(s: seq) - // ensures Valid() - // ensures fresh(Repr) - // { - // iter := new SeqIterator(s); - // original := s; - // enumerated := []; + constructor(s: seq) + ensures Valid() + ensures fresh(Repr) + { + iter := new SeqIterator(s); + original := s; + enumerated := []; - // new; + new; - // // Calling MoveNext() right away ensures we only enumerated yielded state. - // // Another version of this adaptor could not do this, and by consequence - // // enumerate the initial state of the iterator as well. - // var more := iter.MoveNext(); - // done := !more; + // Calling MoveNext() right away ensures we only enumerated yielded state. + // Another version of this adaptor could not do this, and by consequence + // enumerate the initial state of the iterator as well. + var more := iter.MoveNext(); + done := !more; - // Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - // } + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + } - // predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - // && this in Repr - // && iter in Repr - // && this !in iter._modifies - // && this !in iter._reads - // && this !in iter._new - // && iter._modifies <= Repr - // && iter._reads <= Repr - // && iter._new <= Repr - // && (!done ==> iter.Valid()) - // && iter.elements < iter.s - // && |enumerated| <= |original| - // && enumerated == original[0..|enumerated|] - // } + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && iter in Repr + && this !in iter._modifies + && this !in iter._reads + && this !in iter._new + && iter._modifies <= Repr + && iter._reads <= Repr + && iter._new <= Repr + && (!done ==> iter.Valid()) + && (!done ==> enumerated + [Current()] == iter.elements) + && (done ==> enumerated == iter.elements) + && iter.elements < iter.s + && |enumerated| <= |original| + && enumerated == original[0..|enumerated|] + } - // predicate method Done() - // requires Valid() - // reads this, Repr - // decreases Repr, 0 - // ensures Decreases() == 0 ==> Done() - // // ensures Done() ==> enumerated == original - // { - // done - // } + predicate method Done() + requires Valid() + reads this, Repr + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + // ensures Done() ==> enumerated == original + { + done + } - // method Step() - // requires Valid() - // requires !Done() - // modifies Repr - // decreases Repr - // ensures ValidAndDisjoint() - // ensures Decreases() < old(Decreases()) - // ensures enumerated == old(enumerated) + [old(Current())] - // { - // enumerated := enumerated + [Current()]; + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; - // var more := iter.MoveNext(); - // done := !more; + var more := iter.MoveNext(); + done := !more; - // Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - // } + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + } - // function method Current(): T - // reads this, Repr - // requires Valid() - // requires !Done() - // { - // iter.element - // } + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + iter.element + } - // function Decreases(): nat - // reads this, Repr - // requires Valid() - // { - // assert iter.elements < iter.s; - // (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) - // } - // } + function Decreases(): nat + reads this, Repr + requires Valid() + { + assert iter.elements < iter.s; + (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) + } + } class ConcatEnumerator extends Enumerator { diff --git a/src/Enumeration/RustStyle.dfy b/src/Enumeration/RustStyle.dfy new file mode 100644 index 00000000..60690621 --- /dev/null +++ b/src/Enumeration/RustStyle.dfy @@ -0,0 +1,145 @@ + +include "../Frames.dfy" +include "../Wrappers.dfy" +include "Loopers.dfy" + +module RustStyle { + + import opened Frames + import opened Wrappers + import opened Loopers + + trait RustStyleIterator extends Validatable { + + method Next() returns (res: Option) + requires Valid() + modifies Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures res.Some? ==> Decreases() < old(Decreases()) + ensures old(Decreases()) == 0 ==> res.None? + + function Decreases(): nat + reads Repr + requires Valid() + } + + class RustStyleIteratorEnumerator extends Enumerator { + + const iter: RustStyleIterator + var next: Option + + ghost var decr: nat + + constructor(iter: RustStyleIterator) + requires iter.Valid() + modifies iter.Repr + { + this.iter := iter; + new; + this.next := iter.Next(); + Repr := {this} + iter.Repr; + if this.next.None? { + decr := 0; + } else { + decr := iter.Decreases() + 1; + } + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + && ValidComponent(iter) + && decr == (if this.next.None? then 0 else iter.Decreases() + 1) + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; + next := iter.Next(); + if this.next.None? { + decr := 0; + } else { + decr := iter.Decreases() + 1; + } + } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + next.value + } + + function Decreases(): nat + reads Repr + requires Valid() + { + decr + } + + predicate method Done() + reads Repr + requires Valid() + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + next.None? + } + } + + class SeqRustStyleIterator extends RustStyleIterator { + + var s: seq + + constructor(s: seq) + ensures Valid() + { + this.s := s; + Repr := {this}; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + } + + method Next() returns (res: Option) + requires Valid() + modifies Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures res.Some? ==> Decreases() < old(Decreases()) + ensures old(Decreases()) == 0 ==> res.None? + { + if |s| > 0 { + res := Some(s[0]); + s := s[1..]; + } else { + res := None; + } + } + + function Decreases(): nat + reads Repr + requires Valid() + { + |s| + } + } +} \ No newline at end of file From dc59bea691a085fccd055732e02d5062a44786cd Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 14 Feb 2022 18:31:00 -0800 Subject: [PATCH 09/68] Improve while loop idiom, start cleaning up --- src/Enumeration/IteratorAdaptor.dfy | 121 ++++++++++++++++++++++++++++ src/Enumeration/Loopers.dfy | 116 ++------------------------ src/Enumeration/RustStyle.dfy | 20 +++++ 3 files changed, 147 insertions(+), 110 deletions(-) create mode 100644 src/Enumeration/IteratorAdaptor.dfy diff --git a/src/Enumeration/IteratorAdaptor.dfy b/src/Enumeration/IteratorAdaptor.dfy new file mode 100644 index 00000000..3015a127 --- /dev/null +++ b/src/Enumeration/IteratorAdaptor.dfy @@ -0,0 +1,121 @@ + +include "Loopers.dfy" + +module IteratorAdaptorExample { + + import opened Loopers + + iterator SeqIterator(s: seq) yields (element: T) + yield ensures elements <= s + ensures elements == s + { + for i := 0 to |s| + invariant i == |elements| + invariant elements <= s + { + yield s[i]; + } + } + + class SeqIteratorEnumerator extends Enumerator { + + const iter: SeqIterator + var done: bool + + ghost const original: seq + ghost var decr: nat + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr) + { + iter := new SeqIterator(s); + original := s; + enumerated := []; + + new; + + // Calling MoveNext() right away ensures we only enumerate yielded state. + var more := iter.MoveNext(); + done := !more; + + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + decr := |iter.s| - |enumerated|; + assert !done ==> iter.elements < iter.s; + } + + predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + && this in Repr + && iter in Repr + && this !in iter._modifies + && this !in iter._reads + && this !in iter._new + && iter._modifies <= Repr + && iter._reads <= Repr + && iter._new <= Repr + && (!done ==> iter.Valid()) + && (!done ==> enumerated + [iter.element] == iter.elements) + && (done ==> enumerated == iter.elements) + && (!done ==> iter.elements < iter.s) + && decr == |iter.s| - |enumerated| + } + + predicate method Done() + requires Valid() + reads this, Repr + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + done + } + + method Step() + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] + { + enumerated := enumerated + [Current()]; + + var more := iter.MoveNext(); + done := !more; + + assert iter._new == {}; + + Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + decr := |iter.s| - |enumerated|; + + assert this in Repr; + assert iter in Repr; + assert this !in iter._modifies; + assert this !in iter._reads; + assert this !in iter._new; + assert iter._modifies <= Repr; + assert iter._reads <= Repr; + assert iter._new <= Repr; + assert (!done ==> iter.Valid()); + assert (!done ==> enumerated + [iter.element] == iter.elements); + assert (done ==> enumerated == iter.elements); + assert !done ==> iter.elements < iter.s; + } + + function method Current(): T + reads this, Repr + requires Valid() + requires !Done() + { + iter.element + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + decr + } + } +} \ No newline at end of file diff --git a/src/Enumeration/Loopers.dfy b/src/Enumeration/Loopers.dfy index 96a22609..8c9269ba 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumeration/Loopers.dfy @@ -225,106 +225,6 @@ module Loopers { } } - iterator SeqIterator(s: seq) yields (element: T) - yield ensures elements <= s - ensures elements == s - { - for i := 0 to |s| - invariant i == |elements| - invariant elements <= s - { - yield s[i]; - } - } - - class SeqIteratorEnumerator extends Enumerator { - - const iter: SeqIterator - var done: bool - - ghost const original: seq - - constructor(s: seq) - ensures Valid() - ensures fresh(Repr) - { - iter := new SeqIterator(s); - original := s; - enumerated := []; - - new; - - // Calling MoveNext() right away ensures we only enumerated yielded state. - // Another version of this adaptor could not do this, and by consequence - // enumerate the initial state of the iterator as well. - var more := iter.MoveNext(); - done := !more; - - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - } - - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - && this in Repr - && iter in Repr - && this !in iter._modifies - && this !in iter._reads - && this !in iter._new - && iter._modifies <= Repr - && iter._reads <= Repr - && iter._new <= Repr - && (!done ==> iter.Valid()) - && (!done ==> enumerated + [Current()] == iter.elements) - && (done ==> enumerated == iter.elements) - && iter.elements < iter.s - && |enumerated| <= |original| - && enumerated == original[0..|enumerated|] - } - - predicate method Done() - requires Valid() - reads this, Repr - decreases Repr, 0 - ensures Decreases() == 0 ==> Done() - // ensures Done() ==> enumerated == original - { - done - } - - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] - { - enumerated := enumerated + [Current()]; - - var more := iter.MoveNext(); - done := !more; - - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - } - - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - iter.element - } - - function Decreases(): nat - reads this, Repr - requires Valid() - { - assert iter.elements < iter.s; - (|iter.s| - |iter.elements|) //+ (if done then 0 else 1) - } - } - class ConcatEnumerator extends Enumerator { const first: Enumerator @@ -339,8 +239,8 @@ module Loopers { { this.first := first; this.second := second; - enumerated := []; + enumerated := []; Repr := {this} + first.Repr + second.Repr; } @@ -470,13 +370,12 @@ module Loopers { method Example1() { var numbers := [1, 2, 3, 4, 5]; var e: Enumerator := new SeqEnumerator(numbers); - label start: while (!e.Done()) - invariant e.Valid() - invariant old@start(allocated(e)) && fresh(e.Repr) + invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { print e.Current(), "\n"; + e.Step(); } } @@ -485,13 +384,12 @@ module Loopers { var maybeNumbers := [Some(1), Some(2), Some(3), None, None]; var e: Enumerator> := new SeqEnumerator(maybeNumbers); var e': Enumerator := new TerminatedEnumerator(e); - label start: while (!e'.Done()) - invariant e'.Valid() - invariant old@start(allocated(e')) && fresh(e'.Repr) + invariant e'.Valid() && fresh(e'.Repr) decreases e'.Decreases() { print e'.Current(), "\n"; + e'.Step(); } } @@ -503,10 +401,8 @@ module Loopers { var e2 := new SeqEnumerator(second); var e := new ConcatEnumerator(e1, e2); - label start: while (!e.Done()) - invariant e.Valid() - invariant fresh(e.Repr) + invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { print e.Current(), "\n"; diff --git a/src/Enumeration/RustStyle.dfy b/src/Enumeration/RustStyle.dfy index 60690621..a25feef4 100644 --- a/src/Enumeration/RustStyle.dfy +++ b/src/Enumeration/RustStyle.dfy @@ -34,6 +34,9 @@ module RustStyle { constructor(iter: RustStyleIterator) requires iter.Valid() modifies iter.Repr + ensures Valid() + ensures iter.Repr <= old(iter.Repr) + ensures Repr == {this} + iter.Repr { this.iter := iter; new; @@ -106,6 +109,7 @@ module RustStyle { constructor(s: seq) ensures Valid() + ensures fresh(Repr) { this.s := s; Repr := {this}; @@ -142,4 +146,20 @@ module RustStyle { |s| } } + + method Main() { + var iter := new SeqRustStyleIterator([1,2,3,4,5]); + + var enum: Enumerator := new RustStyleIteratorEnumerator(iter); + while (!enum.Done()) + invariant enum.Valid() && fresh(enum.Repr) + decreases enum.Decreases() + { + // Loop body starts + print enum.Current(); + // Loop body ends + + enum.Step(); + } + } } \ No newline at end of file From 4b8307ed5c11eccd6a487d542f3919f7e56ea8b8 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 14 Feb 2022 21:58:24 -0800 Subject: [PATCH 10/68] CLEANUP --- src/Enumeration/DecreasesWeirdness.dfy | 49 -- src/Enumeration/Iterators.dfy | 492 ------------------ src/Enumerators/Datatypes.dfy | 22 + .../Enumerators.dfy} | 336 ++++-------- src/Enumerators/ForEach.dfy | 145 ++++++ .../IteratorAdaptor.dfy | 4 +- .../JavaStyle.dfy | 0 .../RustStyle.dfy | 8 +- 8 files changed, 260 insertions(+), 796 deletions(-) delete mode 100644 src/Enumeration/DecreasesWeirdness.dfy delete mode 100644 src/Enumeration/Iterators.dfy create mode 100644 src/Enumerators/Datatypes.dfy rename src/{Enumeration/Loopers.dfy => Enumerators/Enumerators.dfy} (54%) create mode 100644 src/Enumerators/ForEach.dfy rename src/{Enumeration => Enumerators}/IteratorAdaptor.dfy (98%) rename src/{Enumeration => Enumerators}/JavaStyle.dfy (100%) rename src/{Enumeration => Enumerators}/RustStyle.dfy (95%) diff --git a/src/Enumeration/DecreasesWeirdness.dfy b/src/Enumeration/DecreasesWeirdness.dfy deleted file mode 100644 index 0ea3582c..00000000 --- a/src/Enumeration/DecreasesWeirdness.dfy +++ /dev/null @@ -1,49 +0,0 @@ -module DecreasesWeirdness { - - trait Thing { - var Repr: set - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - method Foo() - requires Valid() - decreases Repr - } - - class MyThing extends Thing { - var d: int - var wrapped: Thing - constructor(wrapped: Thing) - { - this.wrapped := wrapped; - Repr := {this} + wrapped.Repr; - } - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - { - && this in Repr - && wrapped in Repr - && wrapped.Repr <= Repr - && this !in wrapped.Repr - && wrapped.Valid() - } - - method Foo() - requires Valid() - decreases Repr - { - var n := 5; - while (n > 0) decreases n - { - assert wrapped.Repr < Repr; - wrapped.Foo(); - print n; - n := n - 1; - } - } - } - - - -} \ No newline at end of file diff --git a/src/Enumeration/Iterators.dfy b/src/Enumeration/Iterators.dfy deleted file mode 100644 index 57cd98cc..00000000 --- a/src/Enumeration/Iterators.dfy +++ /dev/null @@ -1,492 +0,0 @@ - -include "../Frames.dfy" -include "../Wrappers.dfy" -include "../Collections/Sequences/Seq.dfy" - -module IteratorExperiments { - - import opened Frames - import opened Wrappers - - import Seq - - // Would like to make this extend the even more general Looper - // concept, but tough to use without bounded polymorphism. - trait Enumerator extends Validatable { - - // Any enumerator that produces one value at a time - // and provably terminates is equivalent to an enumerator - // that produces a specific seq. This value may be underspecified - // such that it is not known, even its length, until after all - // values have been produced. - // Dafny doesn't let you pass around an underspecified value though, - // so we don't define a "to be enumerated" field or function. - ghost var enumerated: seq - - // Valid() is used as the enumeration invariant - - method MoveNext() returns (more: bool) - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures more <==> !Done() - ensures !Done() ==> HasCurrent() - ensures !Done() ==> Decreases() < old(Decreases()) - ensures !Done() ==> enumerated == old(enumerated) + [Current()] - - predicate HasCurrent() - reads this, Repr - requires Valid() - - function method Current(): T - reads this, Repr - requires Valid() - requires HasCurrent() - - function Decreases(): nat - reads this, Repr - requires Valid() - - predicate method Done() - requires Valid() - reads this, Repr - } - - class SeqEnumerator extends Enumerator { - - const elements: seq - var index: nat - - constructor(s: seq) - ensures Valid() - ensures fresh(Repr - {this}) - { - elements := s; - index := 0; - Repr := {this}; - } - - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr - { - && this in Repr - && 0 <= index <= |elements| - } - - function Decreases(): nat - reads Repr - requires Valid() - { - |elements| - index - } - - predicate method Done() - reads Repr - requires Valid() - ensures Decreases() == 0 ==> Done() - { - index == |elements| - } - - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures Decreases() < old(Decreases()) - { - index := index + 1; - } - } - - iterator ForEachWithIndex(s: seq) yields (element: T, index: nat) - yield ensures elements <= s - ensures elements == s - { - for i := 0 to |s| - invariant i == |elements| - invariant elements <= s - { - yield s[i], i; - } - } - - class ForEachWithIndexEnumerator extends Enumerator<(T, nat)> { - - const iter: ForEachWithIndex - var done: bool - - ghost const original: seq - ghost var hasCurrent: bool - - constructor(s: seq) - ensures Valid() - ensures fresh(Repr) - ensures !Done() - { - iter := new ForEachWithIndex(s); - original := s; - new; - done := false; - hasCurrent := false; - Repr := {this, iter}; - } - - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - && this in Repr - && iter in Repr - && this !in iter._modifies - && this !in iter._reads - && this !in iter._new - && iter._modifies <= Repr - && iter._reads <= Repr - && iter._new <= Repr - && (!done ==> iter.Valid()) - && enumerated == Seq.Zip(original[0..|enumerated|], Range(0, |enumerated|)) - } - - predicate method Done() - requires Valid() - reads this, Repr - ensures Done() ==> enumerated == Seq.Zip(original, Range(0, |original|)) - { - done - } - - method MoveNext() returns (more: bool) - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures more <==> !Done() - ensures !Done() ==> HasCurrent() - ensures !Done() ==> Decreases() < old(Decreases()) - ensures !Done() ==> enumerated == old(enumerated) + [Current()] - { - more := iter.MoveNext(); - done := !more; - - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - if more { - hasCurrent := true; - enumerated := enumerated + [Current()]; - } - } - - predicate HasCurrent() - reads this, Repr - requires Valid() - { - hasCurrent - } - - function method Current(): (T, nat) - reads this, Repr - requires Valid() - { - (iter.element, iter.index) - } - - function Decreases(): nat - reads this, Repr - requires Valid() - { - |original| - |enumerated| - } - } - - // TODO: move to Seq.dfy? - function Range(start: int, end: int): seq - requires start <= end - decreases end - start - { - var length := end - start; - seq(length, i requires 0 <= i < length => start + i) - } - - // TODO: Prove the semantics! - class Filter extends Enumerator { - const wrapped: Enumerator - const filter: T -> bool - var hasCurrent: bool - var current: T - - constructor(wrapped: Enumerator, filter: T -> bool) - requires wrapped.Valid() - ensures Valid() - { - this.wrapped := wrapped; - this.filter := filter; - Repr := {this} + wrapped.Repr; - } - - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - && this in Repr - && ValidComponent(wrapped) - } - - predicate method Done() - requires Valid() - reads this, Repr - { - wrapped.Done() - } - - method MoveNext() returns (more: bool) - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures more <==> !Done() - ensures !Done() ==> HasCurrent() - ensures !Done() ==> Decreases() < old(Decreases()) - ensures !Done() ==> enumerated == old(enumerated) + [Current()] - { - assert wrapped.Repr < Repr; - more := true; - while (true) - invariant Valid() - invariant more <==> !wrapped.Done() - modifies Repr - decreases more, wrapped.Decreases() - { - assert wrapped.Repr < old(Repr); - more := wrapped.MoveNext(); - Repr := Repr + wrapped.Repr; - if (!more) { break; } - - assert Valid(); - assert wrapped.HasCurrent(); - current := wrapped.Current(); - if (filter(current)) { - enumerated := enumerated + [Current()]; - break; - } - } - } - - predicate HasCurrent() - reads this, Repr - requires Valid() - { - hasCurrent - } - - function method Current(): T - reads this, Repr - requires Valid() - { - current - } - - function Decreases(): nat - reads this, Repr - requires Valid() - { - wrapped.Decreases() - } - - predicate DoneProperty() - requires Valid() - requires Done() - reads this, Repr - { - enumerated == wrapped.enumerated - } - } - - // (0) is necessary because we can't just use an Option to - // hold the current value - the spec doesn't prevent you from constructing - // this and then calling Current() before calling MoveNext() - // TODO: multiset version as well? Instead? - class SetEnumerator extends Enumerator { - ghost const original: set - var remaining: set - var current: T - var hasCurrent: bool - var done: bool - - constructor(s: set) - ensures Valid() - ensures fresh(Repr) - { - this.original := s; - this.remaining := s; - this.done := false; - - enumerated := []; - Repr := {this}; - } - - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { - && this in Repr - } - - predicate method Done() - requires Valid() - reads this, Repr - { - done - } - - method MoveNext() returns (more: bool) - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures ValidAndDisjoint() - ensures more <==> !Done() - ensures more ==> HasCurrent() - ensures !Done() ==> Decreases() < old(Decreases()) - ensures !Done() ==> enumerated == old(enumerated) + [Current()] - { - if |remaining| == 0 { - done := true; - more := false; - } else { - var c :| c in remaining; - current := c; - hasCurrent := true; - enumerated := enumerated + [current]; - remaining := remaining - {c}; - more := true; - } - } - - predicate HasCurrent() - reads this, Repr - requires Valid() - { - hasCurrent - } - - function method Current(): T - reads this, Repr - requires Valid() - requires HasCurrent() - { - current - } - - function Decreases(): nat - reads this, Repr - requires Valid() - { - |remaining| - } - - predicate DoneProperty() - requires Valid() - requires Done() - reads this, Repr - { - multiset(enumerated) == multiset(original) - } - } - - // method Max(s: set) returns (max: int) - // requires |s| > 0 - // ensures max in s - // ensures forall x | x in s :: max >= x - // { - // var first: int :| first in s; - // max := first; - // var sEnum: SetEnumerator := new SetEnumerator(s - {max}); - // assert fresh(sEnum.Repr); - // label start: - // var more := true; - // while (!sEnum.Done()) - // invariant sEnum.Valid() - // invariant fresh(sEnum.Repr) - // invariant max == Seq.Max([first] + sEnum.enumerated) - // decreases more, sEnum.Decreases() - // { - // var more := sEnum.MoveNext(); - // if !more { break; } - - // if sEnum.Current() > max { - // max := sEnum.Current(); - // } - // } - // } - - method Main() { - var s: seq := [1, 1, 2, 3, 5, 8]; - var e: Enumerator<(nat, nat)> := new ForEachWithIndexEnumerator(s); - assert fresh(e.Repr); - label start: - var more := true; - while (true) - invariant e.Valid() - invariant more <==> !e.Done() - invariant old@start(allocated(e)) && fresh(e.Repr) - decreases e.Decreases() - { - more := e.MoveNext(); - if (!more) { break; } - var (element, index) := e.Current(); - - print "Index: ", index, ", Element: ", element, "\n"; - } - } - - datatype E = Done | Next(T, Enum) - type Enum = () -> E - - function OneTwoThree(): Enum { - () => Next(1, () => Next(2, () => Next(3, () => Done))) - } - - function CountdownFrom(n: nat): Enum { - () => - if n > 0 then - Next(n, CountdownFrom(n - 1)) - else - Done - } - - // Doesn't terminate so you can't do this - // function CountupFrom(n: nat): Enum { - // () => Next(n, CountupFrom(n + 1)) - // } - - // iterator Iter(s: set) yields (x: T) - // yield ensures x in s && x !in xs[..|xs|-1]; - // ensures s == set z | z in xs; - // { - // var r := s; - // while (r != {}) - // invariant forall z :: z in xs ==> x !in r; - // // r and xs are disjoint - // invariant s == r + set z | z in xs; - // { - // var y :| y in r; - // r, x := r - {y}, y; - // yield; - // assert y == xs[|xs|-1]; // a lemma to help prove loop invariant - // } - // } - - // method UseIterToCopy(s: set) returns (t: set) - // ensures s == t; - // { - // t := {}; - // var m := new Iter(s); - // while (true) - // invariant m.Valid() && fresh(m._new); - // invariant t == set z | z in m.xs; - // decreases s - t; - // { - // var more := m.MoveNext(); - // if (!more) { break; } - // t := t + {m.x}; - // } - // } -} \ No newline at end of file diff --git a/src/Enumerators/Datatypes.dfy b/src/Enumerators/Datatypes.dfy new file mode 100644 index 00000000..16e8b508 --- /dev/null +++ b/src/Enumerators/Datatypes.dfy @@ -0,0 +1,22 @@ +module DatatypeEnumerator { + + datatype E = Done | Next(T, Enum) + type Enum = () -> E + + function OneTwoThree(): Enum { + () => Next(1, () => Next(2, () => Next(3, () => Done))) + } + + function CountdownFrom(n: nat): Enum { + () => + if n > 0 then + Next(n, CountdownFrom(n - 1)) + else + Done + } + + // Doesn't terminate so you can't do this + // function CountupFrom(n: nat): Enum { + // () => Next(n, CountupFrom(n + 1)) + // } +} \ No newline at end of file diff --git a/src/Enumeration/Loopers.dfy b/src/Enumerators/Enumerators.dfy similarity index 54% rename from src/Enumeration/Loopers.dfy rename to src/Enumerators/Enumerators.dfy index 8c9269ba..b0c895f4 100644 --- a/src/Enumeration/Loopers.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -4,39 +4,34 @@ include "../Frames.dfy" include "../Wrappers.dfy" include "../Collections/Sequences/Seq.dfy" -module Loopers { +module Enumerators { // import opened Actions import opened Frames import opened Wrappers - import opened Seq - trait InfiniteStepper extends Validatable { - method Step() - requires Valid() - ensures Valid() - } + trait {:termination false} Enumerator extends Validatable { + + ghost var enumerated: seq - method DoLoopStar(l: InfiniteStepper) - requires l.Valid() - decreases * - { - while (true) decreases * { - l.Step(); - } - } + // Any enumerator that produces one value at a time + // and provably terminates is equivalent to an enumerator + // that produces a specific seq. This value may be underspecified + // such that it is not known, even its length, until after all + // values have been produced. + // Dafny doesn't let you pass around an underspecified value though, + // so we don't define a "to be enumerated" field or function. - // TODO: Merge this with Enumerator - no point in having it separate - // since you can always define Current() to be `this`. - trait Stepper extends Validatable { - method Step() + method Step() requires Valid() requires !Done() modifies Repr decreases Repr - ensures ValidAndDisjoint() + ensures Valid() + ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [old(Current())] // Would be better as an arbitrary termination clause somehow instead // https://github.com/dafny-lang/dafny/issues/762 @@ -50,43 +45,6 @@ module Loopers { requires Valid() decreases Repr, 0 ensures Decreases() == 0 ==> Done() - } - - method DoLoop(l: Stepper) - requires l.Valid() - modifies l.Repr - ensures l.Valid() - ensures l.Done() - { - while (!l.Done()) - invariant l.ValidAndDisjoint() - decreases l.Decreases() - { - l.Step(); - } - } - - trait {:termination false} Enumerator extends Stepper { - - // Any enumerator that produces one value at a time - // and provably terminates is equivalent to an enumerator - // that produces a specific seq. This value may be underspecified - // such that it is not known, even its length, until after all - // values have been produced. - // Dafny doesn't let you pass around an underspecified value though, - // so we don't define a "to be enumerated" field or function. - - ghost var enumerated: seq - - method Step() - requires Valid() - requires !Done() - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] function method Current(): T reads this, Repr @@ -160,45 +118,45 @@ module Loopers { } } - // Wraps an enumerator that doesn't know if it has another value - // until it tries to get the next value. - // TODO: Equivalent to Filter? - class TerminatedEnumerator extends Enumerator { - const wrapped: Enumerator> + class SetEnumerator extends Enumerator { + ghost const original: set + var remaining: set + var current: Option - constructor(wrapped: Enumerator>) - requires wrapped.Valid() - ensures Valid() - ensures fresh(Repr - {this} - wrapped.Repr) + constructor(s: set) + ensures Valid() + ensures fresh(Repr) { - this.wrapped := wrapped; + this.original := s; + this.remaining := s; + new; + if |remaining| > 0 { + var element: T :| element in remaining; + current := Some(element); + remaining := remaining - {element}; + } else { + current := None; + } + enumerated := []; - Repr := {this} + wrapped.Repr; + Repr := {this}; } predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr { && this in Repr - && ValidComponent(wrapped) - } - - function Decreases(): nat - reads Repr - requires Valid() - { - wrapped.Decreases() + && (current.None? ==> |remaining| == 0) } - predicate method Done() - reads Repr + predicate method Done() requires Valid() + reads this, Repr decreases Repr, 0 ensures Decreases() == 0 ==> Done() { - wrapped.Done() || wrapped.Current().None? + current.None? } method Step() @@ -212,8 +170,14 @@ module Loopers { ensures enumerated == old(enumerated) + [old(Current())] { enumerated := enumerated + [Current()]; - wrapped.Step(); - Repr := {this} + wrapped.Repr; + + if |remaining| > 0 { + var element: T :| element in remaining; + current := Some(element); + remaining := remaining - {element}; + } else { + current := None; + } } function method Current(): T @@ -221,7 +185,14 @@ module Loopers { requires Valid() requires !Done() { - wrapped.Current().value + current.value + } + + function Decreases(): nat + reads this, Repr + requires Valid() + { + |remaining| + (if current.Some? then 1 else 0) } } @@ -240,7 +211,7 @@ module Loopers { this.first := first; this.second := second; - enumerated := []; + enumerated := first.enumerated + second.enumerated; Repr := {this} + first.Repr + second.Repr; } @@ -249,6 +220,7 @@ module Loopers { && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr + && enumerated == first.enumerated + second.enumerated } predicate method Done() @@ -285,7 +257,10 @@ module Loopers { requires Valid() requires !Done() { - if !first.Done() then first.Current() else second.Current() + if !first.Done() then + first.Current() + else + second.Current() } function Decreases(): nat @@ -339,11 +314,8 @@ module Loopers { wrapped.Step(); while (!wrapped.Done() && !filter(wrapped.Current())) invariant Valid() - // Necessary to allow the recursive call to Step invariant wrapped.Repr < old(Repr) - // Necessary to ensure Repr <= old(Repr) invariant Repr == old(Repr) - // Apparently even this is necessary :) invariant enumerated == old(enumerated) + [old(Current())] decreases wrapped.Decreases() { @@ -367,8 +339,35 @@ module Loopers { } } + // method Max(s: set) returns (max: int) + // requires |s| > 0 + // ensures max in s + // ensures forall x | x in s :: max >= x + // { + // var first: int :| first in s; + // max := first; + // var sEnum: SetEnumerator := new SetEnumerator(s - {max}); + // assert fresh(sEnum.Repr); + // label start: + // var more := true; + // while (!sEnum.Done()) + // invariant sEnum.Valid() + // invariant fresh(sEnum.Repr) + // invariant max == Seq.Max([first] + sEnum.enumerated) + // decreases more, sEnum.Decreases() + // { + // var more := sEnum.MoveNext(); + // if !more { break; } + + // if sEnum.Current() > max { + // max := sEnum.Current(); + // } + // } + // } + method Example1() { var numbers := [1, 2, 3, 4, 5]; + var e: Enumerator := new SeqEnumerator(numbers); while (!e.Done()) invariant e.Valid() && fresh(e.Repr) @@ -381,20 +380,6 @@ module Loopers { } method Example2() { - var maybeNumbers := [Some(1), Some(2), Some(3), None, None]; - var e: Enumerator> := new SeqEnumerator(maybeNumbers); - var e': Enumerator := new TerminatedEnumerator(e); - while (!e'.Done()) - invariant e'.Valid() && fresh(e'.Repr) - decreases e'.Decreases() - { - print e'.Current(), "\n"; - - e'.Step(); - } - } - - method Example3() { var first := [1, 2, 3, 4, 5]; var second := [6, 7, 8]; var e1 := new SeqEnumerator(first); @@ -410,151 +395,4 @@ module Loopers { e.Step(); } } - - method Main() { - Example3(); - } - - // TODO: Some similarities between this and composing - // two Actions together. - // One is: - // var r := action1.Invoke(a); - // var r2 := action2.Invoke(r); - // Other is: - // var r := looper.Step(); - // var _ := action.Invoke(r); - // var r2 := looper.Step(); - // var _ := action.Invoke(r2); - // ... - - // class ForEach extends TerminatingLooper { - // const iter: TerminatingLooper - // const body: Action - - // constructor(iter: TerminatingLooper, body: Action) - // requires iter.Valid() - // requires body.Valid() - // requires iter.Repr !! body.Repr - // requires iter.Valid() ==> body.Requires(iter) - // requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr - // ensures Valid() - // { - // this.iter := iter; - // this.body := body; - // Repr := {this} + iter.Repr + body.Repr; - // } - - // predicate Valid() - // reads this, Repr - // decreases Repr - // ensures Valid() ==> this in Repr - // { - // && this in Repr - // && ValidComponent(iter) - // && ValidComponent(body) - // && iter.Repr !! body.Repr - // && (iter.Valid() ==> body.Requires(iter)) - // // TODO: This needs to be a forall somehow - // && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) - // } - - // function Decreases(): nat - // reads Repr - // requires Valid() - // { - // iter.Decreases() - // } - - // predicate method Done() - // reads Repr - // requires Valid() - // ensures Decreases() == 0 ==> Done() - // { - // iter.Done() - // } - - // method Step() - // requires Valid() - // requires !Done() - // modifies Repr - // decreases Repr - // ensures ValidAndDisjoint() - // ensures Decreases() < old(Decreases()) - // { - // iter.Step(); - // Repr := Repr + iter.Repr; - - // assert iter.Valid(); - // assert this in Repr; - // assert ValidComponent(iter); - // assert ValidComponent(body); - // assert iter.Repr !! body.Repr; - // assert (iter.Valid() ==> body.Requires(iter)); - // assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); - // assert Valid(); - - // var _ := body.Invoke(iter); - // Repr := Repr + body.Repr; - // } - // } - - // class SeqCollector extends Action { - - // var elements: seq - - // constructor() - // ensures Valid() - // ensures fresh(Repr - {this}) - // { - // elements := []; - // Repr := {this}; - // } - - // predicate Valid() - // reads this, Repr - // ensures Valid() ==> this in Repr - // decreases Repr - // { - // && this in Repr - // } - - // method Invoke(t: T) returns (nothing: ()) - // requires Valid() - // requires Requires(t) - // modifies Modifies(t) - // decreases Decreases(t) - // ensures ValidAndDisjoint() - // ensures old(allocated(())) && Ensures(t, ()) - // { - // elements := elements + [t]; - // } - - // predicate Requires(t: T) { - // true - // } - - // // Need this separately from Repr for callers - // // Repr is the frame for Valid(), but callers - // // need to know what ELSE gets modified. - // function Modifies(t: T): set requires Requires(t) { - // {this} - // } - - // function Decreases(t: T): nat { - // 0 - // } - - // twostate predicate Ensures(t: T, nothing: ()) { - // true - // } - // } - - - // method Main() { - // var numbers := [1, 2, 3, 4, 5]; - // var numbersIter := new SeqLooper(numbers); - // var numbersPrinterLooper := new SeqCollector(); - // var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); - // DoLoop(forEachLoop); - // } } \ No newline at end of file diff --git a/src/Enumerators/ForEach.dfy b/src/Enumerators/ForEach.dfy new file mode 100644 index 00000000..0c600770 --- /dev/null +++ b/src/Enumerators/ForEach.dfy @@ -0,0 +1,145 @@ + +// module ForEach { + // TODO: Some similarities between this and composing + // two Actions together. + // One is: + // var r := action1.Invoke(a); + // var r2 := action2.Invoke(r); + // Other is: + // var r := looper.Step(); + // var _ := action.Invoke(r); + // var r2 := looper.Step(); + // var _ := action.Invoke(r2); + // ... + + // class ForEach extends TerminatingLooper { + // const iter: TerminatingLooper + // const body: Action + + // constructor(iter: TerminatingLooper, body: Action) + // requires iter.Valid() + // requires body.Valid() + // requires iter.Repr !! body.Repr + // requires iter.Valid() ==> body.Requires(iter) + // requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr + // ensures Valid() + // { + // this.iter := iter; + // this.body := body; + // Repr := {this} + iter.Repr + body.Repr; + // } + + // predicate Valid() + // reads this, Repr + // decreases Repr + // ensures Valid() ==> this in Repr + // { + // && this in Repr + // && ValidComponent(iter) + // && ValidComponent(body) + // && iter.Repr !! body.Repr + // && (iter.Valid() ==> body.Requires(iter)) + // // TODO: This needs to be a forall somehow + // && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) + // } + + // function Decreases(): nat + // reads Repr + // requires Valid() + // { + // iter.Decreases() + // } + + // predicate method Done() + // reads Repr + // requires Valid() + // ensures Decreases() == 0 ==> Done() + // { + // iter.Done() + // } + + // method Step() + // requires Valid() + // requires !Done() + // modifies Repr + // decreases Repr + // ensures ValidAndDisjoint() + // ensures Decreases() < old(Decreases()) + // { + // iter.Step(); + // Repr := Repr + iter.Repr; + + // assert iter.Valid(); + // assert this in Repr; + // assert ValidComponent(iter); + // assert ValidComponent(body); + // assert iter.Repr !! body.Repr; + // assert (iter.Valid() ==> body.Requires(iter)); + // assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); + // assert Valid(); + + // var _ := body.Invoke(iter); + // Repr := Repr + body.Repr; + // } + // } + + // class SeqCollector extends Action { + + // var elements: seq + + // constructor() + // ensures Valid() + // ensures fresh(Repr - {this}) + // { + // elements := []; + // Repr := {this}; + // } + + // predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // decreases Repr + // { + // && this in Repr + // } + + // method Invoke(t: T) returns (nothing: ()) + // requires Valid() + // requires Requires(t) + // modifies Modifies(t) + // decreases Decreases(t) + // ensures ValidAndDisjoint() + // ensures old(allocated(())) && Ensures(t, ()) + // { + // elements := elements + [t]; + // } + + // predicate Requires(t: T) { + // true + // } + + // // Need this separately from Repr for callers + // // Repr is the frame for Valid(), but callers + // // need to know what ELSE gets modified. + // function Modifies(t: T): set requires Requires(t) { + // {this} + // } + + // function Decreases(t: T): nat { + // 0 + // } + + // twostate predicate Ensures(t: T, nothing: ()) { + // true + // } + // } + + + // method Main() { + // var numbers := [1, 2, 3, 4, 5]; + // var numbersIter := new SeqLooper(numbers); + // var numbersPrinterLooper := new SeqCollector(); + // var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); + // DoLoop(forEachLoop); + // } +// } \ No newline at end of file diff --git a/src/Enumeration/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy similarity index 98% rename from src/Enumeration/IteratorAdaptor.dfy rename to src/Enumerators/IteratorAdaptor.dfy index 3015a127..32d2de91 100644 --- a/src/Enumeration/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -1,9 +1,9 @@ -include "Loopers.dfy" +include "Enumerators.dfy" module IteratorAdaptorExample { - import opened Loopers + import opened Enumerators iterator SeqIterator(s: seq) yields (element: T) yield ensures elements <= s diff --git a/src/Enumeration/JavaStyle.dfy b/src/Enumerators/JavaStyle.dfy similarity index 100% rename from src/Enumeration/JavaStyle.dfy rename to src/Enumerators/JavaStyle.dfy diff --git a/src/Enumeration/RustStyle.dfy b/src/Enumerators/RustStyle.dfy similarity index 95% rename from src/Enumeration/RustStyle.dfy rename to src/Enumerators/RustStyle.dfy index a25feef4..604fa21f 100644 --- a/src/Enumeration/RustStyle.dfy +++ b/src/Enumerators/RustStyle.dfy @@ -103,11 +103,11 @@ module RustStyle { } } - class SeqRustStyleIterator extends RustStyleIterator { + class SeqRustStyleIterator extends RustStyleIterator { - var s: seq + var s: seq - constructor(s: seq) + constructor(s: seq) ensures Valid() ensures fresh(Repr) { @@ -123,7 +123,7 @@ module RustStyle { && this in Repr } - method Next() returns (res: Option) + method Next() returns (res: Option) requires Valid() modifies Repr ensures Valid() From eee7d1dbcb20f44f0518ef61f58714ab66668c54 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 15 Feb 2022 12:34:20 -0800 Subject: [PATCH 11/68] Add some todos --- src/Enumerators/Enumerators.dfy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index b0c895f4..bff4b9b4 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -23,6 +23,7 @@ module Enumerators { // Dafny doesn't let you pass around an underspecified value though, // so we don't define a "to be enumerated" field or function. + // TODO: Change into `Next() returns T` so we don't need a separate Current() method method Step() requires Valid() requires !Done() @@ -395,4 +396,9 @@ module Enumerators { e.Step(); } } + + // TODO: Explicit example of working with lazy iterators, more emphasis on `Done` being a pure function + // TODO: Need to give background on Validatable, the fact that Valid() is the loop invariant + // TODO: Mention `enumerated` ghost variable, proving semantics based on that + // TODO: Fix Filter even before proving semantics? } \ No newline at end of file From 37015dd1c9698bfde7966ae46ced7cdbfc99d281 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 15 Feb 2022 18:20:20 -0800 Subject: [PATCH 12/68] (Mostly) converted Step() to Next() --- src/Enumerators/Datatypes.dfy | 76 ++++++++++ src/Enumerators/Enumerators.dfy | 210 ++++++++++++++++------------ src/Enumerators/IteratorAdaptor.dfy | 50 ++----- src/Enumerators/RustStyle.dfy | 26 ++-- 4 files changed, 220 insertions(+), 142 deletions(-) diff --git a/src/Enumerators/Datatypes.dfy b/src/Enumerators/Datatypes.dfy index 16e8b508..1e78c3bd 100644 --- a/src/Enumerators/Datatypes.dfy +++ b/src/Enumerators/Datatypes.dfy @@ -1,5 +1,10 @@ + +include "Enumerators.dfy" + module DatatypeEnumerator { + import opened Enumerators + datatype E = Done | Next(T, Enum) type Enum = () -> E @@ -19,4 +24,75 @@ module DatatypeEnumerator { // function CountupFrom(n: nat): Enum { // () => Next(n, CountupFrom(n + 1)) // } + + + // Implicit trait approach + datatype List = Cons(value: T, tail: List) | Nil { + method Enumerator() returns (e: Enumerator) { + e := new ListEnumerator(this); + } + + function Length(): nat { + match this + case Cons(_, tail) => 1 + tail.Length() + case Nil => 0 + } + } + + class ListEnumerator extends Enumerator { + + var next: List + + constructor(next: List) + ensures Valid() + ensures fresh(Repr - {this}) + { + this.next := next; + + enumerated := []; + Repr := {this}; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + } + + function Decreases(): nat + reads Repr + requires Valid() + { + // This is where I wish I could just say "next" and + // rely on the well-founded ordering. + next.Length() + } + + predicate method Done() + reads Repr + requires Valid() + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + next.Nil? + } + + method Next() returns (element: T) + requires Valid() + requires !Done() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [element] + { + element := next.value; + next := next.tail; + + enumerated := enumerated + [element]; + } + } } \ No newline at end of file diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index bff4b9b4..7131ea7a 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -23,8 +23,7 @@ module Enumerators { // Dafny doesn't let you pass around an underspecified value though, // so we don't define a "to be enumerated" field or function. - // TODO: Change into `Next() returns T` so we don't need a separate Current() method - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -32,7 +31,7 @@ module Enumerators { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] // Would be better as an arbitrary termination clause somehow instead // https://github.com/dafny-lang/dafny/issues/762 @@ -46,13 +45,12 @@ module Enumerators { requires Valid() decreases Repr, 0 ensures Decreases() == 0 ==> Done() - - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() } + // TODO: Common EagerEnumerator that wraps a Enumerator> + // for cases like Filter or IteratorAdaptor that can't calculate Done() + // ahead of time? + class SeqEnumerator extends Enumerator { const elements: seq @@ -96,7 +94,7 @@ module Enumerators { index == |elements| } - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -104,25 +102,17 @@ module Enumerators { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; + element := elements[index]; + enumerated := enumerated + [element]; index := index + 1; } - - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - elements[index] - } } class SetEnumerator extends Enumerator { ghost const original: set var remaining: set - var current: Option constructor(s: set) ensures Valid() @@ -130,14 +120,6 @@ module Enumerators { { this.original := s; this.remaining := s; - new; - if |remaining| > 0 { - var element: T :| element in remaining; - current := Some(element); - remaining := remaining - {element}; - } else { - current := None; - } enumerated := []; Repr := {this}; @@ -148,7 +130,6 @@ module Enumerators { ensures Valid() ==> this in Repr { && this in Repr - && (current.None? ==> |remaining| == 0) } predicate method Done() @@ -157,10 +138,10 @@ module Enumerators { decreases Repr, 0 ensures Decreases() == 0 ==> Done() { - current.None? + |remaining| == 0 } - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -168,37 +149,79 @@ module Enumerators { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; - - if |remaining| > 0 { - var element: T :| element in remaining; - current := Some(element); - remaining := remaining - {element}; - } else { - current := None; - } + var picked: T :| picked in remaining; + element := picked; + remaining := remaining - {element}; + enumerated := enumerated + [element]; } - function method Current(): T + function Decreases(): nat reads this, Repr + requires Valid() + { + |remaining| + } + } + + class MapEnumerator extends Enumerator { + const wrapped: Enumerator + const f: T -> R + + constructor(f: T -> R, wrapped: Enumerator) + requires wrapped.Valid() + ensures Valid() + { + this.wrapped := wrapped; + this.f := f; + Repr := {this} + wrapped.Repr; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr + { + && this in Repr + && ValidComponent(wrapped) + } + + predicate method Done() + reads Repr + requires Valid() + decreases Repr, 0 + ensures Decreases() == 0 ==> Done() + { + wrapped.Done() + } + + method Next() returns (element: R) requires Valid() requires !Done() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures enumerated == old(enumerated) + [element] { - current.value + var t := wrapped.Next(); + element := f(t); + enumerated := enumerated + [element]; } function Decreases(): nat reads this, Repr requires Valid() { - |remaining| + (if current.Some? then 1 else 0) + wrapped.Decreases() } } class ConcatEnumerator extends Enumerator { + // TODO: Unset once each is Done() to allow garbage collection? const first: Enumerator const second: Enumerator @@ -233,7 +256,7 @@ module Enumerators { first.Done() && second.Done() } - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -241,27 +264,16 @@ module Enumerators { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; - if !first.Done() { - first.Step(); + element := first.Next(); } else { - second.Step(); + element := second.Next(); } - Repr := {this} + first.Repr + second.Repr; - } - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - if !first.Done() then - first.Current() - else - second.Current() + Repr := {this} + first.Repr + second.Repr; + enumerated := enumerated + [element]; } function Decreases(): nat @@ -273,9 +285,10 @@ module Enumerators { } // TODO: Prove the semantics! - class Filter extends Enumerator { + class Filter extends Enumerator { const wrapped: Enumerator const filter: T -> bool + var next: Option constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() @@ -297,10 +310,10 @@ module Enumerators { decreases Repr, 0 ensures Decreases() == 0 ==> Done() { - wrapped.Done() + next.None? } - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -308,28 +321,29 @@ module Enumerators { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; - - wrapped.Step(); - while (!wrapped.Done() && !filter(wrapped.Current())) - invariant Valid() - invariant wrapped.Repr < old(Repr) - invariant Repr == old(Repr) - invariant enumerated == old(enumerated) + [old(Current())] - decreases wrapped.Decreases() - { - wrapped.Step(); - } - } + element := next.value; + enumerated := enumerated + [element]; - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - wrapped.Current() + if wrapped.Done() { + next := None; + } else { + var t := wrapped.Next(); + while (!filter(t) && !wrapped.Done()) + invariant Valid() + invariant wrapped.Repr < old(Repr) + invariant Repr == old(Repr) + decreases wrapped.Decreases() + { + t := wrapped.Next(); + } + if filter(t) { + next := Some(t); + } else { + next := None; + } + } } function Decreases(): nat @@ -374,9 +388,9 @@ module Enumerators { invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { - print e.Current(), "\n"; + var element := e.Next(); - e.Step(); + print element, "\n"; } } @@ -391,10 +405,28 @@ module Enumerators { invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { - print e.Current(), "\n"; - - e.Step(); + var element := e.Next(); + + print element, "\n"; + } + } + + method PrintWithCommas() { + var first := [1, 2, 3, 4, 5]; + var e := new SeqEnumerator(first); + + while (!e.Done()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + print element; + if !e.Done() { + print ", "; + } } + print "\n"; } // TODO: Explicit example of working with lazy iterators, more emphasis on `Done` being a pure function diff --git a/src/Enumerators/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy index 32d2de91..b7b2a0bc 100644 --- a/src/Enumerators/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -1,10 +1,12 @@ include "Enumerators.dfy" +include "../Wrappers.dfy" module IteratorAdaptorExample { - import opened Enumerators + import opened Enumerators + import opened Wrappers iterator SeqIterator(s: seq) yields (element: T) yield ensures elements <= s ensures elements == s @@ -17,7 +19,7 @@ module IteratorAdaptorExample { } } - class SeqIteratorEnumerator extends Enumerator { + class SeqIteratorEnumerator extends Enumerator> { const iter: SeqIterator var done: bool @@ -30,18 +32,14 @@ module IteratorAdaptorExample { ensures fresh(Repr) { iter := new SeqIterator(s); + done := false; original := s; enumerated := []; new; - - // Calling MoveNext() right away ensures we only enumerate yielded state. - var more := iter.MoveNext(); - done := !more; Repr := {this, iter} + iter._modifies + iter._reads + iter._new; decr := |iter.s| - |enumerated|; - assert !done ==> iter.elements < iter.s; } predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { @@ -54,8 +52,6 @@ module IteratorAdaptorExample { && iter._reads <= Repr && iter._new <= Repr && (!done ==> iter.Valid()) - && (!done ==> enumerated + [iter.element] == iter.elements) - && (done ==> enumerated == iter.elements) && (!done ==> iter.elements < iter.s) && decr == |iter.s| - |enumerated| } @@ -69,7 +65,7 @@ module IteratorAdaptorExample { done } - method Step() + method Next() returns (element: Option) requires Valid() requires !Done() modifies Repr @@ -77,38 +73,18 @@ module IteratorAdaptorExample { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; - - var more := iter.MoveNext(); - done := !more; - assert iter._new == {}; + var more := iter.MoveNext(); + if more { + element := Some(iter.element); + } else { + element := None; + } Repr := {this, iter} + iter._modifies + iter._reads + iter._new; decr := |iter.s| - |enumerated|; - - assert this in Repr; - assert iter in Repr; - assert this !in iter._modifies; - assert this !in iter._reads; - assert this !in iter._new; - assert iter._modifies <= Repr; - assert iter._reads <= Repr; - assert iter._new <= Repr; - assert (!done ==> iter.Valid()); - assert (!done ==> enumerated + [iter.element] == iter.elements); - assert (done ==> enumerated == iter.elements); - assert !done ==> iter.elements < iter.s; - } - - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - iter.element } function Decreases(): nat diff --git a/src/Enumerators/RustStyle.dfy b/src/Enumerators/RustStyle.dfy index 604fa21f..2853173d 100644 --- a/src/Enumerators/RustStyle.dfy +++ b/src/Enumerators/RustStyle.dfy @@ -1,13 +1,13 @@ include "../Frames.dfy" include "../Wrappers.dfy" -include "Loopers.dfy" +include "Enumerators.dfy" module RustStyle { import opened Frames import opened Wrappers - import opened Loopers + import opened Enumerators trait RustStyleIterator extends Validatable { @@ -59,7 +59,7 @@ module RustStyle { && decr == (if this.next.None? then 0 else iter.Decreases() + 1) } - method Step() + method Next() returns (element: T) requires Valid() requires !Done() modifies Repr @@ -67,9 +67,11 @@ module RustStyle { ensures Valid() ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [old(Current())] + ensures enumerated == old(enumerated) + [element] { - enumerated := enumerated + [Current()]; + element := next.value; + enumerated := enumerated + [element]; + next := iter.Next(); if this.next.None? { decr := 0; @@ -78,14 +80,6 @@ module RustStyle { } } - function method Current(): T - reads this, Repr - requires Valid() - requires !Done() - { - next.value - } - function Decreases(): nat reads Repr requires Valid() @@ -155,11 +149,11 @@ module RustStyle { invariant enum.Valid() && fresh(enum.Repr) decreases enum.Decreases() { + var element := enum.Next(); + // Loop body starts - print enum.Current(); + print element, "\n"; // Loop body ends - - enum.Step(); } } } \ No newline at end of file From 924d6858ce70ee712952a93d488cda8c063426fd Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 15 Feb 2022 22:50:44 -0800 Subject: [PATCH 13/68] Progress on fixing Filter --- src/Enumerators/Enumerators.dfy | 43 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 7131ea7a..72224274 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -244,7 +244,7 @@ module Enumerators { && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr - && enumerated == first.enumerated + second.enumerated + // && enumerated == first.enumerated + second.enumerated } predicate method Done() @@ -292,11 +292,15 @@ module Enumerators { constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() + modifies wrapped.Repr ensures Valid() { this.wrapped := wrapped; this.filter := filter; + this.next := None; Repr := {this} + wrapped.Repr; + new; + FindNext(); } predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { @@ -326,22 +330,31 @@ module Enumerators { element := next.value; enumerated := enumerated + [element]; - if wrapped.Done() { - next := None; - } else { + FindNext(); + } + + method FindNext() + requires Valid() + requires next.Some? + modifies Repr + decreases Repr, 0 + ensures Valid() + ensures Repr <= old(Repr) + ensures Decreases() < old(Decreases()) + ensures unchanged(this`enumerated) + { + next := None; + while (!wrapped.Done()) + invariant Valid() + invariant wrapped.Repr < old(Repr) + invariant Repr == old(Repr) + invariant unchanged(this`enumerated) + decreases wrapped.Decreases() + { var t := wrapped.Next(); - while (!filter(t) && !wrapped.Done()) - invariant Valid() - invariant wrapped.Repr < old(Repr) - invariant Repr == old(Repr) - decreases wrapped.Decreases() - { - t := wrapped.Next(); - } if filter(t) { next := Some(t); - } else { - next := None; + return; } } } @@ -350,7 +363,7 @@ module Enumerators { reads this, Repr requires Valid() { - wrapped.Decreases() + wrapped.Decreases() + (if next.Some? then 1 else 0) } } From 323abfd1ecc7a67e08963cb7b9eec15c6c5e06d1 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 16 Feb 2022 15:55:16 -0800 Subject: [PATCH 14/68] Replace Done() with HasNext() --- src/Enumerators/Datatypes.dfy | 8 +-- src/Enumerators/Enumerators.dfy | 91 ++++++++++++++++------------- src/Enumerators/IteratorAdaptor.dfy | 18 +++--- src/Enumerators/RustStyle.dfy | 10 ++-- 4 files changed, 68 insertions(+), 59 deletions(-) diff --git a/src/Enumerators/Datatypes.dfy b/src/Enumerators/Datatypes.dfy index 1e78c3bd..201ab9e8 100644 --- a/src/Enumerators/Datatypes.dfy +++ b/src/Enumerators/Datatypes.dfy @@ -70,18 +70,18 @@ module DatatypeEnumerator { next.Length() } - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - next.Nil? + next.Cons? } method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 72224274..718e3108 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -25,7 +25,7 @@ module Enumerators { method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -40,11 +40,11 @@ module Enumerators { decreases Repr requires Valid() - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() } // TODO: Common EagerEnumerator that wraps a Enumerator> @@ -84,19 +84,19 @@ module Enumerators { |elements| - index } - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() - ensures Done() ==> enumerated == elements + ensures Decreases() == 0 ==> !HasNext() + ensures !HasNext() ==> enumerated == elements { - index == |elements| + index < |elements| } method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -132,18 +132,18 @@ module Enumerators { && this in Repr } - predicate method Done() + predicate method HasNext() requires Valid() reads this, Repr decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - |remaining| == 0 + |remaining| > 0 } method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -187,18 +187,18 @@ module Enumerators { && ValidComponent(wrapped) } - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - wrapped.Done() + wrapped.HasNext() } method Next() returns (element: R) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -247,18 +247,18 @@ module Enumerators { // && enumerated == first.enumerated + second.enumerated } - predicate method Done() + predicate method HasNext() requires Valid() reads this, Repr decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - first.Done() && second.Done() + first.HasNext() || second.HasNext() } method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -266,7 +266,7 @@ module Enumerators { ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [element] { - if !first.Done() { + if first.HasNext() { element := first.Next(); } else { element := second.Next(); @@ -290,6 +290,8 @@ module Enumerators { const filter: T -> bool var next: Option + ghost var decr: nat + constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() modifies wrapped.Repr @@ -299,6 +301,7 @@ module Enumerators { this.filter := filter; this.next := None; Repr := {this} + wrapped.Repr; + decr := wrapped.Decreases(); new; FindNext(); } @@ -308,18 +311,18 @@ module Enumerators { && ValidComponent(wrapped) } - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - next.None? + next.Some? } method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -335,26 +338,33 @@ module Enumerators { method FindNext() requires Valid() - requires next.Some? modifies Repr decreases Repr, 0 ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) + ensures old(next.Some?) ==> Decreases() < old(Decreases()) ensures unchanged(this`enumerated) { - next := None; - while (!wrapped.Done()) - invariant Valid() - invariant wrapped.Repr < old(Repr) - invariant Repr == old(Repr) - invariant unchanged(this`enumerated) - decreases wrapped.Decreases() - { + // TODO: Had to unroll the loop a bit to prove that Decreases() decreases, + // is it possible to clean this up? + if (!wrapped.HasNext()) { + next := None; + } else { var t := wrapped.Next(); if filter(t) { next := Some(t); - return; + } + while (wrapped.HasNext() && next.None?) + invariant Valid() + invariant wrapped.Repr < old(Repr) + invariant Repr == old(Repr) + invariant unchanged(this`enumerated) + decreases wrapped.Decreases() + { + var t := wrapped.Next(); + if filter(t) { + next := Some(t); + } } } } @@ -397,7 +407,7 @@ module Enumerators { var numbers := [1, 2, 3, 4, 5]; var e: Enumerator := new SeqEnumerator(numbers); - while (!e.Done()) + while (e.HasNext()) invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { @@ -414,7 +424,7 @@ module Enumerators { var e2 := new SeqEnumerator(second); var e := new ConcatEnumerator(e1, e2); - while (!e.Done()) + while (e.HasNext()) invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { @@ -428,14 +438,14 @@ module Enumerators { var first := [1, 2, 3, 4, 5]; var e := new SeqEnumerator(first); - while (!e.Done()) + while (e.HasNext()) invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var element := e.Next(); print element; - if !e.Done() { + if e.HasNext() { print ", "; } } @@ -445,5 +455,4 @@ module Enumerators { // TODO: Explicit example of working with lazy iterators, more emphasis on `Done` being a pure function // TODO: Need to give background on Validatable, the fact that Valid() is the loop invariant // TODO: Mention `enumerated` ghost variable, proving semantics based on that - // TODO: Fix Filter even before proving semantics? } \ No newline at end of file diff --git a/src/Enumerators/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy index b7b2a0bc..87d363f8 100644 --- a/src/Enumerators/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -22,7 +22,7 @@ module IteratorAdaptorExample { class SeqIteratorEnumerator extends Enumerator> { const iter: SeqIterator - var done: bool + var hasNext: bool ghost const original: seq ghost var decr: nat @@ -32,7 +32,7 @@ module IteratorAdaptorExample { ensures fresh(Repr) { iter := new SeqIterator(s); - done := false; + hasNext := true; original := s; enumerated := []; @@ -51,23 +51,23 @@ module IteratorAdaptorExample { && iter._modifies <= Repr && iter._reads <= Repr && iter._new <= Repr - && (!done ==> iter.Valid()) - && (!done ==> iter.elements < iter.s) + && (hasNext ==> iter.Valid()) + && (hasNext ==> iter.elements < iter.s) && decr == |iter.s| - |enumerated| } - predicate method Done() + predicate method HasNext() requires Valid() reads this, Repr decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - done + hasNext } method Next() returns (element: Option) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -91,7 +91,7 @@ module IteratorAdaptorExample { reads this, Repr requires Valid() { - decr + decr } } } \ No newline at end of file diff --git a/src/Enumerators/RustStyle.dfy b/src/Enumerators/RustStyle.dfy index 2853173d..3e43d216 100644 --- a/src/Enumerators/RustStyle.dfy +++ b/src/Enumerators/RustStyle.dfy @@ -61,7 +61,7 @@ module RustStyle { method Next() returns (element: T) requires Valid() - requires !Done() + requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -87,13 +87,13 @@ module RustStyle { decr } - predicate method Done() + predicate method HasNext() reads Repr requires Valid() decreases Repr, 0 - ensures Decreases() == 0 ==> Done() + ensures Decreases() == 0 ==> !HasNext() { - next.None? + next.Some? } } @@ -145,7 +145,7 @@ module RustStyle { var iter := new SeqRustStyleIterator([1,2,3,4,5]); var enum: Enumerator := new RustStyleIteratorEnumerator(iter); - while (!enum.Done()) + while (enum.HasNext()) invariant enum.Valid() && fresh(enum.Repr) decreases enum.Decreases() { From aae551857f27d7d304a9faaac0c2f3daf22dcace Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 17 Feb 2022 18:48:50 -0800 Subject: [PATCH 15/68] Partial progress on proving enumerator semantics --- examples/Enumerators/Enumerators.dfy | 53 ++++++++++ src/Enumerators/Enumerators.dfy | 143 +++++++++++++++++++-------- 2 files changed, 155 insertions(+), 41 deletions(-) create mode 100644 examples/Enumerators/Enumerators.dfy diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy new file mode 100644 index 00000000..ee321ce3 --- /dev/null +++ b/examples/Enumerators/Enumerators.dfy @@ -0,0 +1,53 @@ +include "../../src/Enumerators/Enumerators.dfy" + +module Demo { + + import opened Enumerators + + method AddTwoToEach(s: seq) returns (result: seq) + ensures forall x | x in result :: x >= 2 + { + result := Seq.Map(x => x + 2, s); + } + + method RoundTrip(s: seq) returns (result: seq) + ensures result == s + { + var e: SeqEnumerator := new SeqEnumerator(s); + result := CollectToSeq(e); + } + + method AddTwoToEachEnumeratorVersion(s: seq) returns (result: seq) + ensures result == Seq.Map(x => x + 2, s) + { + var e := new SeqEnumerator(s); + var mapped := new MappingEnumerator(x => x + 2, e); + result := CollectToSeq(mapped); + } + + method SetToSeq(s: set) returns (result: seq) + ensures multiset(result) == multiset(s) + { + var e := new SetEnumerator(s); + result := CollectToSeq(e); + } + + method Filter(s: seq, p: T -> bool) returns (result: seq) + ensures result == Seq.Filter(p, s) + { + var e := new SeqEnumerator(s); + var filtered := new Filter(e, p); + result := CollectToSeq(e); + } + + method Concatenate(first: seq, second: seq) returns (result: seq) + ensures result == first + second + { + var e1: Enumerator := new SeqEnumerator(first); + var e2 := new SeqEnumerator(second); + var concatenated: ConcatEnumerator := new ConcatEnumerator(e1, e2); + result := CollectToSeq(concatenated); + assert concatenated.Valid(); + } + +} \ No newline at end of file diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 718e3108..fff73a03 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -13,6 +13,10 @@ module Enumerators { trait {:termination false} Enumerator extends Validatable { + // The Valid() predicate from the Validatable trait ends up + // becoming the "enumeration invariant", which in turn becomes + // the loop invariant in a while loop that uses an enumerator. + ghost var enumerated: seq // Any enumerator that produces one value at a time @@ -23,6 +27,12 @@ module Enumerators { // Dafny doesn't let you pass around an underspecified value though, // so we don't define a "to be enumerated" field or function. + predicate method HasNext() + reads Repr + requires Valid() + decreases Repr, 0 + ensures Decreases() == 0 ==> !HasNext() + method Next() returns (element: T) requires Valid() requires HasNext() @@ -39,16 +49,10 @@ module Enumerators { reads Repr decreases Repr requires Valid() - - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() } // TODO: Common EagerEnumerator that wraps a Enumerator> - // for cases like Filter or IteratorAdaptor that can't calculate Done() + // for cases like Filter or IteratorAdaptor that can't calculate HasNext() // ahead of time? class SeqEnumerator extends Enumerator { @@ -59,6 +63,8 @@ module Enumerators { constructor(s: seq) ensures Valid() ensures fresh(Repr - {this}) + ensures enumerated == [] + ensures elements == s { elements := s; index := 0; @@ -117,6 +123,8 @@ module Enumerators { constructor(s: set) ensures Valid() ensures fresh(Repr) + ensures enumerated == [] + ensures original == s { this.original := s; this.remaining := s; @@ -127,9 +135,10 @@ module Enumerators { predicate Valid() reads this, Repr - ensures Valid() ==> this in Repr + ensures Valid() ==> this in Repr { && this in Repr + && multiset(enumerated) + multiset(remaining) == multiset(original) } predicate method HasNext() @@ -165,17 +174,23 @@ module Enumerators { } } - class MapEnumerator extends Enumerator { + class MappingEnumerator extends Enumerator { const wrapped: Enumerator const f: T -> R constructor(f: T -> R, wrapped: Enumerator) requires wrapped.Valid() + requires wrapped.enumerated == [] ensures Valid() + ensures fresh(Repr - wrapped.Repr) + ensures enumerated == [] + ensures this.wrapped == wrapped + ensures this.f == f { this.wrapped := wrapped; this.f := f; Repr := {this} + wrapped.Repr; + enumerated := []; } predicate Valid() @@ -185,6 +200,7 @@ module Enumerators { { && this in Repr && ValidComponent(wrapped) + && enumerated == Seq.Map(f, wrapped.enumerated) } predicate method HasNext() @@ -219,7 +235,7 @@ module Enumerators { } } - class ConcatEnumerator extends Enumerator { + class ConcatEnumerator extends Enumerator { // TODO: Unset once each is Done() to allow garbage collection? const first: Enumerator @@ -231,6 +247,8 @@ module Enumerators { requires first.Repr !! second.Repr ensures Valid() ensures fresh(Repr - first.Repr - second.Repr) + ensures this.first == first + ensures this.second == second { this.first := first; this.second := second; @@ -244,7 +262,8 @@ module Enumerators { && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr - // && enumerated == first.enumerated + second.enumerated + // && (first.HasNext() ==> second.enumerated == []) + && enumerated == first.enumerated + second.enumerated } predicate method HasNext() @@ -290,18 +309,17 @@ module Enumerators { const filter: T -> bool var next: Option - ghost var decr: nat - constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() + requires wrapped.enumerated == [] modifies wrapped.Repr - ensures Valid() + ensures Valid() { this.wrapped := wrapped; this.filter := filter; this.next := None; Repr := {this} + wrapped.Repr; - decr := wrapped.Decreases(); + enumerated := []; new; FindNext(); } @@ -309,6 +327,7 @@ module Enumerators { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { && this in Repr && ValidComponent(wrapped) + // && (if next.Some? then enumerated + [next.value] else enumerated) == Seq.Filter(filter, wrapped.enumerated) } predicate method HasNext() @@ -377,31 +396,52 @@ module Enumerators { } } - // method Max(s: set) returns (max: int) - // requires |s| > 0 - // ensures max in s - // ensures forall x | x in s :: max >= x - // { - // var first: int :| first in s; - // max := first; - // var sEnum: SetEnumerator := new SetEnumerator(s - {max}); - // assert fresh(sEnum.Repr); - // label start: - // var more := true; - // while (!sEnum.Done()) - // invariant sEnum.Valid() - // invariant fresh(sEnum.Repr) - // invariant max == Seq.Max([first] + sEnum.enumerated) - // decreases more, sEnum.Decreases() - // { - // var more := sEnum.MoveNext(); - // if !more { break; } - - // if sEnum.Current() > max { - // max := sEnum.Current(); - // } - // } - // } + method CollectToSeq(e: Enumerator) returns (s: seq) + requires e.Valid() + // TODO: Might remove this + requires e.enumerated == [] + modifies e.Repr + ensures e.Valid() + ensures !e.HasNext() + ensures s == e.enumerated + { + s := []; + while (e.HasNext()) + invariant e.Valid() && e.Repr <= old(e.Repr) + decreases e.Decreases() + + invariant s == e.enumerated + { + var element := e.Next(); + s := s + [element]; + } + } + + method Max(s: set) returns (max: int) + requires |s| > 0 + ensures max in s + ensures forall x | x in s :: max >= x + { + var first: int :| first in s; + max := first; + var rest := s - {max}; + + var sEnum: SetEnumerator := new SetEnumerator(rest); + while (sEnum.HasNext()) + invariant sEnum.Valid() + invariant fresh(sEnum.Repr) + invariant max == Seq.Max([first] + sEnum.enumerated) + decreases sEnum.Decreases() + { + var element := sEnum.Next(); + + if max < element { + max := element; + } + } + assert max == Seq.Max([first] + sEnum.enumerated); + assert multiset(sEnum.enumerated) == multiset(rest); + } method Example1() { var numbers := [1, 2, 3, 4, 5]; @@ -452,7 +492,28 @@ module Enumerators { print "\n"; } - // TODO: Explicit example of working with lazy iterators, more emphasis on `Done` being a pure function + method MappingExample() { + var first := [1, 2, 3, 4, 5]; + var e1 := new SeqEnumerator(first); + var e := new MappingEnumerator(x => x + 2, e1); + + var result: seq := []; + while (e.HasNext()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + result := result + [element]; + } + assert e.enumerated == Seq.Map(x => x + 2, first); + } + + // TODO: Explicit example of working with lazy iterators, more emphasis on `HasNext` being a pure function // TODO: Need to give background on Validatable, the fact that Valid() is the loop invariant // TODO: Mention `enumerated` ghost variable, proving semantics based on that + // TODO: Are ghost enumerators a thing? :) + // TODO: Most enumerator constructors should ensure enumerated == [] and other things + // Important to have end to end examples to ensure correctness invariants are actually usable! + // TODO: Framing invariant is a pain in the butt, seems to need a label to be generic } \ No newline at end of file From 2ebc1865efda3a75f68cb7ba5968d233632288a2 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Fri, 18 Feb 2022 17:12:15 -0800 Subject: [PATCH 16/68] Progress on fixing FilterEnumerator --- examples/Enumerators/Enumerators.dfy | 4 +- src/Enumerators/Enumerators.dfy | 118 ++++++++++++++++----------- src/Enumerators/IteratorAdaptor.dfy | 31 +++++-- src/Enumerators/RustStyle.dfy | 7 +- src/Frames.dfy | 1 + 5 files changed, 103 insertions(+), 58 deletions(-) diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index ee321ce3..07bd1b67 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -36,8 +36,8 @@ module Demo { ensures result == Seq.Filter(p, s) { var e := new SeqEnumerator(s); - var filtered := new Filter(e, p); - result := CollectToSeq(e); + var filtered := new Filter(e, p); + result := CollectToSeq(filtered); } method Concatenate(first: seq, second: seq) returns (result: seq) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index fff73a03..c725d0f1 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -30,8 +30,8 @@ module Enumerators { predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 method Next() returns (element: T) requires Valid() @@ -47,7 +47,7 @@ module Enumerators { // https://github.com/dafny-lang/dafny/issues/762 function Decreases(): nat reads Repr - decreases Repr + decreases Repr, 1 requires Valid() } @@ -76,7 +76,7 @@ module Enumerators { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr + decreases Repr, 0 { && this in Repr && 0 <= index <= |elements| @@ -86,6 +86,7 @@ module Enumerators { function Decreases(): nat reads Repr requires Valid() + decreases Repr, 1 { |elements| - index } @@ -93,8 +94,8 @@ module Enumerators { predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 ensures !HasNext() ==> enumerated == elements { index < |elements| @@ -136,6 +137,7 @@ module Enumerators { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr + decreases Repr, 0 { && this in Repr && multiset(enumerated) + multiset(remaining) == multiset(original) @@ -144,8 +146,8 @@ module Enumerators { predicate method HasNext() requires Valid() reads this, Repr - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 { |remaining| > 0 } @@ -169,6 +171,7 @@ module Enumerators { function Decreases(): nat reads this, Repr requires Valid() + decreases Repr, 1 { |remaining| } @@ -196,7 +199,7 @@ module Enumerators { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr + decreases Repr, 0 { && this in Repr && ValidComponent(wrapped) @@ -206,9 +209,10 @@ module Enumerators { predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 { + assert wrapped.HasNext() ==> Decreases() > 0; wrapped.HasNext() } @@ -230,6 +234,7 @@ module Enumerators { function Decreases(): nat reads this, Repr requires Valid() + decreases Repr, 1 { wrapped.Decreases() } @@ -242,8 +247,8 @@ module Enumerators { const second: Enumerator constructor(first: Enumerator, second: Enumerator) - requires first.Valid() - requires second.Valid() + requires first.Valid() && first.enumerated == [] + requires second.Valid() && second.enumerated == [] requires first.Repr !! second.Repr ensures Valid() ensures fresh(Repr - first.Repr - second.Repr) @@ -257,21 +262,27 @@ module Enumerators { Repr := {this} + first.Repr + second.Repr; } - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { && this in Repr && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr - // && (first.HasNext() ==> second.enumerated == []) + && (first.HasNext() ==> second.enumerated == []) && enumerated == first.enumerated + second.enumerated } predicate method HasNext() requires Valid() reads this, Repr - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 { + assert first.HasNext() ==> Decreases() > 0; + assert second.HasNext() ==> Decreases() > 0; first.HasNext() || second.HasNext() } @@ -287,8 +298,10 @@ module Enumerators { { if first.HasNext() { element := first.Next(); + assert first.Decreases() < old(first.Decreases()); } else { element := second.Next(); + assert second.Decreases() < old(second.Decreases()); } Repr := {this} + first.Repr + second.Repr; @@ -298,6 +311,7 @@ module Enumerators { function Decreases(): nat reads this, Repr requires Valid() + decreases Repr, 1 { first.Decreases() + second.Decreases() } @@ -314,6 +328,10 @@ module Enumerators { requires wrapped.enumerated == [] modifies wrapped.Repr ensures Valid() + ensures enumerated == [] + ensures fresh(Repr - wrapped.Repr) + ensures this.wrapped == wrapped + ensures this.filter == filter { this.wrapped := wrapped; this.filter := filter; @@ -324,18 +342,23 @@ module Enumerators { FindNext(); } - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { && this in Repr && ValidComponent(wrapped) - // && (if next.Some? then enumerated + [next.value] else enumerated) == Seq.Filter(filter, wrapped.enumerated) + && (if next.Some? then enumerated + [next.value] else enumerated) == Seq.Filter(filter, wrapped.enumerated) } predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 { + assert if next.Some? then Decreases() >= 1 else true; next.Some? } @@ -391,6 +414,7 @@ module Enumerators { function Decreases(): nat reads this, Repr requires Valid() + decreases Repr, 1 { wrapped.Decreases() + (if next.Some? then 1 else 0) } @@ -417,31 +441,31 @@ module Enumerators { } } - method Max(s: set) returns (max: int) - requires |s| > 0 - ensures max in s - ensures forall x | x in s :: max >= x - { - var first: int :| first in s; - max := first; - var rest := s - {max}; - - var sEnum: SetEnumerator := new SetEnumerator(rest); - while (sEnum.HasNext()) - invariant sEnum.Valid() - invariant fresh(sEnum.Repr) - invariant max == Seq.Max([first] + sEnum.enumerated) - decreases sEnum.Decreases() - { - var element := sEnum.Next(); - - if max < element { - max := element; - } - } - assert max == Seq.Max([first] + sEnum.enumerated); - assert multiset(sEnum.enumerated) == multiset(rest); - } + // method Max(s: set) returns (max: int) + // requires |s| > 0 + // ensures max in s + // ensures forall x | x in s :: max >= x + // { + // var first: int :| first in s; + // max := first; + // var rest := s - {max}; + + // var sEnum: SetEnumerator := new SetEnumerator(rest); + // while (sEnum.HasNext()) + // invariant sEnum.Valid() + // invariant fresh(sEnum.Repr) + // invariant max == Seq.Max([first] + sEnum.enumerated) + // decreases sEnum.Decreases() + // { + // var element := sEnum.Next(); + + // if max < element { + // max := element; + // } + // } + // assert max == Seq.Max([first] + sEnum.enumerated); + // assert multiset(sEnum.enumerated) == multiset(rest); + // } method Example1() { var numbers := [1, 2, 3, 4, 5]; @@ -515,5 +539,7 @@ module Enumerators { // TODO: Are ghost enumerators a thing? :) // TODO: Most enumerator constructors should ensure enumerated == [] and other things // Important to have end to end examples to ensure correctness invariants are actually usable! + // Also usually need to (at least for v1) require that child enumerators are fresh (enumerated == []) // TODO: Framing invariant is a pain in the butt, seems to need a label to be generic + // TODO: Example of various traversals of datatype trees/graphs } \ No newline at end of file diff --git a/src/Enumerators/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy index 87d363f8..0c536633 100644 --- a/src/Enumerators/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -30,7 +30,7 @@ module IteratorAdaptorExample { constructor(s: seq) ensures Valid() ensures fresh(Repr) - { + { iter := new SeqIterator(s); hasNext := true; original := s; @@ -40,9 +40,25 @@ module IteratorAdaptorExample { Repr := {this, iter} + iter._modifies + iter._reads + iter._new; decr := |iter.s| - |enumerated|; + + assert this in Repr; + assert iter in Repr; + assert this !in iter._modifies; + assert this !in iter._reads; + assert this !in iter._new; + assert iter._modifies <= Repr; + assert iter._reads <= Repr; + assert iter._new <= Repr; + assert (hasNext ==> iter.Valid()); + assert (hasNext ==> iter.elements < iter.s); + // assert decr == |iter.s| - |enumerated|; } - predicate Valid() reads this, Repr ensures Valid() ==> this in Repr { + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { && this in Repr && iter in Repr && this !in iter._modifies @@ -59,8 +75,8 @@ module IteratorAdaptorExample { predicate method HasNext() requires Valid() reads this, Repr - decreases Repr, 0 - ensures Decreases() == 0 ==> !HasNext() + decreases Repr, 2 + ensures HasNext() ==> Decreases() > 0 { hasNext } @@ -76,8 +92,8 @@ module IteratorAdaptorExample { ensures enumerated == old(enumerated) + [element] { - var more := iter.MoveNext(); - if more { + hasNext := iter.MoveNext(); + if hasNext { element := Some(iter.element); } else { element := None; @@ -89,7 +105,8 @@ module IteratorAdaptorExample { function Decreases(): nat reads this, Repr - requires Valid() + requires Valid() + decreases Repr, 1 { decr } diff --git a/src/Enumerators/RustStyle.dfy b/src/Enumerators/RustStyle.dfy index 3e43d216..a2c0febd 100644 --- a/src/Enumerators/RustStyle.dfy +++ b/src/Enumerators/RustStyle.dfy @@ -52,7 +52,7 @@ module RustStyle { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr + decreases Repr, 0 { && this in Repr && ValidComponent(iter) @@ -83,6 +83,7 @@ module RustStyle { function Decreases(): nat reads Repr requires Valid() + decreases Repr, 1 { decr } @@ -90,7 +91,7 @@ module RustStyle { predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 + decreases Repr, 2 ensures Decreases() == 0 ==> !HasNext() { next.Some? @@ -112,7 +113,7 @@ module RustStyle { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr + decreases Repr, 0 { && this in Repr } diff --git a/src/Frames.dfy b/src/Frames.dfy index a95fc8f7..695f6ac8 100644 --- a/src/Frames.dfy +++ b/src/Frames.dfy @@ -11,6 +11,7 @@ module Frames { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr + decreases Repr, 0 // Convenience predicate for when your object's validity depends on one // or more other objects. From 5e63373de07f8a99c758a17c85ccbeb471a8ceb3 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 19 Feb 2022 11:07:05 -0800 Subject: [PATCH 17/68] Proved semantics of FilteredEnumerator --- examples/Enumerators/Enumerators.dfy | 2 +- src/Enumerators/Enumerators.dfy | 46 +++++++++++++++------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 07bd1b67..14a44c78 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -36,7 +36,7 @@ module Demo { ensures result == Seq.Filter(p, s) { var e := new SeqEnumerator(s); - var filtered := new Filter(e, p); + var filtered := new FilteredEnumerator(e, p); result := CollectToSeq(filtered); } diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index c725d0f1..cf14b2d5 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -318,7 +318,7 @@ module Enumerators { } // TODO: Prove the semantics! - class Filter extends Enumerator { + class FilteredEnumerator extends Enumerator { const wrapped: Enumerator const filter: T -> bool var next: Option @@ -328,8 +328,8 @@ module Enumerators { requires wrapped.enumerated == [] modifies wrapped.Repr ensures Valid() + ensures fresh(Repr - old(wrapped.Repr)) ensures enumerated == [] - ensures fresh(Repr - wrapped.Repr) ensures this.wrapped == wrapped ensures this.filter == filter { @@ -338,7 +338,9 @@ module Enumerators { this.next := None; Repr := {this} + wrapped.Repr; enumerated := []; + new; + FindNext(); } @@ -357,6 +359,7 @@ module Enumerators { requires Valid() decreases Repr, 2 ensures HasNext() ==> Decreases() > 0 + ensures !HasNext() ==> !wrapped.HasNext() // TODO { assert if next.Some? then Decreases() >= 1 else true; next.Some? @@ -374,40 +377,37 @@ module Enumerators { { element := next.value; enumerated := enumerated + [element]; + next := None; FindNext(); } method FindNext() - requires Valid() + requires Valid() // TODO: Weaken this slightly so Valid() can imply next.None? ==> !wrapped.HasNext() + requires next.None? modifies Repr decreases Repr, 0 ensures Valid() - ensures Repr <= old(Repr) - ensures old(next.Some?) ==> Decreases() < old(Decreases()) + ensures Decreases() <= old(Decreases()) ensures unchanged(this`enumerated) + ensures unchanged(this`Repr) { - // TODO: Had to unroll the loop a bit to prove that Decreases() decreases, - // is it possible to clean this up? - if (!wrapped.HasNext()) { - next := None; - } else { + while (wrapped.HasNext() && next.None?) + invariant Valid() + invariant wrapped.Repr < old(Repr) + invariant Repr == old(Repr) + invariant unchanged(this`enumerated) + invariant Decreases() <= old(Decreases()) + decreases wrapped.Decreases() + { + var wrappedEnumeratedBefore := wrapped.enumerated; var t := wrapped.Next(); + reveal Seq.Filter(); + LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [t]); + if filter(t) { next := Some(t); } - while (wrapped.HasNext() && next.None?) - invariant Valid() - invariant wrapped.Repr < old(Repr) - invariant Repr == old(Repr) - invariant unchanged(this`enumerated) - decreases wrapped.Decreases() - { - var t := wrapped.Next(); - if filter(t) { - next := Some(t); - } - } } } @@ -416,6 +416,8 @@ module Enumerators { requires Valid() decreases Repr, 1 { + // If we could declare semi-arbitrary values as in decreases clauses, + // this could just be (wrapped.Decreases(), next) wrapped.Decreases() + (if next.Some? then 1 else 0) } } From a0e370fe85ab71afd8e0b7b34e7cec27611487da Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 19 Feb 2022 11:59:59 -0800 Subject: [PATCH 18/68] Fixed termination guarantee on FilteredEnumerator --- examples/Enumerators/Enumerators.dfy | 3 ++- src/Enumerators/Enumerators.dfy | 27 ++++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 14a44c78..64f40877 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -36,8 +36,9 @@ module Demo { ensures result == Seq.Filter(p, s) { var e := new SeqEnumerator(s); - var filtered := new FilteredEnumerator(e, p); + var filtered: FilteredEnumerator := new FilteredEnumerator(e, p); result := CollectToSeq(filtered); + assert filtered.Valid(); } method Concatenate(first: seq, second: seq) returns (result: seq) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index cf14b2d5..4958df83 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -344,9 +344,11 @@ module Enumerators { FindNext(); } - predicate Valid() + + + predicate AlmostValid() reads this, Repr - ensures Valid() ==> this in Repr + ensures AlmostValid() ==> this in Repr decreases Repr, 0 { && this in Repr @@ -354,6 +356,15 @@ module Enumerators { && (if next.Some? then enumerated + [next.value] else enumerated) == Seq.Filter(filter, wrapped.enumerated) } + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && AlmostValid() + && (next.None? ==> !wrapped.HasNext()) + } + predicate method HasNext() reads Repr requires Valid() @@ -383,21 +394,21 @@ module Enumerators { } method FindNext() - requires Valid() // TODO: Weaken this slightly so Valid() can imply next.None? ==> !wrapped.HasNext() + requires AlmostValid() requires next.None? modifies Repr decreases Repr, 0 ensures Valid() - ensures Decreases() <= old(Decreases()) + ensures Decreases() <= old(wrapped.Decreases() + (if next.Some? then 1 else 0)) ensures unchanged(this`enumerated) ensures unchanged(this`Repr) { while (wrapped.HasNext() && next.None?) - invariant Valid() + invariant AlmostValid() invariant wrapped.Repr < old(Repr) invariant Repr == old(Repr) invariant unchanged(this`enumerated) - invariant Decreases() <= old(Decreases()) + invariant wrapped.Decreases() + (if next.Some? then 1 else 0) <= old(wrapped.Decreases() + (if next.Some? then 1 else 0)) decreases wrapped.Decreases() { var wrappedEnumeratedBefore := wrapped.enumerated; @@ -413,7 +424,7 @@ module Enumerators { function Decreases(): nat reads this, Repr - requires Valid() + requires Valid() decreases Repr, 1 { // If we could declare semi-arbitrary values as in decreases clauses, @@ -544,4 +555,6 @@ module Enumerators { // Also usually need to (at least for v1) require that child enumerators are fresh (enumerated == []) // TODO: Framing invariant is a pain in the butt, seems to need a label to be generic // TODO: Example of various traversals of datatype trees/graphs + // TODO: Think about Enumerator (and hypothetical Aggregator) as special cases of + // Actions with a relationship between their pre- and post-conditions. } \ No newline at end of file From bcfbdb185d00827544429ac6c3d10f2fa83c5b2b Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 19 Feb 2022 14:28:27 -0800 Subject: [PATCH 19/68] Progress on iterator example --- src/Enumerators/IteratorAdaptor.dfy | 65 ++++++++++------------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/src/Enumerators/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy index 0c536633..00cecedc 100644 --- a/src/Enumerators/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -7,51 +7,35 @@ module IteratorAdaptorExample { import opened Enumerators import opened Wrappers - iterator SeqIterator(s: seq) yields (element: T) - yield ensures elements <= s - ensures elements == s + iterator RangeIterator(start: int, end: int) yields (element: int) + requires start <= end + yield ensures element - start + 1 == |elements| + ensures |elements| == end - start { - for i := 0 to |s| - invariant i == |elements| - invariant elements <= s + for i := start to end + invariant i - start == |elements| { - yield s[i]; + yield i; } } - class SeqIteratorEnumerator extends Enumerator> { + class RangeEnumerator extends Enumerator { - const iter: SeqIterator - var hasNext: bool + const iter: RangeIterator + var remaining: nat - ghost const original: seq - ghost var decr: nat - - constructor(s: seq) + constructor(start: int, end: int) + requires start <= end ensures Valid() ensures fresh(Repr) { - iter := new SeqIterator(s); - hasNext := true; - original := s; + iter := new RangeIterator(start, end); + remaining := end - start; enumerated := []; new; Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - decr := |iter.s| - |enumerated|; - - assert this in Repr; - assert iter in Repr; - assert this !in iter._modifies; - assert this !in iter._reads; - assert this !in iter._new; - assert iter._modifies <= Repr; - assert iter._reads <= Repr; - assert iter._new <= Repr; - assert (hasNext ==> iter.Valid()); - assert (hasNext ==> iter.elements < iter.s); - // assert decr == |iter.s| - |enumerated|; } predicate Valid() @@ -67,9 +51,7 @@ module IteratorAdaptorExample { && iter._modifies <= Repr && iter._reads <= Repr && iter._new <= Repr - && (hasNext ==> iter.Valid()) - && (hasNext ==> iter.elements < iter.s) - && decr == |iter.s| - |enumerated| + && (remaining > 0 ==> iter.Valid()) } predicate method HasNext() @@ -78,10 +60,10 @@ module IteratorAdaptorExample { decreases Repr, 2 ensures HasNext() ==> Decreases() > 0 { - hasNext + remaining > 0 } - method Next() returns (element: Option) + method Next() returns (element: int) requires Valid() requires HasNext() modifies Repr @@ -91,16 +73,11 @@ module IteratorAdaptorExample { ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [element] { - - hasNext := iter.MoveNext(); - if hasNext { - element := Some(iter.element); - } else { - element := None; - } + var more := iter.MoveNext(); + element := iter.element; + remaining := remaining - 1; Repr := {this, iter} + iter._modifies + iter._reads + iter._new; - decr := |iter.s| - |enumerated|; } function Decreases(): nat @@ -108,7 +85,7 @@ module IteratorAdaptorExample { requires Valid() decreases Repr, 1 { - decr + remaining } } } \ No newline at end of file From 9d5283139ac8646c831ffe919f13f4a6e262f797 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 21 Feb 2022 10:43:21 -0800 Subject: [PATCH 20/68] Fixed iterator adaptor example --- src/Enumerators/Enumerators.dfy | 4 ++-- src/Enumerators/IteratorAdaptor.dfy | 19 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 4958df83..5aa7f024 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -344,8 +344,6 @@ module Enumerators { FindNext(); } - - predicate AlmostValid() reads this, Repr ensures AlmostValid() ==> this in Repr @@ -554,7 +552,9 @@ module Enumerators { // Important to have end to end examples to ensure correctness invariants are actually usable! // Also usually need to (at least for v1) require that child enumerators are fresh (enumerated == []) // TODO: Framing invariant is a pain in the butt, seems to need a label to be generic + // Solution seems to be assuming enumerators are usually local, so fresh(e.Repr) // TODO: Example of various traversals of datatype trees/graphs // TODO: Think about Enumerator (and hypothetical Aggregator) as special cases of // Actions with a relationship between their pre- and post-conditions. + // TODO: Could we have a TerminationMeasure trait, implementable by potentially any type? } \ No newline at end of file diff --git a/src/Enumerators/IteratorAdaptor.dfy b/src/Enumerators/IteratorAdaptor.dfy index 00cecedc..8abf2881 100644 --- a/src/Enumerators/IteratorAdaptor.dfy +++ b/src/Enumerators/IteratorAdaptor.dfy @@ -4,16 +4,18 @@ include "../Wrappers.dfy" module IteratorAdaptorExample { - import opened Enumerators import opened Wrappers + iterator RangeIterator(start: int, end: int) yields (element: int) requires start <= end yield ensures element - start + 1 == |elements| + yield ensures _new == {}; ensures |elements| == end - start { for i := start to end invariant i - start == |elements| + invariant _new == {} { yield i; } @@ -35,7 +37,7 @@ module IteratorAdaptorExample { new; - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; + Repr := {this, iter}; } predicate Valid() @@ -45,13 +47,9 @@ module IteratorAdaptorExample { { && this in Repr && iter in Repr - && this !in iter._modifies - && this !in iter._reads - && this !in iter._new - && iter._modifies <= Repr - && iter._reads <= Repr - && iter._new <= Repr - && (remaining > 0 ==> iter.Valid()) + && iter._modifies + iter._reads + iter._new == {} + && iter.Valid() + && remaining == (iter.end - iter.start) - |iter.elements| } predicate method HasNext() @@ -75,9 +73,8 @@ module IteratorAdaptorExample { { var more := iter.MoveNext(); element := iter.element; + enumerated := enumerated + [element]; remaining := remaining - 1; - - Repr := {this, iter} + iter._modifies + iter._reads + iter._new; } function Decreases(): nat From 5b3b3152cb9f4e7ec112d711dd734c8e1b0eaf42 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 22 Feb 2022 14:59:04 -0800 Subject: [PATCH 21/68] Relocating example code --- {src => examples}/Enumerators/Datatypes.dfy | 21 --- .../Enumerators/IteratorAdaptor.dfy | 0 {src => examples}/Enumerators/RustStyle.dfy | 4 +- src/Enumerators/ForEach.dfy | 145 ------------------ src/Enumerators/JavaStyle.dfy | 55 ------- 5 files changed, 2 insertions(+), 223 deletions(-) rename {src => examples}/Enumerators/Datatypes.dfy (77%) rename {src => examples}/Enumerators/IteratorAdaptor.dfy (100%) rename {src => examples}/Enumerators/RustStyle.dfy (98%) delete mode 100644 src/Enumerators/ForEach.dfy delete mode 100644 src/Enumerators/JavaStyle.dfy diff --git a/src/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy similarity index 77% rename from src/Enumerators/Datatypes.dfy rename to examples/Enumerators/Datatypes.dfy index 201ab9e8..c1adb7c6 100644 --- a/src/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -4,27 +4,6 @@ include "Enumerators.dfy" module DatatypeEnumerator { import opened Enumerators - - datatype E = Done | Next(T, Enum) - type Enum = () -> E - - function OneTwoThree(): Enum { - () => Next(1, () => Next(2, () => Next(3, () => Done))) - } - - function CountdownFrom(n: nat): Enum { - () => - if n > 0 then - Next(n, CountdownFrom(n - 1)) - else - Done - } - - // Doesn't terminate so you can't do this - // function CountupFrom(n: nat): Enum { - // () => Next(n, CountupFrom(n + 1)) - // } - // Implicit trait approach datatype List = Cons(value: T, tail: List) | Nil { diff --git a/src/Enumerators/IteratorAdaptor.dfy b/examples/Enumerators/IteratorAdaptor.dfy similarity index 100% rename from src/Enumerators/IteratorAdaptor.dfy rename to examples/Enumerators/IteratorAdaptor.dfy diff --git a/src/Enumerators/RustStyle.dfy b/examples/Enumerators/RustStyle.dfy similarity index 98% rename from src/Enumerators/RustStyle.dfy rename to examples/Enumerators/RustStyle.dfy index a2c0febd..0ec7f336 100644 --- a/src/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/RustStyle.dfy @@ -1,6 +1,6 @@ -include "../Frames.dfy" -include "../Wrappers.dfy" +include "../../src/Frames.dfy" +include "../../src/Wrappers.dfy" include "Enumerators.dfy" module RustStyle { diff --git a/src/Enumerators/ForEach.dfy b/src/Enumerators/ForEach.dfy deleted file mode 100644 index 0c600770..00000000 --- a/src/Enumerators/ForEach.dfy +++ /dev/null @@ -1,145 +0,0 @@ - -// module ForEach { - // TODO: Some similarities between this and composing - // two Actions together. - // One is: - // var r := action1.Invoke(a); - // var r2 := action2.Invoke(r); - // Other is: - // var r := looper.Step(); - // var _ := action.Invoke(r); - // var r2 := looper.Step(); - // var _ := action.Invoke(r2); - // ... - - // class ForEach extends TerminatingLooper { - // const iter: TerminatingLooper - // const body: Action - - // constructor(iter: TerminatingLooper, body: Action) - // requires iter.Valid() - // requires body.Valid() - // requires iter.Repr !! body.Repr - // requires iter.Valid() ==> body.Requires(iter) - // requires iter.Valid() ==> body.Modifies(iter) !! iter.Repr - // ensures Valid() - // { - // this.iter := iter; - // this.body := body; - // Repr := {this} + iter.Repr + body.Repr; - // } - - // predicate Valid() - // reads this, Repr - // decreases Repr - // ensures Valid() ==> this in Repr - // { - // && this in Repr - // && ValidComponent(iter) - // && ValidComponent(body) - // && iter.Repr !! body.Repr - // && (iter.Valid() ==> body.Requires(iter)) - // // TODO: This needs to be a forall somehow - // && (iter.Valid() ==> body.Modifies(iter) !! iter.Repr) - // } - - // function Decreases(): nat - // reads Repr - // requires Valid() - // { - // iter.Decreases() - // } - - // predicate method Done() - // reads Repr - // requires Valid() - // ensures Decreases() == 0 ==> Done() - // { - // iter.Done() - // } - - // method Step() - // requires Valid() - // requires !Done() - // modifies Repr - // decreases Repr - // ensures ValidAndDisjoint() - // ensures Decreases() < old(Decreases()) - // { - // iter.Step(); - // Repr := Repr + iter.Repr; - - // assert iter.Valid(); - // assert this in Repr; - // assert ValidComponent(iter); - // assert ValidComponent(body); - // assert iter.Repr !! body.Repr; - // assert (iter.Valid() ==> body.Requires(iter)); - // assert (iter.Valid() ==> body.Modifies(iter) !! iter.Repr); - // assert Valid(); - - // var _ := body.Invoke(iter); - // Repr := Repr + body.Repr; - // } - // } - - // class SeqCollector extends Action { - - // var elements: seq - - // constructor() - // ensures Valid() - // ensures fresh(Repr - {this}) - // { - // elements := []; - // Repr := {this}; - // } - - // predicate Valid() - // reads this, Repr - // ensures Valid() ==> this in Repr - // decreases Repr - // { - // && this in Repr - // } - - // method Invoke(t: T) returns (nothing: ()) - // requires Valid() - // requires Requires(t) - // modifies Modifies(t) - // decreases Decreases(t) - // ensures ValidAndDisjoint() - // ensures old(allocated(())) && Ensures(t, ()) - // { - // elements := elements + [t]; - // } - - // predicate Requires(t: T) { - // true - // } - - // // Need this separately from Repr for callers - // // Repr is the frame for Valid(), but callers - // // need to know what ELSE gets modified. - // function Modifies(t: T): set requires Requires(t) { - // {this} - // } - - // function Decreases(t: T): nat { - // 0 - // } - - // twostate predicate Ensures(t: T, nothing: ()) { - // true - // } - // } - - - // method Main() { - // var numbers := [1, 2, 3, 4, 5]; - // var numbersIter := new SeqLooper(numbers); - // var numbersPrinterLooper := new SeqCollector(); - // var forEachLoop := new ForEach(numbersIter, numbersPrinterLooper); - // DoLoop(forEachLoop); - // } -// } \ No newline at end of file diff --git a/src/Enumerators/JavaStyle.dfy b/src/Enumerators/JavaStyle.dfy deleted file mode 100644 index 2ee3837b..00000000 --- a/src/Enumerators/JavaStyle.dfy +++ /dev/null @@ -1,55 +0,0 @@ - -include "../Frames.dfy" - -module JavaStyle { - - import opened Frames - - class JavaStyleIterator { - const s: seq - var index: nat - - constructor(s: seq) - ensures Valid() - { - this.s := s; - this.index := 0; - } - - predicate Valid() - reads this - { - && 0 <= index <= |s| - } - - function method HasNext(): (res: bool) - reads this - ensures res == (index < |s|) - { - index < |s| - } - - method Next() returns (res: T) - requires Valid() - requires HasNext() - modifies this - // ensures Valid() - ensures index == old(index) + 1 - { - res := s[index]; - index := index + 1; - } - } - - method Main() { - var s := [1, 2, 3, 4, 5]; - var iter := new JavaStyleIterator(s); - while (iter.HasNext()) - decreases |iter.s| - iter.index - { - var n := iter.Next(); - print n; - } - } - -} \ No newline at end of file From 23395ac02fef65868eaf7a3eb55e6a06304b38a7 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 09:39:15 -0800 Subject: [PATCH 22/68] Add Fold --- examples/Enumerators/Enumerators.dfy | 10 +++-- src/Enumerators/Enumerators.dfy | 57 ++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 64f40877..9dbc4f9b 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -13,7 +13,7 @@ module Demo { method RoundTrip(s: seq) returns (result: seq) ensures result == s { - var e: SeqEnumerator := new SeqEnumerator(s); + var e: Enumerator := new SeqEnumerator(s); result := CollectToSeq(e); } @@ -21,7 +21,7 @@ module Demo { ensures result == Seq.Map(x => x + 2, s) { var e := new SeqEnumerator(s); - var mapped := new MappingEnumerator(x => x + 2, e); + var mapped: Enumerator := new MappingEnumerator(x => x + 2, e); result := CollectToSeq(mapped); } @@ -45,10 +45,14 @@ module Demo { ensures result == first + second { var e1: Enumerator := new SeqEnumerator(first); - var e2 := new SeqEnumerator(second); + var e2: Enumerator := new SeqEnumerator(second); var concatenated: ConcatEnumerator := new ConcatEnumerator(e1, e2); result := CollectToSeq(concatenated); assert concatenated.Valid(); } + // TODO: Need to support the case of an invariant that isn't attached to a specific + // concrete type that implements Enumerator. + // Want to state "will enumerate this" without that being baked into Enumerator itself. + } \ No newline at end of file diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 5aa7f024..121760e9 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -27,6 +27,13 @@ module Enumerators { // Dafny doesn't let you pass around an underspecified value though, // so we don't define a "to be enumerated" field or function. + // Would be better as an arbitrary termination clause somehow instead. + // https://github.com/dafny-lang/dafny/issues/762 + function Decreases(): nat + reads Repr + decreases Repr, 1 + requires Valid() + predicate method HasNext() reads Repr requires Valid() @@ -42,19 +49,8 @@ module Enumerators { ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [element] - - // Would be better as an arbitrary termination clause somehow instead - // https://github.com/dafny-lang/dafny/issues/762 - function Decreases(): nat - reads Repr - decreases Repr, 1 - requires Valid() } - // TODO: Common EagerEnumerator that wraps a Enumerator> - // for cases like Filter or IteratorAdaptor that can't calculate HasNext() - // ahead of time? - class SeqEnumerator extends Enumerator { const elements: seq @@ -431,25 +427,54 @@ module Enumerators { } } - method CollectToSeq(e: Enumerator) returns (s: seq) + method Fold(f: (A, T) -> A, init: A, e: Enumerator) returns (result: A) + requires e.Valid() + requires e.enumerated == [] + modifies e.Repr + ensures e.Valid() + ensures !e.HasNext() + ensures result == Seq.FoldLeft(f, init, e.enumerated) + { + reveal Seq.FoldLeft(); + result := init; + while (e.HasNext()) + invariant e.Valid() && e.Repr <= old(e.Repr) + decreases e.Decreases() + + invariant result == Seq.FoldLeft(f, init, e.enumerated) + { + ghost var enumeratedBefore := e.enumerated; + var element := e.Next(); + result := f(result, element); + + Seq.LemmaFoldLeftDistributesOverConcat(f, init, enumeratedBefore, [element]); + } + } + + method CollectToSeq(e: Enumerator) returns (result: seq) requires e.Valid() // TODO: Might remove this requires e.enumerated == [] modifies e.Repr ensures e.Valid() ensures !e.HasNext() - ensures s == e.enumerated + ensures result == e.enumerated { - s := []; + result := []; while (e.HasNext()) invariant e.Valid() && e.Repr <= old(e.Repr) decreases e.Decreases() - invariant s == e.enumerated + invariant result == e.enumerated { var element := e.Next(); - s := s + [element]; + result := result + [element]; } + + // TODO: Figure out how to use Fold. Good case study for invariant support! + // var f := (s, x) => s + [x]; + // result := Fold(f, [], e); + // Seq.LemmaInvFoldLeft(???, (s, x, s') => s + x == s', f, [], []); } // method Max(s: set) returns (max: int) From 16142fb24773f288a462a208b179716f11f2aad8 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 11:43:52 -0800 Subject: [PATCH 23/68] Tons of documentation --- examples/Enumerators/Datatypes.dfy | 16 +- examples/Enumerators/Enumerators.dfy | 106 +++++++- examples/Enumerators/IteratorAdaptor.dfy | 20 +- examples/Enumerators/RustStyle.dfy | 18 +- src/Actions.dfy | 305 ----------------------- src/Enumerators/Enumerators.dfy | 44 ++-- 6 files changed, 168 insertions(+), 341 deletions(-) delete mode 100644 src/Actions.dfy diff --git a/examples/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy index c1adb7c6..9447c464 100644 --- a/examples/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -1,11 +1,18 @@ include "Enumerators.dfy" +// An example of an enumerator that traverses the sub-values in +// an algebraic datatype value. module DatatypeEnumerator { import opened Enumerators - // Implicit trait approach + // TODO: A TreeEnumerator would be much more interesting! + // Show how to implement multiple traversal options, probably + // using ConcatEnumerator for child paths. + + // TODO: Could define a Enumerable trait as well, although datatypes + // can't yet implement that anyway. datatype List = Cons(value: T, tail: List) | Nil { method Enumerator() returns (e: Enumerator) { e := new ListEnumerator(this); @@ -35,7 +42,7 @@ module DatatypeEnumerator { predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr + decreases Repr, 0 { && this in Repr } @@ -43,8 +50,9 @@ module DatatypeEnumerator { function Decreases(): nat reads Repr requires Valid() + decreases Repr, 1 { - // This is where I wish I could just say "next" and + // TODO: This is where I wish I could just say "next" and // rely on the well-founded ordering. next.Length() } @@ -52,7 +60,7 @@ module DatatypeEnumerator { predicate method HasNext() reads Repr requires Valid() - decreases Repr, 0 + decreases Repr, 2 ensures Decreases() == 0 ==> !HasNext() { next.Cons? diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 9dbc4f9b..b9e0ad0f 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -4,12 +4,79 @@ module Demo { import opened Enumerators - method AddTwoToEach(s: seq) returns (result: seq) - ensures forall x | x in result :: x >= 2 - { - result := Seq.Map(x => x + 2, s); + // Note the common template for using a while loop on + // an arbitrary Enumerator. This will likely be what we + // propose that a "foreach" loop in Dafny will desugar to. + + method Example1() { + var numbers := [1, 2, 3, 4, 5]; + + var e: Enumerator := new SeqEnumerator(numbers); + while (e.HasNext()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + print element, "\n"; + } + } + + method Example2() { + var first := [1, 2, 3, 4, 5]; + var second := [6, 7, 8]; + var e1 := new SeqEnumerator(first); + var e2 := new SeqEnumerator(second); + var e := new ConcatEnumerator(e1, e2); + + while (e.HasNext()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + print element, "\n"; + } } + method PrintWithCommas() { + var first := [1, 2, 3, 4, 5]; + var e := new SeqEnumerator(first); + + while (e.HasNext()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + print element; + if e.HasNext() { + print ", "; + } + } + print "\n"; + } + + method MappingExample() { + var first := [1, 2, 3, 4, 5]; + var e1 := new SeqEnumerator(first); + var e := new MappingEnumerator(x => x + 2, e1); + + var result: seq := []; + while (e.HasNext()) + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var element := e.Next(); + + result := result + [element]; + } + assert e.enumerated == Seq.Map(x => x + 2, first); + } + + // Some examples showing the equivalence between enumerator + // operations and sequence operations. + method RoundTrip(s: seq) returns (result: seq) ensures result == s { @@ -17,11 +84,11 @@ module Demo { result := CollectToSeq(e); } - method AddTwoToEachEnumeratorVersion(s: seq) returns (result: seq) + method AddTwoToEach(s: seq) returns (result: seq) ensures result == Seq.Map(x => x + 2, s) { var e := new SeqEnumerator(s); - var mapped: Enumerator := new MappingEnumerator(x => x + 2, e); + var mapped := new MappingEnumerator(x => x + 2, e); result := CollectToSeq(mapped); } @@ -44,15 +111,30 @@ module Demo { method Concatenate(first: seq, second: seq) returns (result: seq) ensures result == first + second { - var e1: Enumerator := new SeqEnumerator(first); - var e2: Enumerator := new SeqEnumerator(second); + var e1 := new SeqEnumerator(first); + var e2 := new SeqEnumerator(second); var concatenated: ConcatEnumerator := new ConcatEnumerator(e1, e2); result := CollectToSeq(concatenated); assert concatenated.Valid(); } - // TODO: Need to support the case of an invariant that isn't attached to a specific - // concrete type that implements Enumerator. - // Want to state "will enumerate this" without that being baked into Enumerator itself. - + // TODO: The examples above work because Dafny is aware of the concrete + // types of the various enumerator values, and hence knows the additional post-conditions + // of Valid() and !HasNext() necessary to support the more specific assertions. + // That's why we need to explicitly attach a more specific type than Enumerator + // to some variables, when type inference would otherwise choose Enumerator. + // This will be an issue when trying to add more higher-order operations on enumerators, + // or on linking to external implementations that don't have specific Dafny types to + // attach their invariants on. + // + // We'd like to have signatures like this, that ensures the Valid() and HasNext() + // implementations on the result have the desired properties, so we don't need the + // verifier to know the concrete type of the result: + // + // method MakeSeqEnumerator(s: seq) returns (result: Enumerator) + // ensures forall e :: (result's HasNext applied to e) == false ==> e.enumerated == s) + // + // There isn't currently any way to refer to the HasNext function on the result that doesn't + // bind result as the function receiver, though, and my attempts to define ghost vars that + // hold onto such function references haven't worked out well so far. } \ No newline at end of file diff --git a/examples/Enumerators/IteratorAdaptor.dfy b/examples/Enumerators/IteratorAdaptor.dfy index 8abf2881..f7729802 100644 --- a/examples/Enumerators/IteratorAdaptor.dfy +++ b/examples/Enumerators/IteratorAdaptor.dfy @@ -1,15 +1,29 @@ -include "Enumerators.dfy" -include "../Wrappers.dfy" +include "../../src/Enumerators/Enumerators.dfy" +include "../../src/Wrappers.dfy" +// Dafny iterators do not share any common supertype, so there isn't +// currently any way to write a single adaptor from an arbitrary +// iterator to the Enumerator trait. However, this example should +// serve as a template for adapting any specific iterator. +// +// In practice, a loop that uses a specific iterator type will need to +// declare a "decreases" termination metric, and hence the iterator +// in question will need at least one "yield ensures" clause to +// support such a metric. The same is true for adapting a specific iterator +// to the Enumerator trait in order to implement the Decreases() function. module IteratorAdaptorExample { import opened Enumerators import opened Wrappers - + iterator RangeIterator(start: int, end: int) yields (element: int) requires start <= end + // This is necessary in order to prove termination via Decreases() yield ensures element - start + 1 == |elements| + // This is necessary to prove the Repr <= old(Repr) post-condition + // of Next(). Iterators that instantiate and "hand off" objects + // will need a weaker post-condition. yield ensures _new == {}; ensures |elements| == end - start { diff --git a/examples/Enumerators/RustStyle.dfy b/examples/Enumerators/RustStyle.dfy index 0ec7f336..d3940f2a 100644 --- a/examples/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/RustStyle.dfy @@ -3,12 +3,19 @@ include "../../src/Frames.dfy" include "../../src/Wrappers.dfy" include "Enumerators.dfy" -module RustStyle { +// There are lots of ways to define an Iterator/Enumerator +// concept, and there is precious little alignment on the exact +// APIs that various programming languages have chosen. :) +// This is an example of how to adapt another flavour of enumerator +// (Rust's in this case) to Dafny's. +module RustStyleExample { import opened Frames import opened Wrappers import opened Enumerators + // Roughly aligns with Rust's std::iter::Iterator trait. + // https://doc.rust-lang.org/std/iter/trait.Iterator.html trait RustStyleIterator extends Validatable { method Next() returns (res: Option) @@ -24,6 +31,13 @@ module RustStyle { requires Valid() } + // Adapts from a Rust Iterator to an Enumerator. + // Because the latter defines HasNext() as a pure predicate, + // the adaptor needs to pre-fetch the next element eagerly. + // An alternative approach is to extend Enumerator> + // instead, in which case clients of the enumerator will need + // to handle None values as well, likely by break-ing out of the + // loop. class RustStyleIteratorEnumerator extends Enumerator { const iter: RustStyleIterator @@ -152,9 +166,7 @@ module RustStyle { { var element := enum.Next(); - // Loop body starts print element, "\n"; - // Loop body ends } } } \ No newline at end of file diff --git a/src/Actions.dfy b/src/Actions.dfy deleted file mode 100644 index 55ba67c3..00000000 --- a/src/Actions.dfy +++ /dev/null @@ -1,305 +0,0 @@ -include "Wrappers.dfy" -include "Frames.dfy" - -module Actions { - - import opened Wrappers - import opened Frames - - // TODO: Unfortunately, would be good to have Action0 up to ActionN for - // some small N. Maybe also M for number of returns. - // Using () is SUPER awkward with Dafny syntax. An action with - // no arguments or return using unit looks like: - // var _ := runnable.Invoke(()); - trait {:termination false} Action extends Validatable - { - method Invoke(a: A) returns (r: R) - requires Valid() - requires Requires(a) - modifies Modifies(a) - decreases Decreases(a) - ensures ValidAndDisjoint() - ensures old(allocated(r)) && Ensures(a, r) - - predicate Requires(a: A) - // Need this separately from Repr for callers - // Repr is the frame for Valid(), but callers - // need to know what ELSE gets modified. - function Modifies(a: A): set requires Requires(a) - function Decreases(a: A): nat - predicate Ensures(a: A, r: R) - } - - // TODO: ArrayCollector will be more interesting, to track capacity - class SeqCollector extends Action { - - var elements: seq - - predicate Valid() reads this, Repr { - true - } - - method Invoke(t: T) returns (nothing: ()) - requires Valid() - requires Requires(t) - modifies Modifies(t) - decreases Decreases(t) - ensures ValidAndDisjoint() - ensures old(allocated(())) && Ensures(t, ()) - { - elements := elements + [t]; - } - - predicate Requires(t: T) { - true - } - - function Modifies(t: T): set { - {this} - } - - function Decreases(t: T): nat { - 0 - } - - predicate Ensures(t: T, nothing: ()) { - true - } - } - - class FunctionAsAction extends Action { - const f: T ~> R - const e: (T, R) ~> bool - - constructor(f: T ~> R, e: (T, R) ~> bool) { - this.f := f; - this.e := e; - } - - predicate Valid() reads this, Repr { - // TODO: This is what we want but can't because we're not using (!new) - // forall t: T :: e(t, f(t)) - true - } - - method Invoke(t: T) returns (r: R) - requires Valid() - requires Requires(t) - modifies Modifies(t) - decreases Decreases(t) - ensures ValidAndDisjoint() - ensures old(allocated(r)) && Ensures(t, r) - { - r := f(t); - } - - predicate Requires(t: T) { - f.requires(t) - } - - function Modifies(t: T): set { - {} - } - - function Decreases(t: T): nat { - 0 - } - - predicate Ensures(t: T, r: R) { - e(t, r) - } - } - - class ComposedAction extends Action { - const inner: Action - const outer: Action - - constructor(inner: Action, outer: Action) { - this.inner := inner; - this.outer := outer; - } - - predicate Valid() reads this, Repr { - true - } - - method Invoke(t: T) returns (r: R) - requires Valid() - requires Requires(t) - modifies Modifies(t) - decreases Decreases(t) - ensures ValidAndDisjoint() - ensures old(allocated(r)) && Ensures(t, r) - { - var m := inner.Invoke(t); - r := outer.Invoke(m); - } - - predicate Requires(t: T) { - inner.Requires(t) - } - - function Modifies(t: T): set { - set m: M, o: object | inner.Ensures(t, m) && o in outer.Modifies(m) :: o - } - - function Decreases(t: T): nat { - inner.Decreases(t) - } - - predicate Ensures(t: T, r: R) { - exists m: M :: inner.Ensures(t, m) && outer.Ensures(m, r) - } - } - - class InvokeAction extends Action, ()> { - - predicate Valid() reads this, Repr { - true - } - - method Invoke(a: Action) returns (nothing: ()) - requires Valid() - requires Requires(a) - modifies Modifies(a) - decreases Decreases(a) - ensures ValidAndDisjoint() - ensures Ensures(a, ()) - { - var _ := a.Invoke(42); - } - - predicate Requires(a: Action) { - a.Decreases(42) < Decreases(a) - } - - function Modifies(a: Action): set requires Requires(a) { - a.Modifies(42) - } - - function Decreases(a: Action): nat { - 0 - } - - predicate Ensures(a: Action, nothing: ()) { - true - } - } - - type IEnumeration = Action<(), T> - - // predicate Enumerates(e: IEnumeration, elements: seq) { - // e.Ensures((), elements[0]) - // } - - trait IEnumerator { - ghost var pending: seq - ghost var Repr: set - - predicate Valid() reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr - - method Next() returns (t: T) - requires Valid() - requires |pending| > 0 - modifies Repr - ensures Valid() - ensures t == old(pending)[0] - ensures pending == old(pending)[1..] - } - - trait Enumerator { - ghost var pending: seq - ghost var Repr: set - - predicate Valid() reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr - - method Next() returns (t: Option) - requires Valid() - modifies Repr - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures |old(pending)| > 0 ==> - && t.Some? - && t.value == old(pending)[0] - && pending == old(pending)[1..] - ensures |old(pending)| == 0 ==> - && t.None? - && unchanged(this) - } - - trait MyEnumerator extends IEnumerator { - - } - - iterator SeqIterator(s: seq) yields (t: T) { - for i := 0 to |s| { - yield s[i]; - } - } - - class SeqEnumerator extends MyEnumerator { - - var elements: seq - - constructor(s: seq) { - pending := s; - elements := s; - Repr := {this}; - } - - predicate Valid() reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr - { - && this in Repr - && pending == elements - } - - method Next() returns (t: T) - requires Valid() - requires |pending| > 0 - modifies Repr - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures t == old(pending)[0] - ensures pending == old(pending)[1..] - ensures |pending| == |old(pending)| - 1 - { - t := elements[0]; - elements := elements[1..]; - pending := elements; - } - } - - method Count(e: Enumerator) returns (count: int) - requires e.Valid() - modifies e.Repr - decreases * - ensures count == |e.pending| - { - count := 0; - var next := e.Next(); - if next.None? { - return; - } - count := count + 1; - while next.Some? - invariant e.Valid() - invariant fresh(e.Repr - old(e.Repr)) - invariant count + |e.pending| == |old(e.pending)| - decreases |e.pending| - { - label before: next := e.Next(); - - // assert |old@before(e.pending)| + 1 == |e.pending|; - if next.Some? { - count := count + 1; - } - } - assert |e.pending| == 0; - } -} \ No newline at end of file diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 121760e9..6e74954b 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -6,17 +6,20 @@ include "../Collections/Sequences/Seq.dfy" module Enumerators { - // import opened Actions import opened Frames import opened Wrappers import opened Seq + + // A trait for any value that produces a finite set of value. trait {:termination false} Enumerator extends Validatable { // The Valid() predicate from the Validatable trait ends up // becoming the "enumeration invariant", which in turn becomes // the loop invariant in a while loop that uses an enumerator. + // All values produced by the Next() method in the order they + // were produced. ghost var enumerated: seq // Any enumerator that produces one value at a time @@ -26,9 +29,13 @@ module Enumerators { // values have been produced. // Dafny doesn't let you pass around an underspecified value though, // so we don't define a "to be enumerated" field or function. - - // Would be better as an arbitrary termination clause somehow instead. - // https://github.com/dafny-lang/dafny/issues/762 + + // The termination measure for the enumerator. Must decrease on every + // call to Next(). + // + // Would be better as an arbitrary termination clause somehow instead, + // but we don't have language-level access to the built-in well-founded + // ordering. See https://github.com/dafny-lang/dafny/issues/762. function Decreases(): nat reads Repr decreases Repr, 1 @@ -113,6 +120,17 @@ module Enumerators { } } + // This encapsulates the canonical method for enumerating + // the values in a set by using :| and set subtraction. + // Enumerating a set this way will generally take quadratic time, however, + // given each set subtraction will normally require making a new copy of + // the set at runtime. + // + // The good news is that if the Enumerator concept, or a generalized + // type characteristic it satisfies, is added to Dafny itself, then + // the various runtimes can provide a much more efficient implementation + // of this enumerator based on iteration features in the underlying set + // implementation. class SetEnumerator extends Enumerator { ghost const original: set var remaining: set @@ -139,6 +157,14 @@ module Enumerators { && multiset(enumerated) + multiset(remaining) == multiset(original) } + function Decreases(): nat + reads this, Repr + requires Valid() + decreases Repr, 1 + { + |remaining| + } + predicate method HasNext() requires Valid() reads this, Repr @@ -163,14 +189,6 @@ module Enumerators { remaining := remaining - {element}; enumerated := enumerated + [element]; } - - function Decreases(): nat - reads this, Repr - requires Valid() - decreases Repr, 1 - { - |remaining| - } } class MappingEnumerator extends Enumerator { @@ -238,7 +256,6 @@ module Enumerators { class ConcatEnumerator extends Enumerator { - // TODO: Unset once each is Done() to allow garbage collection? const first: Enumerator const second: Enumerator @@ -313,7 +330,6 @@ module Enumerators { } } - // TODO: Prove the semantics! class FilteredEnumerator extends Enumerator { const wrapped: Enumerator const filter: T -> bool From 2b4b2f6a1a49a82abcdd12c3c2d71d7dcc551a60 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 13:28:32 -0800 Subject: [PATCH 24/68] Bit more cleanup --- src/Enumerators/Enumerators.dfy | 136 +++++++------------------------- 1 file changed, 27 insertions(+), 109 deletions(-) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 6e74954b..b659146d 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -41,6 +41,15 @@ module Enumerators { decreases Repr, 1 requires Valid() + // Pre-condition for Next(). Making this a pure predicate means that + // enumerators have to at least know ahead of time if they are able to + // produce a value, even if they do not know what value it is until + // Next() is invoked. This avoids forcing the type parameter for any + // enumerator to satisfy the Auto-initializable type characteristic (0), + // and allows for much cleaner assertions about state and invariants. + // When this is overly restrictive, having an enumerator produce a wrapper + // such as Option instead is a good option (ha!), with the caveat that + // client code will need to skip over Nones. predicate method HasNext() reads Repr requires Valid() @@ -53,6 +62,12 @@ module Enumerators { modifies Repr decreases Repr ensures Valid() + // This is more restrictive than the usual post-condition of + // Validatable.ValidAndDisjoint(), because if we allow the Repr + // of an enumerator to grow, even if fresh, it becomes much more + // difficult to prove termination of a wrapper enumerator like + // FilteredEnumerator below which needs to make a recursive call to + // Next() inside a loop. ensures Repr <= old(Repr) ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [element] @@ -330,6 +345,10 @@ module Enumerators { } } + // Note that to satisfy the Enumerator API, this enumerator has + // to eagerly fetch the next value to return from Next(). + // An alternative is to use a MappingEnumerator that maps to Option + // values, and having consumers skip None values. class FilteredEnumerator extends Enumerator { const wrapped: Enumerator const filter: T -> bool @@ -380,7 +399,7 @@ module Enumerators { requires Valid() decreases Repr, 2 ensures HasNext() ==> Decreases() > 0 - ensures !HasNext() ==> !wrapped.HasNext() // TODO + ensures !HasNext() ==> !wrapped.HasNext() { assert if next.Some? then Decreases() >= 1 else true; next.Some? @@ -422,6 +441,11 @@ module Enumerators { decreases wrapped.Decreases() { var wrappedEnumeratedBefore := wrapped.enumerated; + // This is where it is very difficult to prove termination if we + // allow wrapped.Repr to potentially grow, because the assertion + // we must satisfy for the recursive call to be allowed is actually + // wrapped.Repr < old(Repr). That means updating Repr after this call + // wouldn't help. var t := wrapped.Next(); reveal Seq.Filter(); LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [t]); @@ -469,7 +493,7 @@ module Enumerators { method CollectToSeq(e: Enumerator) returns (result: seq) requires e.Valid() - // TODO: Might remove this + // TODO: Might remove these requires e.enumerated == [] modifies e.Repr ensures e.Valid() @@ -487,115 +511,9 @@ module Enumerators { result := result + [element]; } - // TODO: Figure out how to use Fold. Good case study for invariant support! + // TODO: Figure out how to use Fold instead. Good case study for invariant support! // var f := (s, x) => s + [x]; // result := Fold(f, [], e); // Seq.LemmaInvFoldLeft(???, (s, x, s') => s + x == s', f, [], []); } - - // method Max(s: set) returns (max: int) - // requires |s| > 0 - // ensures max in s - // ensures forall x | x in s :: max >= x - // { - // var first: int :| first in s; - // max := first; - // var rest := s - {max}; - - // var sEnum: SetEnumerator := new SetEnumerator(rest); - // while (sEnum.HasNext()) - // invariant sEnum.Valid() - // invariant fresh(sEnum.Repr) - // invariant max == Seq.Max([first] + sEnum.enumerated) - // decreases sEnum.Decreases() - // { - // var element := sEnum.Next(); - - // if max < element { - // max := element; - // } - // } - // assert max == Seq.Max([first] + sEnum.enumerated); - // assert multiset(sEnum.enumerated) == multiset(rest); - // } - - method Example1() { - var numbers := [1, 2, 3, 4, 5]; - - var e: Enumerator := new SeqEnumerator(numbers); - while (e.HasNext()) - invariant e.Valid() && fresh(e.Repr) - decreases e.Decreases() - { - var element := e.Next(); - - print element, "\n"; - } - } - - method Example2() { - var first := [1, 2, 3, 4, 5]; - var second := [6, 7, 8]; - var e1 := new SeqEnumerator(first); - var e2 := new SeqEnumerator(second); - var e := new ConcatEnumerator(e1, e2); - - while (e.HasNext()) - invariant e.Valid() && fresh(e.Repr) - decreases e.Decreases() - { - var element := e.Next(); - - print element, "\n"; - } - } - - method PrintWithCommas() { - var first := [1, 2, 3, 4, 5]; - var e := new SeqEnumerator(first); - - while (e.HasNext()) - invariant e.Valid() && fresh(e.Repr) - decreases e.Decreases() - { - var element := e.Next(); - - print element; - if e.HasNext() { - print ", "; - } - } - print "\n"; - } - - method MappingExample() { - var first := [1, 2, 3, 4, 5]; - var e1 := new SeqEnumerator(first); - var e := new MappingEnumerator(x => x + 2, e1); - - var result: seq := []; - while (e.HasNext()) - invariant e.Valid() && fresh(e.Repr) - decreases e.Decreases() - { - var element := e.Next(); - - result := result + [element]; - } - assert e.enumerated == Seq.Map(x => x + 2, first); - } - - // TODO: Explicit example of working with lazy iterators, more emphasis on `HasNext` being a pure function - // TODO: Need to give background on Validatable, the fact that Valid() is the loop invariant - // TODO: Mention `enumerated` ghost variable, proving semantics based on that - // TODO: Are ghost enumerators a thing? :) - // TODO: Most enumerator constructors should ensure enumerated == [] and other things - // Important to have end to end examples to ensure correctness invariants are actually usable! - // Also usually need to (at least for v1) require that child enumerators are fresh (enumerated == []) - // TODO: Framing invariant is a pain in the butt, seems to need a label to be generic - // Solution seems to be assuming enumerators are usually local, so fresh(e.Repr) - // TODO: Example of various traversals of datatype trees/graphs - // TODO: Think about Enumerator (and hypothetical Aggregator) as special cases of - // Actions with a relationship between their pre- and post-conditions. - // TODO: Could we have a TerminationMeasure trait, implementable by potentially any type? } \ No newline at end of file From a090841e423b49794a4494526813a1de415da997 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 13:36:54 -0800 Subject: [PATCH 25/68] Add lit commands --- examples/Enumerators/Datatypes.dfy | 2 ++ examples/Enumerators/Datatypes.dfy.expect | 2 ++ examples/Enumerators/Datatypes.dll | Bin 0 -> 108032 bytes .../Enumerators/Datatypes.runtimeconfig.json | 10 ++++++++++ examples/Enumerators/Enumerators.dfy | 3 +++ examples/Enumerators/Enumerators.dfy.expect | 2 ++ examples/Enumerators/Enumerators.dll | Bin 0 -> 104448 bytes .../Enumerators/Enumerators.runtimeconfig.json | 10 ++++++++++ examples/Enumerators/IteratorAdaptor.dfy | 2 ++ .../Enumerators/IteratorAdaptor.dfy.expect | 2 ++ examples/Enumerators/IteratorAdaptor.dll | Bin 0 -> 101888 bytes .../IteratorAdaptor.runtimeconfig.json | 10 ++++++++++ examples/Enumerators/RustStyle.dfy | 2 ++ examples/Enumerators/RustStyle.dfy.expect | 7 +++++++ src/Enumerators/Enumerators.dfy | 3 ++- src/Enumerators/Enumerators.dfy.expect | 2 ++ src/Enumerators/Enumerators.dll | Bin 0 -> 98816 bytes src/Enumerators/Enumerators.runtimeconfig.json | 10 ++++++++++ 18 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 examples/Enumerators/Datatypes.dfy.expect create mode 100644 examples/Enumerators/Datatypes.dll create mode 100644 examples/Enumerators/Datatypes.runtimeconfig.json create mode 100644 examples/Enumerators/Enumerators.dfy.expect create mode 100644 examples/Enumerators/Enumerators.dll create mode 100644 examples/Enumerators/Enumerators.runtimeconfig.json create mode 100644 examples/Enumerators/IteratorAdaptor.dfy.expect create mode 100644 examples/Enumerators/IteratorAdaptor.dll create mode 100644 examples/Enumerators/IteratorAdaptor.runtimeconfig.json create mode 100644 examples/Enumerators/RustStyle.dfy.expect create mode 100644 src/Enumerators/Enumerators.dfy.expect create mode 100644 src/Enumerators/Enumerators.dll create mode 100644 src/Enumerators/Enumerators.runtimeconfig.json diff --git a/examples/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy index 9447c464..86780d88 100644 --- a/examples/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -1,3 +1,5 @@ +// RUN: %dafny /compile:3 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" include "Enumerators.dfy" diff --git a/examples/Enumerators/Datatypes.dfy.expect b/examples/Enumerators/Datatypes.dfy.expect new file mode 100644 index 00000000..e5f9ee5a --- /dev/null +++ b/examples/Enumerators/Datatypes.dfy.expect @@ -0,0 +1,2 @@ + +Dafny program verifier finished with 12 verified, 0 errors diff --git a/examples/Enumerators/Datatypes.dll b/examples/Enumerators/Datatypes.dll new file mode 100644 index 0000000000000000000000000000000000000000..83f76486569e95b80c29c111d7f0b0d6c3e50029 GIT binary patch literal 108032 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

#r%T|m-L9I@AHxctGw0WUpNSzBEvBv14 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 fIbznwIvl?9I;RMiRCz{z&>PUH(DT4}LhjuR#YK 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 literal 0 HcmV?d00001 diff --git a/examples/Enumerators/Datatypes.runtimeconfig.json b/examples/Enumerators/Datatypes.runtimeconfig.json new file mode 100644 index 00000000..c9fdac56 --- /dev/null +++ b/examples/Enumerators/Datatypes.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0", + "rollForward": "LatestMinor" + } + } +} diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index b9e0ad0f..683fc6b8 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -1,3 +1,6 @@ +// RUN: %dafny /compile:3 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" + include "../../src/Enumerators/Enumerators.dfy" module Demo { diff --git a/examples/Enumerators/Enumerators.dfy.expect b/examples/Enumerators/Enumerators.dfy.expect new file mode 100644 index 00000000..ccc01c35 --- /dev/null +++ b/examples/Enumerators/Enumerators.dfy.expect @@ -0,0 +1,2 @@ + +Dafny program verifier finished with 11 verified, 0 errors diff --git a/examples/Enumerators/Enumerators.dll b/examples/Enumerators/Enumerators.dll new file mode 100644 index 0000000000000000000000000000000000000000..ed2fe5e9dff4a915982093762532242d40c08677 GIT binary patch literal 104448 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 F{{c4V8Y%z) literal 0 HcmV?d00001 diff --git a/examples/Enumerators/Enumerators.runtimeconfig.json b/examples/Enumerators/Enumerators.runtimeconfig.json new file mode 100644 index 00000000..c9fdac56 --- /dev/null +++ b/examples/Enumerators/Enumerators.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0", + "rollForward": "LatestMinor" + } + } +} diff --git a/examples/Enumerators/IteratorAdaptor.dfy b/examples/Enumerators/IteratorAdaptor.dfy index f7729802..fe02fa64 100644 --- a/examples/Enumerators/IteratorAdaptor.dfy +++ b/examples/Enumerators/IteratorAdaptor.dfy @@ -1,3 +1,5 @@ +// RUN: %dafny /compile:3 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" include "../../src/Enumerators/Enumerators.dfy" include "../../src/Wrappers.dfy" diff --git a/examples/Enumerators/IteratorAdaptor.dfy.expect b/examples/Enumerators/IteratorAdaptor.dfy.expect new file mode 100644 index 00000000..e5f9ee5a --- /dev/null +++ b/examples/Enumerators/IteratorAdaptor.dfy.expect @@ -0,0 +1,2 @@ + +Dafny program verifier finished with 12 verified, 0 errors diff --git a/examples/Enumerators/IteratorAdaptor.dll b/examples/Enumerators/IteratorAdaptor.dll new file mode 100644 index 0000000000000000000000000000000000000000..efd0e5e35ae004743613d19a5ecd6927379f2a12 GIT binary patch literal 101888 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 dPeSjJ}sO)*wd@LzVjr~Y{Wg*tskJL_? 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 V!JLoy-U@(z9)14q-~WFc_`j28aRdMW literal 0 HcmV?d00001 diff --git a/examples/Enumerators/IteratorAdaptor.runtimeconfig.json b/examples/Enumerators/IteratorAdaptor.runtimeconfig.json new file mode 100644 index 00000000..c9fdac56 --- /dev/null +++ b/examples/Enumerators/IteratorAdaptor.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0", + "rollForward": "LatestMinor" + } + } +} diff --git a/examples/Enumerators/RustStyle.dfy b/examples/Enumerators/RustStyle.dfy index d3940f2a..d2b769b7 100644 --- a/examples/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/RustStyle.dfy @@ -1,3 +1,5 @@ +// RUN: %dafny /compile:3 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" include "../../src/Frames.dfy" include "../../src/Wrappers.dfy" diff --git a/examples/Enumerators/RustStyle.dfy.expect b/examples/Enumerators/RustStyle.dfy.expect new file mode 100644 index 00000000..b15bb607 --- /dev/null +++ b/examples/Enumerators/RustStyle.dfy.expect @@ -0,0 +1,7 @@ + +Dafny program verifier finished with 22 verified, 0 errors +1 +2 +3 +4 +5 diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index b659146d..f91358c3 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -1,5 +1,6 @@ +// RUN: %dafny /compile:0 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" -// include "../Actions.dfy" include "../Frames.dfy" include "../Wrappers.dfy" include "../Collections/Sequences/Seq.dfy" diff --git a/src/Enumerators/Enumerators.dfy.expect b/src/Enumerators/Enumerators.dfy.expect new file mode 100644 index 00000000..0341967b --- /dev/null +++ b/src/Enumerators/Enumerators.dfy.expect @@ -0,0 +1,2 @@ + +Dafny program verifier finished with 63 verified, 0 errors diff --git a/src/Enumerators/Enumerators.dll b/src/Enumerators/Enumerators.dll new file mode 100644 index 0000000000000000000000000000000000000000..02c72119d0760978777ada09d589006f8d268bf2 GIT binary patch literal 98816 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 UAJGnik01S(_WJG4-X;?GKdOWnvH$=8 literal 0 HcmV?d00001 diff --git a/src/Enumerators/Enumerators.runtimeconfig.json b/src/Enumerators/Enumerators.runtimeconfig.json new file mode 100644 index 00000000..c9fdac56 --- /dev/null +++ b/src/Enumerators/Enumerators.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0", + "rollForward": "LatestMinor" + } + } +} From 07132ada18902f68a8ff05c8230c28c492633419 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 14:18:19 -0800 Subject: [PATCH 26/68] Remove execution-created files --- examples/Enumerators/Datatypes.dll | Bin 108032 -> 0 bytes .../Enumerators/Datatypes.runtimeconfig.json | 10 ---------- examples/Enumerators/Enumerators.dll | Bin 104448 -> 0 bytes .../Enumerators/Enumerators.runtimeconfig.json | 10 ---------- examples/Enumerators/IteratorAdaptor.dll | Bin 101888 -> 0 bytes .../IteratorAdaptor.runtimeconfig.json | 10 ---------- src/Enumerators/Enumerators.dll | Bin 98816 -> 0 bytes src/Enumerators/Enumerators.runtimeconfig.json | 10 ---------- 8 files changed, 40 deletions(-) delete mode 100644 examples/Enumerators/Datatypes.dll delete mode 100644 examples/Enumerators/Datatypes.runtimeconfig.json delete mode 100644 examples/Enumerators/Enumerators.dll delete mode 100644 examples/Enumerators/Enumerators.runtimeconfig.json delete mode 100644 examples/Enumerators/IteratorAdaptor.dll delete mode 100644 examples/Enumerators/IteratorAdaptor.runtimeconfig.json delete mode 100644 src/Enumerators/Enumerators.dll delete mode 100644 src/Enumerators/Enumerators.runtimeconfig.json diff --git a/examples/Enumerators/Datatypes.dll b/examples/Enumerators/Datatypes.dll deleted file mode 100644 index 83f76486569e95b80c29c111d7f0b0d6c3e50029..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108032 zcmd443t&{m^*=uMv3GZ~y9r4)2}uwFBw=9_9zqor6%`c(Dkvx_T2NF#>LMHP5rP5n zjrc-*QANc!K5A37ii$v~t+Z;T)mB7oEwwGSw#C*~jQ`K)%7r(E*QtEkJ`8QJ1uTR>6F7EY0vFeEawDzAZtlFy`SYfKyY8In7hafYsXP1Jx_R?2tebUV-KY~!uKRXo#<>F%iK2SLdcv_v zjk9d^@gtRI=cMgWed=P?V5RQ2l(Kc{f>)8RLw*OYN_CSwaMYWBxvNw#bsRPFN|N$_ z|7t)M;jbQiPt@6cr;r!lH|Fb0>y(NFC?bGI2jGD%=U&nRc;S7fU4gdd$WUe4zsD>r*~3@5q3VDD(7mS*NONM+oJ7<# zCmI)+dWLlaOGD*u_DJd;E)7RY-ONbjRcW}~OZQWyUgjvGqNUODNNHq0Z3h*GLs5BA zX;JeQ?N_dP673KFPq%-szBv$vKZddceG|@%VhNhqRHo*^KMFbu6$I^-dd(ZUb|aK8 z9~@G@fbNJT|9AZ=d#urW(Kw8ThUTiqMPrfa^uDx|mpu;1hIV4Ynd6aH@VykqTv~dy zJd{qWXl4vKpxes5(omIgjZ#niU&vA?kRSv~a4upGHvd@nkF79tRg`U{_`KKKIj;|= zFhJ9ZXpvX$rXd_Y?Wn1!Q?&m&ev8Hzwy4u@5ix#?q9TP|1vwt%c#xxYi-{bNL$_}t zjYsSZ<_(U>a0;E;RW}Sj#%#1l*O_4IOw@IqVY;@+gXXbtQ%Iq9+Ob8O%yiakWwm6dhv=Km zo{fA4aVVXgft(1UeQbrerYgwK5jzxlO@lR)Wr+2)p74`w>ReQ8D%Wg1VJ<~!tI|w~ z!Vj~BswBU<5ZKNx@@miars!1jO(CMBXXlHkWC{aNie_m})C=CDs>o*^dT?%$HxC7K zQ|Eyi6QpRBf{oIqM2EK=R(PUX9t&^D)fEYFI&EY6maVWpQ2VoKBzu0|V5CLgtM1xf z^bKwQz&ZlBz1v^K_UgvbN9eD*ZO{~L6P-@V4Fo$y4{w`^WI!XmKys`{+1h7YS(cfF zM0>67%O>FAU!`!s&I)#)t?6cy&NjdW0EDF$yT}(ihnR08u@_y)YzE1Ilc)i6b}En8 zzG$o7zCKbdH5YW5i;&3do0^B5ub-n}urB&Z4ZD@KGg(qYO9HZhNwpv+3TahkJR`QL z8n)4HrB=e;H{GK7tg--!Hl5_8kEv^|uE)a6#YjZUqB51b1VB}%mGk4;!JfJS^gV*M zgg~iyDQlxAFo^0R8FWWcLPcX58O}hxG(%#Oqqg@}kqn%@sQKOPu}D)m!tqz6w#|DG zot|w){xU2L*kbanx`7?mQ0cAVHMI~$sWv3i7z9y7%@FNwL86V8G#xLYO(9&I0x}dP z+ZG{%%jLKXI9efaNP(jZP`A*BQthD2EJot@k*x-S?tomov7D*3HQf@>#Un90bp*5%z|{q)OCG+j zrLG2@=-r&KQ`h8xv{mJ|!;&~zmum-2YWwvNFUypHb-RjGx*JM_8A~wXtK5vQ7i)P zZDda)#o)ZDmB^$pVW+*!?a0O>O~uOgQgH$Ph&DetU@!Z! zwFUO=qkV_=$lG@~bn2Vy-_ht_%-_|@&D;ed@zWCO6wchuN+@52E5yB@>s79A$AU*a zdFZuL3PUTM`i@9xPj~bc5(DF*9?ou;fZc z+mrZ4jYmi<%B(KHJL2oQzMZ)bI2}z%n!2C#h5R(V=C^P92MYMR3Vp8nQGTVgtSC@x zUWtXN^7~k*&byqs=a3Vk*rGMyBHsESGM(N-NV5;Kteuz?8kM#)k02YbOrR^m(MUA) zD8S5PNW^q1S~VpRogH32OSkB8AQ&v0f6j%W4so1g2SdM<^vhaMh|DLD>GYmNnth68 z?Zl+8(1OgTku`o{tFutIKkJI=i|BrHfESxBWB)ml%F+dKphVMoGS|e@4a)PU9(Z@Y zdlF8$8|{YGayKjecmy3uJ>l?Nwj!?i-4rXcWICzrw6KKOJ)XYkYt%?YF=j-| z)H6an3QFfCJ1Hy|WlqU_4_LXMtAQe!4nV%LrfOawR+7-sQJ6z{Urv{^18VAud2i}! z8K_+~#l9az#ukA}Kg!>4`q)mLIKmodeRO((+!*^Y1H%xaokhQ7Hkan+HaZx$bLGD(

#r%T|m-L9I@AHxctGw0WUpNSzBEvBv14 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 fIbznwIvl?9I;RMiRCz{z&>PUH(DT4}LhjuR#YK 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 diff --git a/examples/Enumerators/Datatypes.runtimeconfig.json b/examples/Enumerators/Datatypes.runtimeconfig.json deleted file mode 100644 index c9fdac56..00000000 --- a/examples/Enumerators/Datatypes.runtimeconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net6.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "6.0.0", - "rollForward": "LatestMinor" - } - } -} diff --git a/examples/Enumerators/Enumerators.dll b/examples/Enumerators/Enumerators.dll deleted file mode 100644 index ed2fe5e9dff4a915982093762532242d40c08677..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104448 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 F{{c4V8Y%z) diff --git a/examples/Enumerators/Enumerators.runtimeconfig.json b/examples/Enumerators/Enumerators.runtimeconfig.json deleted file mode 100644 index c9fdac56..00000000 --- a/examples/Enumerators/Enumerators.runtimeconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net6.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "6.0.0", - "rollForward": "LatestMinor" - } - } -} diff --git a/examples/Enumerators/IteratorAdaptor.dll b/examples/Enumerators/IteratorAdaptor.dll deleted file mode 100644 index efd0e5e35ae004743613d19a5ecd6927379f2a12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101888 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 dPeSjJ}sO)*wd@LzVjr~Y{Wg*tskJL_? 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 V!JLoy-U@(z9)14q-~WFc_`j28aRdMW diff --git a/examples/Enumerators/IteratorAdaptor.runtimeconfig.json b/examples/Enumerators/IteratorAdaptor.runtimeconfig.json deleted file mode 100644 index c9fdac56..00000000 --- a/examples/Enumerators/IteratorAdaptor.runtimeconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net6.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "6.0.0", - "rollForward": "LatestMinor" - } - } -} diff --git a/src/Enumerators/Enumerators.dll b/src/Enumerators/Enumerators.dll deleted file mode 100644 index 02c72119d0760978777ada09d589006f8d268bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98816 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 UAJGnik01S(_WJG4-X;?GKdOWnvH$=8 diff --git a/src/Enumerators/Enumerators.runtimeconfig.json b/src/Enumerators/Enumerators.runtimeconfig.json deleted file mode 100644 index c9fdac56..00000000 --- a/src/Enumerators/Enumerators.runtimeconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net6.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "6.0.0", - "rollForward": "LatestMinor" - } - } -} From ee50ca5c4b7b7c9e397103b1d84ded494190d6df Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 23 Feb 2022 19:16:34 -0800 Subject: [PATCH 27/68] Pr feedback, fix build --- examples/Enumerators/Datatypes.dfy | 34 ++++++++++++++++++------ examples/Enumerators/Enumerators.dfy | 8 +++--- examples/Enumerators/IteratorAdaptor.dfy | 7 ++--- examples/Enumerators/RustStyle.dfy | 2 +- src/Enumerators/Enumerators.dfy | 9 +++---- src/Frames.dfy | 3 +++ src/Frames.dfy.expect | 2 ++ 7 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 src/Frames.dfy.expect diff --git a/examples/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy index 86780d88..71b17a8d 100644 --- a/examples/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -16,15 +16,12 @@ module DatatypeEnumerator { // TODO: Could define a Enumerable trait as well, although datatypes // can't yet implement that anyway. datatype List = Cons(value: T, tail: List) | Nil { - method Enumerator() returns (e: Enumerator) { + method Enumerator() returns (e: Enumerator) + ensures e.Valid() + ensures fresh(e.Repr) + { e := new ListEnumerator(this); } - - function Length(): nat { - match this - case Cons(_, tail) => 1 + tail.Length() - case Nil => 0 - } } class ListEnumerator extends Enumerator { @@ -56,7 +53,14 @@ module DatatypeEnumerator { { // TODO: This is where I wish I could just say "next" and // rely on the well-founded ordering. - next.Length() + Length(next) + } + + + static function Length(l: List): nat { + match l + case Cons(_, tail) => 1 + Length(tail) + case Nil => 0 } predicate method HasNext() @@ -84,4 +88,18 @@ module DatatypeEnumerator { enumerated := enumerated + [element]; } } + + method Main() { + var list := Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil))))); + + var e := list.Enumerator(); + while e.HasNext() + invariant e.Valid() && fresh(e.Repr) + decreases e.Decreases() + { + var x := e.Next(); + + print x; + } + } } \ No newline at end of file diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 683fc6b8..9bcdef04 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -15,7 +15,7 @@ module Demo { var numbers := [1, 2, 3, 4, 5]; var e: Enumerator := new SeqEnumerator(numbers); - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { @@ -32,7 +32,7 @@ module Demo { var e2 := new SeqEnumerator(second); var e := new ConcatEnumerator(e1, e2); - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { @@ -46,7 +46,7 @@ module Demo { var first := [1, 2, 3, 4, 5]; var e := new SeqEnumerator(first); - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { @@ -66,7 +66,7 @@ module Demo { var e := new MappingEnumerator(x => x + 2, e1); var result: seq := []; - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { diff --git a/examples/Enumerators/IteratorAdaptor.dfy b/examples/Enumerators/IteratorAdaptor.dfy index fe02fa64..6abdef16 100644 --- a/examples/Enumerators/IteratorAdaptor.dfy +++ b/examples/Enumerators/IteratorAdaptor.dfy @@ -26,7 +26,7 @@ module IteratorAdaptorExample { // This is necessary to prove the Repr <= old(Repr) post-condition // of Next(). Iterators that instantiate and "hand off" objects // will need a weaker post-condition. - yield ensures _new == {}; + yield ensures _new == {} ensures |elements| == end - start { for i := start to end @@ -50,9 +50,6 @@ module IteratorAdaptorExample { iter := new RangeIterator(start, end); remaining := end - start; enumerated := []; - - new; - Repr := {this, iter}; } @@ -63,7 +60,7 @@ module IteratorAdaptorExample { { && this in Repr && iter in Repr - && iter._modifies + iter._reads + iter._new == {} + && iter._modifies == iter._reads == iter._new == {} && iter.Valid() && remaining == (iter.end - iter.start) - |iter.elements| } diff --git a/examples/Enumerators/RustStyle.dfy b/examples/Enumerators/RustStyle.dfy index d2b769b7..01f10134 100644 --- a/examples/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/RustStyle.dfy @@ -162,7 +162,7 @@ module RustStyleExample { var iter := new SeqRustStyleIterator([1,2,3,4,5]); var enum: Enumerator := new RustStyleIteratorEnumerator(iter); - while (enum.HasNext()) + while enum.HasNext() invariant enum.Valid() && fresh(enum.Repr) decreases enum.Decreases() { diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index f91358c3..2657a7b2 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -200,8 +200,7 @@ module Enumerators { ensures Decreases() < old(Decreases()) ensures enumerated == old(enumerated) + [element] { - var picked: T :| picked in remaining; - element := picked; + element :| element in remaining; remaining := remaining - {element}; enumerated := enumerated + [element]; } @@ -433,7 +432,7 @@ module Enumerators { ensures unchanged(this`enumerated) ensures unchanged(this`Repr) { - while (wrapped.HasNext() && next.None?) + while wrapped.HasNext() && next.None? invariant AlmostValid() invariant wrapped.Repr < old(Repr) invariant Repr == old(Repr) @@ -478,7 +477,7 @@ module Enumerators { { reveal Seq.FoldLeft(); result := init; - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && e.Repr <= old(e.Repr) decreases e.Decreases() @@ -502,7 +501,7 @@ module Enumerators { ensures result == e.enumerated { result := []; - while (e.HasNext()) + while e.HasNext() invariant e.Valid() && e.Repr <= old(e.Repr) decreases e.Decreases() diff --git a/src/Frames.dfy b/src/Frames.dfy index 695f6ac8..778c5a80 100644 --- a/src/Frames.dfy +++ b/src/Frames.dfy @@ -1,3 +1,6 @@ +// RUN: %dafny /compile:0 "%s" > "%t" +// RUN: %diff "%s.expect" "%t" + module Frames { // A trait for objects with a Valid() predicate. Necessary in order to diff --git a/src/Frames.dfy.expect b/src/Frames.dfy.expect new file mode 100644 index 00000000..00a51f82 --- /dev/null +++ b/src/Frames.dfy.expect @@ -0,0 +1,2 @@ + +Dafny program verifier finished with 3 verified, 0 errors From ef77a1c9b1346cd47b70a004581e30ebf3b2ce35 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 09:54:13 -0800 Subject: [PATCH 28/68] Fix another lit test failure --- examples/Enumerators/Datatypes.dfy.expect | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/Enumerators/Datatypes.dfy.expect b/examples/Enumerators/Datatypes.dfy.expect index e5f9ee5a..5e227f01 100644 --- a/examples/Enumerators/Datatypes.dfy.expect +++ b/examples/Enumerators/Datatypes.dfy.expect @@ -1,2 +1,3 @@ -Dafny program verifier finished with 12 verified, 0 errors +Dafny program verifier finished with 14 verified, 0 errors +12345 From f172ef58e756c10bbb634091e390ba59fb756971 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 10:04:41 -0800 Subject: [PATCH 29/68] Trim redundant post-conditions --- src/Enumerators/Enumerators.dfy | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 2657a7b2..dcbd9b8a 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -12,7 +12,7 @@ module Enumerators { import opened Seq - // A trait for any value that produces a finite set of value. + // A trait for any value that produces a finite sequence of values. trait {:termination false} Enumerator extends Validatable { // The Valid() predicate from the Validatable trait ends up @@ -55,7 +55,6 @@ module Enumerators { reads Repr requires Valid() decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 method Next() returns (element: T) requires Valid() @@ -239,9 +238,7 @@ module Enumerators { reads Repr requires Valid() decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 { - assert wrapped.HasNext() ==> Decreases() > 0; wrapped.HasNext() } @@ -307,10 +304,7 @@ module Enumerators { requires Valid() reads this, Repr decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 { - assert first.HasNext() ==> Decreases() > 0; - assert second.HasNext() ==> Decreases() > 0; first.HasNext() || second.HasNext() } From 08876c02a0c64cbbd61f2221fc8151a970210ab5 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 10:09:21 -0800 Subject: [PATCH 30/68] ACTUALLY fix lit test failure --- examples/Enumerators/Datatypes.dfy.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Enumerators/Datatypes.dfy.expect b/examples/Enumerators/Datatypes.dfy.expect index 5e227f01..11b67a47 100644 --- a/examples/Enumerators/Datatypes.dfy.expect +++ b/examples/Enumerators/Datatypes.dfy.expect @@ -1,3 +1,3 @@ Dafny program verifier finished with 14 verified, 0 errors -12345 +12345 \ No newline at end of file From 89984b412756aa4a4461a8be6eec03bc8a5f8d38 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 12:12:13 -0800 Subject: [PATCH 31/68] Add trait specializations Addresses my concern about needing to know the concrete types of Enumerators to prove behaviors --- examples/Enumerators/Datatypes.dfy | 116 +++++++++------------- examples/Enumerators/Datatypes.dfy.expect | 2 +- examples/Enumerators/Enumerators.dfy | 21 +--- examples/Enumerators/RustStyle.dfy | 1 - src/Enumerators/Enumerators.dfy | 63 +++++++++++- src/Enumerators/Enumerators.dfy.expect | 2 +- 6 files changed, 107 insertions(+), 98 deletions(-) diff --git a/examples/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy index 71b17a8d..7d8f8306 100644 --- a/examples/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -9,90 +9,57 @@ module DatatypeEnumerator { import opened Enumerators - // TODO: A TreeEnumerator would be much more interesting! - // Show how to implement multiple traversal options, probably - // using ConcatEnumerator for child paths. + datatype Tree = Node(left: Tree, value: T, right: Tree) | Nil - // TODO: Could define a Enumerable trait as well, although datatypes - // can't yet implement that anyway. - datatype List = Cons(value: T, tail: List) | Nil { + datatype TreeTraversal = InorderTraversal(Tree) /* | PreorderTraversal(Tree) | PostorderTraversal(Tree) */ { method Enumerator() returns (e: Enumerator) ensures e.Valid() ensures fresh(e.Repr) + ensures e.enumerated == [] { - e := new ListEnumerator(this); + match this + case InorderTraversal(tree) => { + e := InorderEnumerator(tree); + } } } - class ListEnumerator extends Enumerator { - - var next: List - - constructor(next: List) - ensures Valid() - ensures fresh(Repr - {this}) - { - this.next := next; - - enumerated := []; - Repr := {this}; - } - - predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - decreases Repr, 0 - { - && this in Repr - } - - function Decreases(): nat - reads Repr - requires Valid() - decreases Repr, 1 - { - // TODO: This is where I wish I could just say "next" and - // rely on the well-founded ordering. - Length(next) - } - - - static function Length(l: List): nat { - match l - case Cons(_, tail) => 1 + Length(tail) - case Nil => 0 + method InorderEnumerator(tree: Tree) returns (e: Enumerator) + ensures e.Valid() + ensures fresh(e.Repr) + ensures e.enumerated == [] + { + match tree + case Nil => { + e := new SeqEnumerator([]); } - - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 2 - ensures Decreases() == 0 ==> !HasNext() - { - next.Cons? - } - - method Next() returns (element: T) - requires Valid() - requires HasNext() - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] - { - element := next.value; - next := next.tail; - - enumerated := enumerated + [element]; + case Node(left, value, right) => { + // TODO: How to make this lazy? + var thisValueEnum := new SeqEnumerator([value]); + var leftEnum := InorderEnumerator(left); + var rightEnum := InorderEnumerator(right); + var tmp := new ConcatEnumerator(leftEnum, thisValueEnum); + e := new ConcatEnumerator(tmp, rightEnum); } } method Main() { - var list := Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil))))); - - var e := list.Enumerator(); + var tree := Node( + Node( + Nil, + 1, + Node(Nil, 2, Nil) + ), + 3, + Node( + Node(Nil, 4, Nil), + 5, + Nil + ) + ); + + var traversal := InorderTraversal(tree); + var e := traversal.Enumerator(); while e.HasNext() invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() @@ -101,5 +68,12 @@ module DatatypeEnumerator { print x; } + + // With foreach loop support, the above could just be: + // + // foreach x in InorderTraversal(tree) { + // print x; + // } + } } \ No newline at end of file diff --git a/examples/Enumerators/Datatypes.dfy.expect b/examples/Enumerators/Datatypes.dfy.expect index 11b67a47..eef2943b 100644 --- a/examples/Enumerators/Datatypes.dfy.expect +++ b/examples/Enumerators/Datatypes.dfy.expect @@ -1,3 +1,3 @@ -Dafny program verifier finished with 14 verified, 0 errors +Dafny program verifier finished with 5 verified, 0 errors 12345 \ No newline at end of file diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index 9bcdef04..bfc61160 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -121,23 +121,6 @@ module Demo { assert concatenated.Valid(); } - // TODO: The examples above work because Dafny is aware of the concrete - // types of the various enumerator values, and hence knows the additional post-conditions - // of Valid() and !HasNext() necessary to support the more specific assertions. - // That's why we need to explicitly attach a more specific type than Enumerator - // to some variables, when type inference would otherwise choose Enumerator. - // This will be an issue when trying to add more higher-order operations on enumerators, - // or on linking to external implementations that don't have specific Dafny types to - // attach their invariants on. - // - // We'd like to have signatures like this, that ensures the Valid() and HasNext() - // implementations on the result have the desired properties, so we don't need the - // verifier to know the concrete type of the result: - // - // method MakeSeqEnumerator(s: seq) returns (result: Enumerator) - // ensures forall e :: (result's HasNext applied to e) == false ==> e.enumerated == s) - // - // There isn't currently any way to refer to the HasNext function on the result that doesn't - // bind result as the function receiver, though, and my attempts to define ghost vars that - // hold onto such function references haven't worked out well so far. + // TODO: Add examples that use external implementations of specialized traits + // like EnumeratorOfSeq. } \ No newline at end of file diff --git a/examples/Enumerators/RustStyle.dfy b/examples/Enumerators/RustStyle.dfy index 01f10134..a511abc8 100644 --- a/examples/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/RustStyle.dfy @@ -26,7 +26,6 @@ module RustStyleExample { ensures Valid() ensures Repr <= old(Repr) ensures res.Some? ==> Decreases() < old(Decreases()) - ensures old(Decreases()) == 0 ==> res.None? function Decreases(): nat reads Repr diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index dcbd9b8a..cc27a162 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -11,7 +11,6 @@ module Enumerators { import opened Wrappers import opened Seq - // A trait for any value that produces a finite sequence of values. trait {:termination false} Enumerator extends Validatable { @@ -73,7 +72,50 @@ module Enumerators { ensures enumerated == old(enumerated) + [element] } - class SeqEnumerator extends Enumerator { + /********************************************************** + * + * Specializations + * + * Having these traits separate from the concrete implementations + * allows us to generalize over different cases with common + * tighter specifications of what they enumerate, such as + * knowing the exact seq of values that will be enumerated. + * These can also be attached to external implementations to express + * assumptions about their behavior. + * + ***********************************************************/ + + // TODO: Need a naming convention that clearly distinguishes + // "Enumerator where you can tell me the ghost seq value that will be enumerated" + // vs. + // "Enumerator that enumerates a runtime seq value" + trait EnumeratorOfSeq extends Enumerator { + ghost var toEnumerate: seq + + predicate method HasNext() + reads Repr + requires Valid() + decreases Repr, 2 + ensures !HasNext() ==> enumerated == toEnumerate + } + + trait EnumeratorOfSet extends Enumerator { + ghost var toEnumerate: set + + predicate method HasNext() + reads Repr + requires Valid() + decreases Repr, 2 + ensures !HasNext() ==> multiset(enumerated) == multiset(toEnumerate) + } + + /********************************************************** + * + * Concrete implementations + * + ***********************************************************/ + + class SeqEnumerator extends EnumeratorOfSeq { const elements: seq var index: nat @@ -87,6 +129,7 @@ module Enumerators { elements := s; index := 0; + toEnumerate := s; enumerated := []; Repr := {this}; } @@ -97,6 +140,7 @@ module Enumerators { decreases Repr, 0 { && this in Repr + && toEnumerate == elements && 0 <= index <= |elements| && enumerated == elements[0..index] } @@ -144,9 +188,9 @@ module Enumerators { // The good news is that if the Enumerator concept, or a generalized // type characteristic it satisfies, is added to Dafny itself, then // the various runtimes can provide a much more efficient implementation - // of this enumerator based on iteration features in the underlying set + // of EnumeratorOfSet based on iteration features in the underlying set // implementation. - class SetEnumerator extends Enumerator { + class SetEnumerator extends EnumeratorOfSet { ghost const original: set var remaining: set @@ -159,6 +203,7 @@ module Enumerators { this.original := s; this.remaining := s; + toEnumerate := s; enumerated := []; Repr := {this}; } @@ -169,6 +214,7 @@ module Enumerators { decreases Repr, 0 { && this in Repr + && toEnumerate == original && multiset(enumerated) + multiset(remaining) == multiset(original) } @@ -184,7 +230,7 @@ module Enumerators { requires Valid() reads this, Repr decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 + ensures !HasNext() ==> multiset(enumerated) == multiset(toEnumerate) { |remaining| > 0 } @@ -205,6 +251,12 @@ module Enumerators { } } + /********************************************************** + * + * Higher-order operations + * + ***********************************************************/ + class MappingEnumerator extends Enumerator { const wrapped: Enumerator const f: T -> R @@ -266,6 +318,7 @@ module Enumerators { } } + // TODO: Generalize to a FlattenEnumerator that wraps an Enumerator> instead? class ConcatEnumerator extends Enumerator { const first: Enumerator diff --git a/src/Enumerators/Enumerators.dfy.expect b/src/Enumerators/Enumerators.dfy.expect index 0341967b..8760f62d 100644 --- a/src/Enumerators/Enumerators.dfy.expect +++ b/src/Enumerators/Enumerators.dfy.expect @@ -1,2 +1,2 @@ -Dafny program verifier finished with 63 verified, 0 errors +Dafny program verifier finished with 78 verified, 0 errors From 64f257f3abdf2e2008e014fd1f9684d3bffa11b8 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 12:25:03 -0800 Subject: [PATCH 32/68] Sigh --- src/Enumerators/Enumerators.dfy.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Enumerators/Enumerators.dfy.expect b/src/Enumerators/Enumerators.dfy.expect index 8760f62d..2f021147 100644 --- a/src/Enumerators/Enumerators.dfy.expect +++ b/src/Enumerators/Enumerators.dfy.expect @@ -1,2 +1,2 @@ -Dafny program verifier finished with 78 verified, 0 errors +Dafny program verifier finished with 67 verified, 0 errors From a5c7863e69b82821602a907bc53d5849f1a6f353 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 24 Feb 2022 16:50:33 -0800 Subject: [PATCH 33/68] Switching to Option flavour A few verification failures left but looks like it will work out! --- examples/Enumerators/Datatypes.dfy | 5 +- examples/Enumerators/Enumerators.dfy | 28 +- examples/Enumerators/IteratorAdaptor.dfy | 46 +- .../{RustStyle.dfy => JavaStyle.dfy} | 113 ++-- ...tStyle.dfy.expect => JavaStyle.dfy.expect} | 0 src/Enumerators/Enumerators.dfy | 505 ++++++++++++------ 6 files changed, 431 insertions(+), 266 deletions(-) rename examples/Enumerators/{RustStyle.dfy => JavaStyle.dfy} (50%) rename examples/Enumerators/{RustStyle.dfy.expect => JavaStyle.dfy.expect} (100%) diff --git a/examples/Enumerators/Datatypes.dfy b/examples/Enumerators/Datatypes.dfy index 7d8f8306..58636d4f 100644 --- a/examples/Enumerators/Datatypes.dfy +++ b/examples/Enumerators/Datatypes.dfy @@ -60,13 +60,14 @@ module DatatypeEnumerator { var traversal := InorderTraversal(tree); var e := traversal.Enumerator(); - while e.HasNext() + while true invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var x := e.Next(); + if x.None? { break; } - print x; + print x.value; } // With foreach loop support, the above could just be: diff --git a/examples/Enumerators/Enumerators.dfy b/examples/Enumerators/Enumerators.dfy index bfc61160..f6da6c48 100644 --- a/examples/Enumerators/Enumerators.dfy +++ b/examples/Enumerators/Enumerators.dfy @@ -15,13 +15,14 @@ module Demo { var numbers := [1, 2, 3, 4, 5]; var e: Enumerator := new SeqEnumerator(numbers); - while e.HasNext() + while true invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var element := e.Next(); + if element.None? { break; } - print element, "\n"; + print element.value, "\n"; } } @@ -30,32 +31,34 @@ module Demo { var second := [6, 7, 8]; var e1 := new SeqEnumerator(first); var e2 := new SeqEnumerator(second); - var e := new ConcatEnumerator(e1, e2); + var e: Enumerator := new ConcatEnumerator(e1, e2); - while e.HasNext() + while true invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var element := e.Next(); + if element.None? { break; } - print element, "\n"; + print element.value, "\n"; } } method PrintWithCommas() { var first := [1, 2, 3, 4, 5]; - var e := new SeqEnumerator(first); + var e: Enumerator := new SeqEnumerator(first); - while e.HasNext() + for index := 0 to * invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var element := e.Next(); + if element.None? { break; } - print element; - if e.HasNext() { + if index > 0 { print ", "; } + print element; } print "\n"; } @@ -63,16 +66,17 @@ module Demo { method MappingExample() { var first := [1, 2, 3, 4, 5]; var e1 := new SeqEnumerator(first); - var e := new MappingEnumerator(x => x + 2, e1); + var e: Enumerator := new MappingEnumerator(x => x + 2, e1); var result: seq := []; - while e.HasNext() + while true invariant e.Valid() && fresh(e.Repr) decreases e.Decreases() { var element := e.Next(); + if element.None? { break; } - result := result + [element]; + result := result + [element.value]; } assert e.enumerated == Seq.Map(x => x + 2, first); } diff --git a/examples/Enumerators/IteratorAdaptor.dfy b/examples/Enumerators/IteratorAdaptor.dfy index 6abdef16..5a7aa634 100644 --- a/examples/Enumerators/IteratorAdaptor.dfy +++ b/examples/Enumerators/IteratorAdaptor.dfy @@ -40,6 +40,7 @@ module IteratorAdaptorExample { class RangeEnumerator extends Enumerator { const iter: RangeIterator + var iterDone: bool var remaining: nat constructor(start: int, end: int) @@ -61,33 +62,42 @@ module IteratorAdaptorExample { && this in Repr && iter in Repr && iter._modifies == iter._reads == iter._new == {} - && iter.Valid() + && (!iterDone ==> iter.Valid()) && remaining == (iter.end - iter.start) - |iter.elements| } - predicate method HasNext() + method Next() returns (element: Option) requires Valid() - reads this, Repr - decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 - { - remaining > 0 - } - - method Next() returns (element: int) - requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - var more := iter.MoveNext(); - element := iter.element; - enumerated := enumerated + [element]; - remaining := remaining - 1; + if iterDone { + element := None; + } else { + assert remaining == (iter.end - iter.start) - |iter.elements|; + var more := iter.MoveNext(); + if more { + assert |iter.elements| == old(|iter.elements|) + 1; + assert remaining > 0; + element := Some(iter.element); + remaining := remaining - 1; + } else { + iterDone := true; + element := None; + } + } + Enumerated(element); } function Decreases(): nat diff --git a/examples/Enumerators/RustStyle.dfy b/examples/Enumerators/JavaStyle.dfy similarity index 50% rename from examples/Enumerators/RustStyle.dfy rename to examples/Enumerators/JavaStyle.dfy index a511abc8..cc2022cd 100644 --- a/examples/Enumerators/RustStyle.dfy +++ b/examples/Enumerators/JavaStyle.dfy @@ -9,44 +9,39 @@ include "Enumerators.dfy" // concept, and there is precious little alignment on the exact // APIs that various programming languages have chosen. :) // This is an example of how to adapt another flavour of enumerator -// (Rust's in this case) to Dafny's. -module RustStyleExample { +// (Java's in this case) to Dafny's. +module JavaStyleExample { import opened Frames import opened Wrappers import opened Enumerators - // Roughly aligns with Rust's std::iter::Iterator trait. - // https://doc.rust-lang.org/std/iter/trait.Iterator.html - trait RustStyleIterator extends Validatable { + // Roughly aligns with Java's Iterator interface. + trait JavaStyleIterator extends Validatable { - method Next() returns (res: Option) + method HasNext() returns (result: bool) requires Valid() + ensures !result <==> Decreases() == 0 + + method Next() returns (res: T) + requires Valid() + requires Decreases() > 0 modifies Repr ensures Valid() ensures Repr <= old(Repr) - ensures res.Some? ==> Decreases() < old(Decreases()) + ensures Decreases() < old(Decreases()) function Decreases(): nat reads Repr requires Valid() } - // Adapts from a Rust Iterator to an Enumerator. - // Because the latter defines HasNext() as a pure predicate, - // the adaptor needs to pre-fetch the next element eagerly. - // An alternative approach is to extend Enumerator> - // instead, in which case clients of the enumerator will need - // to handle None values as well, likely by break-ing out of the - // loop. - class RustStyleIteratorEnumerator extends Enumerator { + // Adapts from a Java Iterator to an Enumerator. + class JavaStyleIteratorEnumerator extends Enumerator { - const iter: RustStyleIterator - var next: Option + const iter: JavaStyleIterator - ghost var decr: nat - - constructor(iter: RustStyleIterator) + constructor(iter: JavaStyleIterator) requires iter.Valid() modifies iter.Repr ensures Valid() @@ -54,14 +49,7 @@ module RustStyleExample { ensures Repr == {this} + iter.Repr { this.iter := iter; - new; - this.next := iter.Next(); Repr := {this} + iter.Repr; - if this.next.None? { - decr := 0; - } else { - decr := iter.Decreases() + 1; - } } predicate Valid() @@ -71,28 +59,32 @@ module RustStyleExample { { && this in Repr && ValidComponent(iter) - && decr == (if this.next.None? then 0 else iter.Decreases() + 1) } - method Next() returns (element: T) + method Next() returns (element: Option) requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - element := next.value; - enumerated := enumerated + [element]; - - next := iter.Next(); - if this.next.None? { - decr := 0; + var hasNext := iter.HasNext(); + if hasNext { + var value := iter.Next(); + element := Some(value); } else { - decr := iter.Decreases() + 1; + element := None; } + Enumerated(element); } function Decreases(): nat @@ -100,20 +92,11 @@ module RustStyleExample { requires Valid() decreases Repr, 1 { - decr - } - - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 2 - ensures Decreases() == 0 ==> !HasNext() - { - next.Some? + iter.Decreases() } } - class SeqRustStyleIterator extends RustStyleIterator { + class SeqJavaStyleIterator extends JavaStyleIterator { var s: seq @@ -133,20 +116,23 @@ module RustStyleExample { && this in Repr } - method Next() returns (res: Option) + method HasNext() returns (result: bool) + requires Valid() + ensures !result <==> Decreases() == 0 + { + return |s| > 0; + } + + method Next() returns (res: T) requires Valid() + requires Decreases() > 0 modifies Repr ensures Valid() ensures Repr <= old(Repr) - ensures res.Some? ==> Decreases() < old(Decreases()) - ensures old(Decreases()) == 0 ==> res.None? + ensures Decreases() < old(Decreases()) { - if |s| > 0 { - res := Some(s[0]); - s := s[1..]; - } else { - res := None; - } + res := s[0]; + s := s[1..]; } function Decreases(): nat @@ -158,16 +144,17 @@ module RustStyleExample { } method Main() { - var iter := new SeqRustStyleIterator([1,2,3,4,5]); + var iter := new SeqJavaStyleIterator([1,2,3,4,5]); - var enum: Enumerator := new RustStyleIteratorEnumerator(iter); - while enum.HasNext() + var enum: Enumerator := new JavaStyleIteratorEnumerator(iter); + while true invariant enum.Valid() && fresh(enum.Repr) decreases enum.Decreases() { var element := enum.Next(); + if element.None? { break; } - print element, "\n"; + print element.value, "\n"; } } } \ No newline at end of file diff --git a/examples/Enumerators/RustStyle.dfy.expect b/examples/Enumerators/JavaStyle.dfy.expect similarity index 100% rename from examples/Enumerators/RustStyle.dfy.expect rename to examples/Enumerators/JavaStyle.dfy.expect diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index cc27a162..488d4be9 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -11,27 +11,71 @@ module Enumerators { import opened Wrappers import opened Seq - // A trait for any value that produces a finite sequence of values. - trait {:termination false} Enumerator extends Validatable { - - // The Valid() predicate from the Validatable trait ends up - // becoming the "enumeration invariant", which in turn becomes + // A trait for any value that produces a potentially infinite sequence of values. + trait {:termination false} IEnumerator extends Validatable { + + // The Valid() predicate from the Validatable trait can be thought of + // as the "enumeration invariant", which in turn becomes // the loop invariant in a while loop that uses an enumerator. // All values produced by the Next() method in the order they // were produced. - ghost var enumerated: seq + // TODO: Is this actually useful? + ghost var ienumerated: seq + + method Next() returns (element: T) + requires Valid() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures ienumerated == old(ienumerated) + [element] + } - // Any enumerator that produces one value at a time - // and provably terminates is equivalent to an enumerator - // that produces a specific seq. This value may be underspecified - // such that it is not known, even its length, until after all - // values have been produced. - // Dafny doesn't let you pass around an underspecified value though, - // so we don't define a "to be enumerated" field or function. + class NatEnumerator extends IEnumerator { + + var next: nat + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && this in Repr + // && ienumerated == Range(0, |ienumerated|) + } + + method Next() returns (element: nat) + requires Valid() + modifies Repr + decreases Repr + ensures ValidAndDisjoint() + ensures ienumerated == old(ienumerated) + [element] + { + element := next; + ienumerated := ienumerated + [element]; + next := next + 1; + } + } + + // A trait for any value that produces a finite sequence of values. + trait {:termination false} Enumerator extends IEnumerator> { + + ghost var enumerated: seq + + ghost method Enumerated(element: Option) + modifies this`ienumerated, this`enumerated + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> enumerated == old(enumerated) + [element.value] + ensures element.None? ==> unchanged(`enumerated) + { + ienumerated := ienumerated + [element]; + if element.Some? { + enumerated := enumerated + [element.value]; + } + } + // The termination measure for the enumerator. Must decrease on every - // call to Next(). + // call to Next() that doesn't return None. // // Would be better as an arbitrary termination clause somehow instead, // but we don't have language-level access to the built-in well-founded @@ -41,23 +85,10 @@ module Enumerators { decreases Repr, 1 requires Valid() - // Pre-condition for Next(). Making this a pure predicate means that - // enumerators have to at least know ahead of time if they are able to - // produce a value, even if they do not know what value it is until - // Next() is invoked. This avoids forcing the type parameter for any - // enumerator to satisfy the Auto-initializable type characteristic (0), - // and allows for much cleaner assertions about state and invariants. - // When this is overly restrictive, having an enumerator produce a wrapper - // such as Option instead is a good option (ha!), with the caveat that - // client code will need to skip over Nones. - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 2 + // TODO: Done() alias for Decreases() == 0? - method Next() returns (element: T) + method Next() returns (element: Option) requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() @@ -68,8 +99,16 @@ module Enumerators { // FilteredEnumerator below which needs to make a recursive call to // Next() inside a loop. ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + // TODO: Package up the rest in a twostate predicate + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) } /********************************************************** @@ -92,21 +131,21 @@ module Enumerators { trait EnumeratorOfSeq extends Enumerator { ghost var toEnumerate: seq - predicate method HasNext() + function Decreases(): nat reads Repr + decreases Repr, 1 requires Valid() - decreases Repr, 2 - ensures !HasNext() ==> enumerated == toEnumerate + ensures Decreases() == 0 ==> enumerated == toEnumerate } trait EnumeratorOfSet extends Enumerator { ghost var toEnumerate: set - predicate method HasNext() + function Decreases(): nat reads Repr + decreases Repr, 1 requires Valid() - decreases Repr, 2 - ensures !HasNext() ==> multiset(enumerated) == multiset(toEnumerate) + ensures Decreases() == 0 ==> multiset(enumerated) == multiset(toEnumerate) } /********************************************************** @@ -115,6 +154,9 @@ module Enumerators { * ***********************************************************/ + // TODO: Some of these should be IEnumerator adaptors instead, + // possibly with an Enumerator subclass specialization. + class SeqEnumerator extends EnumeratorOfSeq { const elements: seq @@ -153,29 +195,29 @@ module Enumerators { |elements| - index } - predicate method HasNext() - reads Repr + method Next() returns (element: Option) requires Valid() - decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 - ensures !HasNext() ==> enumerated == elements - { - index < |elements| - } - - method Next() returns (element: T) - requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - element := elements[index]; - enumerated := enumerated + [element]; - index := index + 1; + if index < |elements| { + element := Some(elements[index]); + index := index + 1; + } else { + element := None; + } + Enumerated(element); } } @@ -226,28 +268,30 @@ module Enumerators { |remaining| } - predicate method HasNext() - requires Valid() - reads this, Repr - decreases Repr, 2 - ensures !HasNext() ==> multiset(enumerated) == multiset(toEnumerate) - { - |remaining| > 0 - } - - method Next() returns (element: T) + method Next() returns (element: Option) requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - element :| element in remaining; - remaining := remaining - {element}; - enumerated := enumerated + [element]; + if |remaining| > 0 { + var t :| t in remaining; + element := Some(t); + remaining := remaining - {t}; + } else { + element := None; + } + Enumerated(element); } } @@ -286,27 +330,32 @@ module Enumerators { && enumerated == Seq.Map(f, wrapped.enumerated) } - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 2 - { - wrapped.HasNext() - } - - method Next() returns (element: R) + method Next() returns (element: Option) requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - var t := wrapped.Next(); - element := f(t); - enumerated := enumerated + [element]; + var optT := wrapped.Next(); + match optT + case Some(t) => { + element := Some(f(t)); + Enumerated(element); + } + case None => { + element := None; + Enumerated(element); + } } function Decreases(): nat @@ -322,6 +371,7 @@ module Enumerators { class ConcatEnumerator extends Enumerator { const first: Enumerator + var firstDone: bool const second: Enumerator constructor(first: Enumerator, second: Enumerator) @@ -334,6 +384,7 @@ module Enumerators { ensures this.second == second { this.first := first; + this.firstDone := false; this.second := second; enumerated := first.enumerated + second.enumerated; @@ -349,38 +400,38 @@ module Enumerators { && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr - && (first.HasNext() ==> second.enumerated == []) + && (firstDone ==> first.Decreases() == 0) + && (!firstDone ==> second.enumerated == []) && enumerated == first.enumerated + second.enumerated } - predicate method HasNext() + method Next() returns (element: Option) requires Valid() - reads this, Repr - decreases Repr, 2 - { - first.HasNext() || second.HasNext() - } - - method Next() returns (element: T) - requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - if first.HasNext() { + element := None; + if !firstDone { element := first.Next(); - assert first.Decreases() < old(first.Decreases()); - } else { + } + if element.None? { + firstDone := true; element := second.Next(); - assert second.Decreases() < old(second.Decreases()); } Repr := {this} + first.Repr + second.Repr; - enumerated := enumerated + [element]; + Enumerated(element); } function Decreases(): nat @@ -392,6 +443,142 @@ module Enumerators { } } + // class ZipEnumerator extends Enumerator<(A, B)> { + + // const first: Enumerator + // const second: Enumerator + + // constructor(first: Enumerator, second: Enumerator) + // requires first.Valid() && first.enumerated == [] + // requires second.Valid() && second.enumerated == [] + // requires first.Repr !! second.Repr + // ensures Valid() + // ensures fresh(Repr - first.Repr - second.Repr) + // ensures this.first == first + // ensures this.second == second + // { + // this.first := first; + // this.second := second; + + // enumerated := []; + // Repr := {this} + first.Repr + second.Repr; + // } + + // predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // decreases Repr, 0 + // { + // && this in Repr + // && ValidComponent(first) + // && ValidComponent(second) + // && first.Repr !! second.Repr + // && |first.enumerated| == |second.enumerated| + // && enumerated == Seq.Zip(first.enumerated, second.enumerated) + // } + + // method Next() returns (element: Option<(A, B)>) + // requires Valid() + // modifies Repr + // decreases Repr + // ensures Valid() + // ensures Repr <= old(Repr) + // ensures ienumerated == old(ienumerated) + [element] + // ensures element.Some? ==> ( + // && Decreases() < old(Decreases()) + // && enumerated == old(enumerated) + [element.value] + // ) + // ensures element.None? ==> ( + // && Decreases() == 0 + // && enumerated == old(enumerated) + // ) + // { + // var left := first.Next(); + // var right := second.Next(); + // if left.Some? && right.Some? { + // element := Some((left.value, right.value)); + // } else { + // element := None; + // } + + // Repr := {this} + first.Repr + second.Repr; + // Enumerated(element); + // } + + // function Decreases(): nat + // reads this, Repr + // requires Valid() + // decreases Repr, 1 + // { + // first.Decreases() + // } + // } + + // class WithIndexEnumerator extends Enumerator<(T, nat)> { + + // const wrapped: Enumerator + // var nextIndex: nat + + // constructor(wrapped: Enumerator) + // requires wrapped.Valid() + // requires wrapped.enumerated == [] + // ensures Valid() + // ensures fresh(Repr - wrapped.Repr) + // ensures enumerated == [] + // ensures this.wrapped == wrapped + // { + // this.wrapped := wrapped; + // this.nextIndex := 0; + + // Repr := {this} + wrapped.Repr; + // enumerated := []; + // } + + // predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // decreases Repr, 0 + // { + // && this in Repr + // && ValidComponent(wrapped) + // && enumerated == Seq.Zip(wrapped.enumerated, Seq.Range(0, |wrapped.enumerated|)) + // } + + // predicate method HasNext() + // reads Repr + // requires Valid() + // decreases Repr, 2 + // ensures HasNext() ==> Decreases() > 0 + // { + // assert wrapped.HasNext() ==> Decreases() > 0; + // wrapped.HasNext() + // } + + // method Next() returns (element: (T, nat)) + // requires Valid() + // requires HasNext() + // modifies Repr + // decreases Repr + // ensures Valid() + // ensures Repr <= old(Repr) + // ensures Decreases() < old(Decreases()) + // ensures enumerated == old(enumerated) + [element] + // { + // var t := wrapped.Next(); + // element := (t, nextIndex); + // nextIndex := nextIndex + 1; + // enumerated := enumerated + [element]; + // } + + // function Decreases(): nat + // reads this, Repr + // requires Valid() + // decreases Repr, 1 + // { + // wrapped.Decreases() + // } + // } + // Note that to satisfy the Enumerator API, this enumerator has // to eagerly fetch the next value to return from Next(). // An alternative is to use a MappingEnumerator that maps to Option @@ -404,7 +591,6 @@ module Enumerators { constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() requires wrapped.enumerated == [] - modifies wrapped.Repr ensures Valid() ensures fresh(Repr - old(wrapped.Repr)) ensures enumerated == [] @@ -416,20 +602,6 @@ module Enumerators { this.next := None; Repr := {this} + wrapped.Repr; enumerated := []; - - new; - - FindNext(); - } - - predicate AlmostValid() - reads this, Repr - ensures AlmostValid() ==> this in Repr - decreases Repr, 0 - { - && this in Repr - && ValidComponent(wrapped) - && (if next.Some? then enumerated + [next.value] else enumerated) == Seq.Filter(filter, wrapped.enumerated) } predicate Valid() @@ -437,55 +609,35 @@ module Enumerators { ensures Valid() ==> this in Repr decreases Repr, 0 { - && AlmostValid() - && (next.None? ==> !wrapped.HasNext()) - } - - predicate method HasNext() - reads Repr - requires Valid() - decreases Repr, 2 - ensures HasNext() ==> Decreases() > 0 - ensures !HasNext() ==> !wrapped.HasNext() - { - assert if next.Some? then Decreases() >= 1 else true; - next.Some? + && this in Repr + && ValidComponent(wrapped) + && enumerated == Seq.Filter(filter, wrapped.enumerated) } - method Next() returns (element: T) + method Next() returns (element: Option) requires Valid() - requires HasNext() modifies Repr decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures Decreases() < old(Decreases()) - ensures enumerated == old(enumerated) + [element] + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) { - element := next.value; - enumerated := enumerated + [element]; - next := None; - - FindNext(); - } - - method FindNext() - requires AlmostValid() - requires next.None? - modifies Repr - decreases Repr, 0 - ensures Valid() - ensures Decreases() <= old(wrapped.Decreases() + (if next.Some? then 1 else 0)) - ensures unchanged(this`enumerated) - ensures unchanged(this`Repr) - { - while wrapped.HasNext() && next.None? - invariant AlmostValid() + element := None; + while true + invariant Valid() invariant wrapped.Repr < old(Repr) invariant Repr == old(Repr) + invariant unchanged(this`ienumerated) invariant unchanged(this`enumerated) - invariant wrapped.Decreases() + (if next.Some? then 1 else 0) <= old(wrapped.Decreases() + (if next.Some? then 1 else 0)) - decreases wrapped.Decreases() + decreases wrapped.Decreases(), element { var wrappedEnumeratedBefore := wrapped.enumerated; // This is where it is very difficult to prove termination if we @@ -493,14 +645,20 @@ module Enumerators { // we must satisfy for the recursive call to be allowed is actually // wrapped.Repr < old(Repr). That means updating Repr after this call // wouldn't help. - var t := wrapped.Next(); + var element := wrapped.Next(); + if element.None? { break; } + reveal Seq.Filter(); - LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [t]); - - if filter(t) { - next := Some(t); + LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [element.value]); + + if filter(element.value) { + break; } } + Enumerated(element); + assert this in Repr; + assert ValidComponent(wrapped); + assert enumerated == Seq.Filter(filter, wrapped.enumerated); } function Decreases(): nat @@ -508,9 +666,7 @@ module Enumerators { requires Valid() decreases Repr, 1 { - // If we could declare semi-arbitrary values as in decreases clauses, - // this could just be (wrapped.Decreases(), next) - wrapped.Decreases() + (if next.Some? then 1 else 0) + wrapped.Decreases() } } @@ -519,22 +675,26 @@ module Enumerators { requires e.enumerated == [] modifies e.Repr ensures e.Valid() - ensures !e.HasNext() + ensures e.Decreases() == 0 ensures result == Seq.FoldLeft(f, init, e.enumerated) { reveal Seq.FoldLeft(); result := init; - while e.HasNext() + while true invariant e.Valid() && e.Repr <= old(e.Repr) decreases e.Decreases() invariant result == Seq.FoldLeft(f, init, e.enumerated) { + // TODO: Will the foreach loop sugar support this? + // May at least need to use old@, IEnumerator) -> IEnumerator<(A, B)> + // * If either argument is finte, the result is too + // * If both arguments are sized, the result is too + // + // ...and if the class forgets any useful such properties, + // additional code can't add them later since the methods are opaque :( + // + // May also want an Enumerator2 variation, to avoid the + // pair overhead for common cases like `foreach x, index in` + class ZipEnumerator extends Enumerator<(A, B)> { - // const first: Enumerator - // const second: Enumerator + const first: Enumerator + const second: Enumerator - // constructor(first: Enumerator, second: Enumerator) - // requires first.Valid() && first.enumerated == [] - // requires second.Valid() && second.enumerated == [] - // requires first.Repr !! second.Repr - // ensures Valid() - // ensures fresh(Repr - first.Repr - second.Repr) - // ensures this.first == first - // ensures this.second == second - // { - // this.first := first; - // this.second := second; - - // enumerated := []; - // Repr := {this} + first.Repr + second.Repr; - // } + constructor(first: Enumerator, second: Enumerator, size: nat) + requires first.Valid() && first.enumerated == [] + requires second.Valid() && second.enumerated == [] + requires first.Repr !! second.Repr + ensures Valid() + ensures fresh(Repr - first.Repr - second.Repr) + ensures this.first == first + ensures this.second == second + { + this.first := first; + this.second := second; - // predicate Valid() - // reads this, Repr - // ensures Valid() ==> this in Repr - // decreases Repr, 0 - // { - // && this in Repr - // && ValidComponent(first) - // && ValidComponent(second) - // && first.Repr !! second.Repr - // && |first.enumerated| == |second.enumerated| - // && enumerated == Seq.Zip(first.enumerated, second.enumerated) - // } + enumerated := []; + Repr := {this} + first.Repr + second.Repr; + } - // method Next() returns (element: Option<(A, B)>) - // requires Valid() - // modifies Repr - // decreases Repr - // ensures Valid() - // ensures Repr <= old(Repr) - // ensures ienumerated == old(ienumerated) + [element] - // ensures element.Some? ==> ( - // && Decreases() < old(Decreases()) - // && enumerated == old(enumerated) + [element.value] - // ) - // ensures element.None? ==> ( - // && Decreases() == 0 - // && enumerated == old(enumerated) - // ) - // { - // var left := first.Next(); - // var right := second.Next(); - // if left.Some? && right.Some? { - // element := Some((left.value, right.value)); - // } else { - // element := None; - // } + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && this in Repr + && ValidComponent(first) + && ValidComponent(second) + && first.Repr !! second.Repr + // TODO: See top-level comment + // && |enumerated| == |first.enumerated| == |second.enumerated| + // && enumerated == Seq.Zip(first.enumerated, second.enumerated) + } + + method Next() returns (element: Option<(A, B)>) + requires Valid() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) + { + var left := first.Next(); + var right := second.Next(); + if left.Some? && right.Some? { + element := Some((left.value, right.value)); + } else { + element := None; + } - // Repr := {this} + first.Repr + second.Repr; - // Enumerated(element); - // } + Repr := {this} + first.Repr + second.Repr; + Enumerated(element); + } - // function Decreases(): nat - // reads this, Repr - // requires Valid() - // decreases Repr, 1 - // { - // first.Decreases() - // } - // } + function Decreases(): nat + reads this, Repr + requires Valid() + decreases Repr, 1 + { + Math.Min(first.Decreases(), second.Decreases()) + } + } class WithIndexEnumerator extends Enumerator<(T, nat)> { @@ -560,7 +585,8 @@ module Enumerators { { && this in Repr && ValidComponent(wrapped) - && enumerated == Seq.Zip(wrapped.enumerated, seq(|wrapped.enumerated|, i => i)) + // TODO: Similar challenge to ZipEnumerator although hopefully not as hard + // && enumerated == Seq.Zip(wrapped.enumerated, seq(|wrapped.enumerated|, i => i)) } method Next() returns (element: Option<(T, nat)>) @@ -588,8 +614,8 @@ module Enumerators { } Enumerated(element); - reveal Seq.Zip(); - assert enumerated == Seq.Zip(wrapped.enumerated, seq(|wrapped.enumerated|, i => i)); + // reveal Seq.Zip(); + // assert enumerated == Seq.Zip(wrapped.enumerated, seq(|wrapped.enumerated|, i => i)); } function Decreases(): nat @@ -601,14 +627,9 @@ module Enumerators { } } - // Note that to satisfy the Enumerator API, this enumerator has - // to eagerly fetch the next value to return from Next(). - // An alternative is to use a MappingEnumerator that maps to Option - // values, and having consumers skip None values. class FilteredEnumerator extends Enumerator { const wrapped: Enumerator const filter: T -> bool - var next: Option constructor(wrapped: Enumerator, filter: T -> bool) requires wrapped.Valid() @@ -621,7 +642,6 @@ module Enumerators { { this.wrapped := wrapped; this.filter := filter; - this.next := None; Repr := {this} + wrapped.Repr; enumerated := []; } @@ -653,13 +673,17 @@ module Enumerators { ) { element := None; - while true + var more := true; + while more invariant Valid() invariant wrapped.Repr < old(Repr) invariant Repr == old(Repr) invariant element.Some? ==> Decreases() < old(Decreases()) + invariant !more && element.None? ==> Decreases() == 0 + invariant !more && element.None? ==> enumerated == old(enumerated) + invariant unchanged(`ienumerated) invariant enumerated == Seq.Filter(filter, wrapped.enumerated) - decreases wrapped.Decreases() + decreases wrapped.Decreases(), more { var wrappedEnumeratedBefore := wrapped.enumerated; // This is where it is very difficult to prove termination if we @@ -668,22 +692,23 @@ module Enumerators { // wrapped.Repr < old(Repr). That means updating Repr after this call // wouldn't help. var element := wrapped.Next(); - if element.None? { break; } - - reveal Seq.Filter(); - LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [element.value]); - - if filter(element.value) { - Enumerated(element); - break; + if element.None? { + assert Decreases() == 0; + more := false; + // break; + } else { + reveal Seq.Filter(); + LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [element.value]); + + if filter(element.value) { + enumerated := enumerated + [element.value]; + more := false; + // break; + } else { + } } } - if element.None? { - Enumerated(element); - } - assert this in Repr; - assert ValidComponent(wrapped); - assert enumerated == Seq.Filter(filter, wrapped.enumerated); + ienumerated := ienumerated + [element]; } function Decreases(): nat @@ -752,34 +777,62 @@ module Enumerators { // Seq.LemmaInvFoldLeft(???, (s, x, s') => s + x == s', f, [], []); } - method CollectToArray(e: SizedEnumerator) returns (result: array) - requires e.Valid() - requires e.enumerated == [] - modifies e.Repr - ensures e.Valid() - ensures e.Decreases() == 0 - ensures result[..] == e.enumerated - { - result := new T[e.count]; - var eWithIndex: Enumerator := new WithIndexEnumerator(e); - while true - invariant eWithIndex.Valid() && fresh(eWithIndex.Repr - e.Repr) - modifies eWithIndex.Repr - decreases eWithIndex.Decreases() - { - var pair := eWithIndex.Next(); - if pair.None? { break; } - var (element, index) := pair.value; + // TODO: Still working on this one, it needs a solid specification of the number of + // elements e will enumerate. + // method CollectToArray(e: Enumerator, size: nat) returns (result: array) + // requires e.Valid() + // requires e.enumerated == [] + // requires Sized(e, size) + // modifies e.Repr + // ensures e.Valid() + // ensures e.Decreases() == 0 + // ensures result[..] == e.enumerated + // { + // result := new T[size]; + // var eWithIndex: Enumerator := new WithIndexEnumerator(e); + // while true + // invariant eWithIndex.Valid() && fresh(eWithIndex.Repr - e.Repr) + // modifies eWithIndex.Repr + // decreases eWithIndex.Decreases() + // { + // var pair := eWithIndex.Next(); + // if pair.None? { break; } + // var (element, index) := pair.value; - result[index] := element; - } - assert |e.enumerated| == e.count; + // result[index] := element; + // } + // assert |e.enumerated| == size; + // } - // TODO: Figure out how to use Fold instead. Good case study for invariant support! - // var f := (s, x) => s + [x]; - // result := Fold(f, [], e); - // Seq.LemmaInvFoldLeft(???, (s, x, s') => s + x == s', f, [], []); + trait Enumerable extends Validatable { + + method Enumerator() returns (e: Enumerator) + requires Valid() + ensures Valid() + ensures e.Valid() + ensures fresh(e.Repr) + // TODO: How to express "will enumerate the contents of this"? } + class SeqEnumerable extends Enumerable { + const s: seq + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + this in Repr + } + + method Enumerator() returns (e: Enumerator) + requires Valid() + ensures Valid() + ensures e.Valid() + ensures fresh(e.Repr) + { + e := new SeqEnumerator(s); + } + } } \ No newline at end of file From a811a1fb3d9602cf4a3d0c917d7328a883c6a3e4 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 1 Mar 2022 10:05:35 -0800 Subject: [PATCH 36/68] Added WithEnumeratedEnumerator --- src/Enumerators/Enumerators.dfy | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index be7db8ac..93f266c0 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -720,6 +720,71 @@ module Enumerators { } } + // This would allow a foreach loop to bind the values enumerated so far: + // + // foreach (x, xs) in WithEnumerated(collection) { ... } + class WithEnumeratedEnumerator extends Enumerator<(T, ghost seq)> { + const wrapped: Enumerator + + constructor(wrapped: Enumerator) + requires wrapped.Valid() + requires wrapped.enumerated == [] + ensures Valid() + ensures fresh(Repr - wrapped.Repr) + ensures enumerated == [] + ensures this.wrapped == wrapped + { + this.wrapped := wrapped; + Repr := {this} + wrapped.Repr; + enumerated := []; + } + + predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && this in Repr + && ValidComponent(wrapped) + } + + method Next() returns (element: Option<(T, ghost seq)>) + requires Valid() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures ienumerated == old(ienumerated) + [element] + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) + { + var optT := wrapped.Next(); + match optT + case Some(t) => { + element := Some((t, ghost wrapped.enumerated)); + Enumerated(element); + } + case None => { + element := None; + Enumerated(element); + } + } + + function Decreases(): nat + reads this, Repr + requires Valid() + decreases Repr, 1 + { + wrapped.Decreases() + } + } + method Fold(f: (A, T) -> A, init: A, e: Enumerator) returns (result: A) requires e.Valid() requires e.enumerated == [] From a208cd487a1f021dc8474dae985f7a330c8d8858 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 1 Mar 2022 10:09:52 -0800 Subject: [PATCH 37/68] Extra comment --- src/Enumerators/Enumerators.dfy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 93f266c0..6c3695e3 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -723,6 +723,9 @@ module Enumerators { // This would allow a foreach loop to bind the values enumerated so far: // // foreach (x, xs) in WithEnumerated(collection) { ... } + // + // I like this better than the magical definitions iterators provide, + // since this way you're not culture-specific. :) class WithEnumeratedEnumerator extends Enumerator<(T, ghost seq)> { const wrapped: Enumerator From e68755c8d4372a0aa112731b61267e914a880240 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 2 Mar 2022 09:06:27 -0800 Subject: [PATCH 38/68] Quick note --- src/Enumerators/Enumerators.dfy | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 6c3695e3..027ae5fc 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -142,6 +142,7 @@ module Enumerators { // its two children do, so it forces a separate SizedConcatEnumerator // (which can't even share implementation by extending SizedEnumerator) + // TODO: Don't use "Size", implies sizeof() semantics predicate Sized(e: Enumerator, count: nat) reads e.Repr requires e.Valid() From 256aabe61caad0978e23d948fdfad8d052b97c8b Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 3 May 2023 15:12:06 -0700 Subject: [PATCH 39/68] Update for Dafny 4, couple of notes --- src/Enumerators/Enumerators.dfy | 29 +++++++++++++++-------------- src/Frames.dfy | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Enumerators/Enumerators.dfy b/src/Enumerators/Enumerators.dfy index 027ae5fc..0edea5f7 100644 --- a/src/Enumerators/Enumerators.dfy +++ b/src/Enumerators/Enumerators.dfy @@ -37,7 +37,7 @@ module Enumerators { var next: nat - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -93,6 +93,7 @@ module Enumerators { // Would be better as an arbitrary termination clause somehow instead, // but we don't have language-level access to the built-in well-founded // ordering. See https://github.com/dafny-lang/dafny/issues/762. + // TODO: Rename since this isn't 1:1 with decreases clauses, possibly "Remaining" function Decreases(): nat reads Repr decreases Repr, 1 @@ -143,28 +144,28 @@ module Enumerators { // (which can't even share implementation by extending SizedEnumerator) // TODO: Don't use "Size", implies sizeof() semantics - predicate Sized(e: Enumerator, count: nat) + ghost predicate Sized(e: Enumerator, count: nat) reads e.Repr requires e.Valid() { e.Decreases() == 0 ==> |e.enumerated| == count } - predicate EnumeratesMultiset(e: Enumerator, enumerated: multiset) + ghost predicate EnumeratesMultiset(e: Enumerator, enumerated: multiset) reads e.Repr requires e.Valid() { e.Decreases() == 0 ==> multiset(e.enumerated) == enumerated } - predicate EnumeratesSet(e: Enumerator, enumerated: set) + ghost predicate EnumeratesSet(e: Enumerator, enumerated: set) reads e.Repr requires e.Valid() { e.Decreases() == 0 ==> multiset(e.enumerated) == multiset(enumerated) } - predicate EnumeratesSeq(e: Enumerator, enumerated: seq) + ghost predicate EnumeratesSeq(e: Enumerator, enumerated: seq) reads e.Repr requires e.Valid() { @@ -198,7 +199,7 @@ module Enumerators { Repr := {this}; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -270,7 +271,7 @@ module Enumerators { Repr := {this}; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -340,7 +341,7 @@ module Enumerators { enumerated := []; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -417,7 +418,7 @@ module Enumerators { Repr := {this} + first.Repr + second.Repr; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -508,7 +509,7 @@ module Enumerators { Repr := {this} + first.Repr + second.Repr; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -579,7 +580,7 @@ module Enumerators { enumerated := []; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -647,7 +648,7 @@ module Enumerators { enumerated := []; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -743,7 +744,7 @@ module Enumerators { enumerated := []; } - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 @@ -886,7 +887,7 @@ module Enumerators { class SeqEnumerable extends Enumerable { const s: seq - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 diff --git a/src/Frames.dfy b/src/Frames.dfy index 778c5a80..1b8cd085 100644 --- a/src/Frames.dfy +++ b/src/Frames.dfy @@ -11,14 +11,14 @@ module Frames { // methods need to read. ghost var Repr: set - predicate Valid() + ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr decreases Repr, 0 // Convenience predicate for when your object's validity depends on one // or more other objects. - predicate ValidComponent(component: Validatable) + ghost predicate ValidComponent(component: Validatable) reads this, Repr { && component in Repr From 2f2aa051e0e9e3181b310860bf60d3a50fad91a7 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 8 May 2023 10:41:44 -0700 Subject: [PATCH 40/68] Trying out a pure-functional version --- src/Enumerators/EnumeratorFns.dfy | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/Enumerators/EnumeratorFns.dfy diff --git a/src/Enumerators/EnumeratorFns.dfy b/src/Enumerators/EnumeratorFns.dfy new file mode 100644 index 00000000..f7d5df2b --- /dev/null +++ b/src/Enumerators/EnumeratorFns.dfy @@ -0,0 +1,71 @@ + + +datatype Option<+T> = Some(value: T) | None + +function SeqEnumeratorFn(s: seq): Option<(seq, T)> { + if |s| == 0 then + None + else + Some((s[1..], s[0])) +} + +function SetEnumeratorFn(s: set): Option<(set, T)> { + if |s| == 0 then + None + else + var t :| t in s; + Some((s - {t}, t)) +} + +type EnumeratorFn = S -> Option<(S, T)> +datatype Enumerator_ = Enumerator(state: S, fn: EnumeratorFn, ghost decreasesFn: S -> nat) { + ghost predicate Valid() { + Decreases(fn, decreasesFn) + } +} +ghost predicate Decreases(fn: EnumeratorFn, decreasesFn: S -> nat) { + forall s: S :: match fn(s) + case Some((s', _)) => decreasesFn(s') < decreasesFn(s) + case None => true +} + +type Enumerator = e: Enumerator_ | e.Valid() witness * + +function {:tailrecursion} Enumerate(e: Enumerator): seq + decreases e.decreasesFn(e.state) +{ + match e.fn(e.state) + case Some((s, t)) => [t] + Enumerate(Enumerator(s, e.fn, e.decreasesFn)) + case None => [] +} + +function ConcatEnumeratorFn(first: EnumeratorFn, second: EnumeratorFn): EnumeratorFn<(A, B), T> { + x => + var (a, b) := x; + match first(a) + case Some((a', t)) => Some(((a', b), t)) + case None => + match second(b) + case Some((b', t)) => Some(((a, b'), t)) + case None => None +} + +function ConcatDecreasesFn(first: A -> nat, second: B -> nat): ((A, B)) -> nat { + x => + var (a, b) := x; + var result: nat := first(a) + second(b); + result +} + +function ConcatEnumerator(first: Enumerator, second: Enumerator): Enumerator<(A, B), T> { + var result := Enumerator((first.state, second.state), + ConcatEnumeratorFn(first.fn, second.fn), + ConcatDecreasesFn(first.decreasesFn, second.decreasesFn)); + result +} + +lemma ConcatEnumeratesConcat(first: Enumerator, second: Enumerator) + ensures Enumerate(ConcatEnumerator(first, second)) == Enumerate(first) + Enumerate(second) +{ + +} \ No newline at end of file From 5986181d5b49d12c54b4832315c45fdad6d53ca6 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 10 May 2023 14:03:48 -0700 Subject: [PATCH 41/68] Promising success with constant Next invariant --- src/Enumerators/EnumeratorsSandbox.dfy | 258 +++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 src/Enumerators/EnumeratorsSandbox.dfy diff --git a/src/Enumerators/EnumeratorsSandbox.dfy b/src/Enumerators/EnumeratorsSandbox.dfy new file mode 100644 index 00000000..3b0a0d75 --- /dev/null +++ b/src/Enumerators/EnumeratorsSandbox.dfy @@ -0,0 +1,258 @@ + +include "../Wrappers.dfy" +include "../Frames.dfy" +include "../Collections/Sequences/Seq.dfy" + +module Enumerators { + + import opened Wrappers + import opened Frames + import opened Seq + + // An invariant that constrains what Next() can return, + // based on the previously enumerated elements. + // Used to express possibly incomplete knowledge about the enumerator's behavior, + // without needing to know the concrete class of the enumerator + // or read any of its fields. + type EnumerationInvariant = (seq, Option) -> bool + + trait {:termination false} Enumerator extends Validatable { + + ghost var enumerated: seq + + ghost const inv: EnumerationInvariant + + ghost method Enumerated(element: Option) + modifies this`enumerated + ensures element.Some? ==> enumerated == old(enumerated) + [element.value] + ensures element.None? ==> unchanged(`enumerated) + { + if element.Some? { + enumerated := enumerated + [element.value]; + } + } + + // The termination measure for the enumerator. Must decrease on every + // call to Next() that doesn't return None. + // + // Would be better as an arbitrary termination clause somehow instead, + // but we don't have language-level access to the built-in well-founded + // ordering. See https://github.com/dafny-lang/dafny/issues/762. + // TODO: Rename since this isn't 1:1 with decreases clauses, possibly "Remaining" + function Decreases(): nat + reads Repr + decreases Repr, 1 + requires Valid() + + // TODO: Done() alias for Decreases() == 0? + + method Next() returns (element: Option) + requires Valid() + modifies Repr + decreases Repr + ensures Valid() + // This is more restrictive than the usual post-condition of + // Validatable.ValidAndDisjoint(), because if we allow the Repr + // of an enumerator to grow, even if fresh, it becomes much more + // difficult to prove termination of a wrapper enumerator like + // FilteredEnumerator below which needs to make a recursive call to + // Next() inside a loop. + ensures Repr <= old(Repr) + ensures inv(old(enumerated), element) + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) + } + + class SeqEnumerator extends Enumerator { + + const elements: seq + var index: nat + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + ensures enumerated == [] + ensures elements == s + { + elements := s; + index := 0; + + enumerated := []; + Repr := {this}; + inv := (sofar, next: Option) => ( + match next + case Some(x) => sofar + [x] <= s + case None => sofar == s + ); + } + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && this in Repr + && 0 <= index <= |elements| + && enumerated == elements[0..index] + && inv == (sofar, next: Option) => ( + match next + case Some(x) => sofar + [x] <= elements + case None => sofar == elements + ) + } + + function Decreases(): nat + reads Repr + requires Valid() + decreases Repr, 1 + { + |elements| - index + } + + method Next() returns (element: Option) + requires Valid() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures inv(old(enumerated), element) + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) + { + if index < |elements| { + element := Some(elements[index]); + index := index + 1; + } else { + element := None; + assert enumerated == elements; + } + Enumerated(element); + } + } + + class MappingEnumerator extends Enumerator { + const wrapped: Enumerator + const f: T -> R + + constructor(f: T -> R, wrapped: Enumerator) + requires wrapped.Valid() + requires wrapped.enumerated == [] + ensures Valid() + ensures fresh(Repr - wrapped.Repr) + ensures enumerated == [] + ensures this.wrapped == wrapped + ensures this.f == f + { + this.wrapped := wrapped; + this.f := f; + Repr := {this} + wrapped.Repr; + enumerated := []; + inv := (sofar, next: Option) => ( + && exists wrappedSofar :: + && sofar == Seq.Map(f, wrappedSofar) + && match next + case Some(r) => exists t :: r == f(t) && wrapped.inv(wrappedSofar, Some(t)) + case None => wrapped.inv(wrappedSofar, None) + ); + } + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + decreases Repr, 0 + { + && this in Repr + && ValidComponent(wrapped) + && enumerated == Seq.Map(f, wrapped.enumerated) + && inv == (sofar, next: Option) => ( + && exists wrappedSofar :: + && sofar == Seq.Map(f, wrappedSofar) + && match next + case Some(r) => exists t :: r == f(t) && wrapped.inv(wrappedSofar, Some(t)) + case None => wrapped.inv(wrappedSofar, None) + ) + } + + method Next() returns (element: Option) + requires Valid() + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures inv(old(enumerated), element) + ensures element.Some? ==> ( + && Decreases() < old(Decreases()) + && enumerated == old(enumerated) + [element.value] + ) + ensures element.None? ==> ( + && Decreases() == 0 + && enumerated == old(enumerated) + ) + { + var optT := wrapped.Next(); + match optT + case Some(t) => { + element := Some(f(t)); + Enumerated(element); + } + case None => { + element := None; + Enumerated(element); + } + } + + function Decreases(): nat + reads this, Repr + requires Valid() + decreases Repr, 1 + { + wrapped.Decreases() + } + } + + ghost predicate EnumeratesThisMany(e: Enumerator, n: nat) { + forall sofar :: e.inv(sofar, None) ==> |sofar| == n + } + + ghost predicate Enumerates(e: Enumerator, s: seq) { + forall sofar :: e.inv(sofar, None) ==> sofar == s + } + + method MapEnumerator(f: T -> R, e: Enumerator) returns (e': Enumerator) + requires e.Valid() + requires e.enumerated == [] + ensures (e'.inv == (sofar: seq, next: Option) => ( + && exists wrappedSofar :: + && sofar == Seq.Map(f, wrappedSofar) + && match next + case Some(r) => exists t :: r == f(t) && e.inv(wrappedSofar, Some(t)) + case None => e.inv(wrappedSofar, None))) + { + e' := new MappingEnumerator(f, e); + } + + method Main() { + var e: Enumerator := new SeqEnumerator([1, 2, 3, 4, 5]); + assert EnumeratesThisMany(e, 5); + assert Enumerates(e, [1, 2, 3, 4, 5]); + + var f := (x: int) => x + 2; + var e': Enumerator := MapEnumerator(f, e); + assert EnumeratesThisMany(e', 5); + + assert forall y :: exists x | y == f(x) :: x == y - 2; + assert Enumerates(e, [3, 4, 5, 6, 7]); + } +} \ No newline at end of file From 47dcc9d0c656370dc882eb616ceb9c641431af50 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 11 May 2023 15:56:17 -0700 Subject: [PATCH 42/68] Partial attempt to use function instead of constant function value --- src/Enumerators/EnumeratorsSandbox.dfy | 101 ++++++++++++++++--------- 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/src/Enumerators/EnumeratorsSandbox.dfy b/src/Enumerators/EnumeratorsSandbox.dfy index 3b0a0d75..a07be046 100644 --- a/src/Enumerators/EnumeratorsSandbox.dfy +++ b/src/Enumerators/EnumeratorsSandbox.dfy @@ -14,13 +14,16 @@ module Enumerators { // Used to express possibly incomplete knowledge about the enumerator's behavior, // without needing to know the concrete class of the enumerator // or read any of its fields. - type EnumerationInvariant = (seq, Option) -> bool + // type EnumerationInvariant = (seq, Option) -> bool trait {:termination false} Enumerator extends Validatable { ghost var enumerated: seq - ghost const inv: EnumerationInvariant + ghost predicate Invariant(sofar: seq, next: Option) + reads Repr + requires Valid() + decreases Repr, 1 ghost method Enumerated(element: Option) modifies this`enumerated @@ -58,7 +61,6 @@ module Enumerators { // FilteredEnumerator below which needs to make a recursive call to // Next() inside a loop. ensures Repr <= old(Repr) - ensures inv(old(enumerated), element) ensures element.Some? ==> ( && Decreases() < old(Decreases()) && enumerated == old(enumerated) + [element.value] @@ -85,11 +87,6 @@ module Enumerators { enumerated := []; Repr := {this}; - inv := (sofar, next: Option) => ( - match next - case Some(x) => sofar + [x] <= s - case None => sofar == s - ); } ghost predicate Valid() @@ -100,11 +97,14 @@ module Enumerators { && this in Repr && 0 <= index <= |elements| && enumerated == elements[0..index] - && inv == (sofar, next: Option) => ( - match next - case Some(x) => sofar + [x] <= elements - case None => sofar == elements - ) + } + + ghost predicate Invariant(sofar: seq, next: Option) + decreases Repr, 1 + { + match next + case Some(x) => sofar + [x] <= elements + case None => sofar == elements } function Decreases(): nat @@ -121,7 +121,6 @@ module Enumerators { decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures inv(old(enumerated), element) ensures element.Some? ==> ( && Decreases() < old(Decreases()) && enumerated == old(enumerated) + [element.value] @@ -159,13 +158,6 @@ module Enumerators { this.f := f; Repr := {this} + wrapped.Repr; enumerated := []; - inv := (sofar, next: Option) => ( - && exists wrappedSofar :: - && sofar == Seq.Map(f, wrappedSofar) - && match next - case Some(r) => exists t :: r == f(t) && wrapped.inv(wrappedSofar, Some(t)) - case None => wrapped.inv(wrappedSofar, None) - ); } ghost predicate Valid() @@ -176,13 +168,18 @@ module Enumerators { && this in Repr && ValidComponent(wrapped) && enumerated == Seq.Map(f, wrapped.enumerated) - && inv == (sofar, next: Option) => ( - && exists wrappedSofar :: + } + + ghost predicate Invariant(sofar: seq, next: Option) + reads Repr + requires Valid() + decreases Repr, 1 + { + exists wrappedSofar :: && sofar == Seq.Map(f, wrappedSofar) && match next - case Some(r) => exists t :: r == f(t) && wrapped.inv(wrappedSofar, Some(t)) - case None => wrapped.inv(wrappedSofar, None) - ) + case Some(r) => exists t :: r == f(t) && wrapped.Invariant(wrappedSofar, Some(t)) + case None => wrapped.Invariant(wrappedSofar, None) } method Next() returns (element: Option) @@ -191,7 +188,7 @@ module Enumerators { decreases Repr ensures Valid() ensures Repr <= old(Repr) - ensures inv(old(enumerated), element) + ensures Invariant(old(enumerated), element) ensures element.Some? ==> ( && Decreases() < old(Decreases()) && enumerated == old(enumerated) + [element.value] @@ -222,23 +219,59 @@ module Enumerators { } } - ghost predicate EnumeratesThisMany(e: Enumerator, n: nat) { - forall sofar :: e.inv(sofar, None) ==> |sofar| == n + ghost predicate EnumeratesThisMany(e: Enumerator, n: nat) + reads e.Repr + requires e.Valid() + { + forall sofar :: e.Invariant(sofar, None) ==> |sofar| == n } - ghost predicate Enumerates(e: Enumerator, s: seq) { - forall sofar :: e.inv(sofar, None) ==> sofar == s + ghost predicate Enumerates(e: Enumerator, s: seq) + reads e.Repr + requires e.Valid() + { + forall sofar :: e.Invariant(sofar, None) ==> sofar == s + } + + method Enumerate(e: Enumerator) returns (s: seq) + requires e.Valid() + requires e.enumerated == [] + modifies e.Repr + ensures e.Valid() + ensures s == e.enumerated + ensures e.Invariant(s, None) + { + s := []; + while true + invariant e.Valid() + invariant e.Repr <= old(e.Repr) + invariant s == e.enumerated + decreases e.Decreases() + modifies e.Repr + { + var next := e.Next(); + match next { + case Some(x) => { + s := s + [x]; + } + case None => { + assert e.Invariant(e.enumerated, None); + return; + } + } + } } method MapEnumerator(f: T -> R, e: Enumerator) returns (e': Enumerator) requires e.Valid() requires e.enumerated == [] - ensures (e'.inv == (sofar: seq, next: Option) => ( + ensures e'.Valid() + ensures forall sofar, next :: e'.Invariant(sofar, next) <==> ( && exists wrappedSofar :: && sofar == Seq.Map(f, wrappedSofar) && match next - case Some(r) => exists t :: r == f(t) && e.inv(wrappedSofar, Some(t)) - case None => e.inv(wrappedSofar, None))) + case Some(r) => exists t :: r == f(t) && e.Invariant(wrappedSofar, Some(t)) + case None => e.Invariant(wrappedSofar, None)) { e' := new MappingEnumerator(f, e); } From 49de185d49ded5c41189be6d57f35ed71deef83b Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sun, 14 May 2023 08:39:49 -0700 Subject: [PATCH 43/68] Generalizing to Actions --- src/Actions/Actions.dfy | 199 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/Actions/Actions.dfy diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy new file mode 100644 index 00000000..6a5a22c4 --- /dev/null +++ b/src/Actions/Actions.dfy @@ -0,0 +1,199 @@ + +include "../Wrappers.dfy" +include "../Frames.dfy" +include "../Math.dfy" +include "../Collections/Sequences/Seq.dfy" + +module Actions { + + import opened Wrappers + import opened Frames + import opened Seq + import opened Math + + trait Action extends Validatable { + + ghost var consumed: seq + ghost var produced: seq + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + + + ghost predicate CanConsume(consumed: seq, produced: seq, nextIn: T) + requires CanProduce(consumed, produced) + + ghost predicate CanProduce(consumed: seq, produced: seq) + + ghost method Update(t: T, r: R) + modifies `consumed, `produced + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + consumed := consumed + [t]; + produced := produced + [r]; + } + + method Invoke(t: T) returns (r: R) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + ensures Valid() + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + } + + ghost predicate OnlyProduces(i: Action, c: R) { + forall consumed: seq, toProduce: seq :: + i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c + } + + ghost predicate TerminatedBy(s: seq, c: T) { + forall i, j | 0 <= i < j < |s| :: s[i] == c ==> s[j] == c + } + + ghost predicate ProducesTerminatedBy(i: Action, c: R) { + forall consumed: seq, toProduce: seq :: + i.CanProduce(consumed, toProduce) ==> TerminatedBy(toProduce, c) + } + + type IEnumerator = Action<(), T> + + class SeqIEnumerator extends Action<(), T> { + + const elements: seq + var index: nat + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + { + && this in Repr + && 0 <= index <= |elements| + && |consumed| == index + && produced == elements[0..index] + } + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + ensures produced == [] + ensures elements == s + { + elements := s; + index := 0; + + consumed := []; + produced := []; + Repr := {this}; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) { + |consumed| + 1 <= |elements| + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq) { + |consumed| <= |elements| && produced == elements[..|consumed|] + } + + method Invoke(t: ()) returns (r: T) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + ensures Valid() + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + r := elements[index]; + index := index + 1; + + Update((), r); + } + } + + // TODO: Not strong enough, could still be infinite + // TODO: How to provide decreases clause for loops etc? + type Enumerator = a: Action<(), Option> | ProducesTerminatedBy(a, None) witness * + + function Enumerated(produced: seq>): seq { + if |produced| == 0 || produced[0].None? then + [] + else + [produced[0].value] + Enumerated(produced[1..]) + } + + ghost function ProducedForEnumerator(s: seq, n: nat): seq> { + var before := Math.Min(|s|, n); + var after := Math.Max(|s|, n); + Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) + } + + ghost predicate EnumeratesAtMost(e: Enumerator, n: nat) { + forall consumed: seq<()>, toProduce: seq> :: + e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n + } + + type IAggregator = Action + type Aggregator = a: Action | ProducesTerminatedBy(a, false) witness * + + // class SeqEnumerator extends Action<(), Option> { + + // const elements: seq + // var index: nat + + // ghost predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // ensures Valid() ==> + // && CanProduce(consumed, produced) + // decreases Repr, 0 + // { + // && this in Repr + // && 0 <= index <= |elements| + // && Enumerated(produced) == elements[0..index] + // } + + // constructor(s: seq) + // ensures Valid() + // ensures fresh(Repr - {this}) + // ensures produced == [] + // ensures elements == s + // { + // elements := s; + // index := 0; + + // consumed := []; + // produced := []; + // Repr := {this}; + // } + + // ghost predicate CanConsume(toConsume: seq<()>, produced: seq>, next: ()) { + // true + // } + // ghost predicate CanProduce(consumed: seq<()>, toProduce: seq>) { + // toProduce == ProducedForEnumerator(elements, |consumed|) + // } + + // method Invoke(t: ()) returns (r: Option) + // requires Valid() + // requires CanConsume(consumed, produced, t) + // ensures Valid() + // ensures consumed == old(consumed) + [t] + // ensures produced == old(produced) + [r] + // ensures CanProduce(consumed, produced) + // { + + // } + // } + + + +} \ No newline at end of file From a32f73b765414b402fd666d7941fd34e7e8e0aa0 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 15 May 2023 14:52:50 -0700 Subject: [PATCH 44/68] Temporary Repr restriction --- src/Actions/Actions.dfy | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 6a5a22c4..0954b567 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -22,7 +22,6 @@ module Actions { ensures Valid() ==> && CanProduce(consumed, produced) decreases Repr, 0 - ghost predicate CanConsume(consumed: seq, produced: seq, nextIn: T) requires CanProduce(consumed, produced) @@ -43,6 +42,10 @@ module Actions { requires CanConsume(consumed, produced, t) modifies Repr ensures Valid() + // TODO: Might be too limiting, could have a similar predicate for valid Repr changes. + // At least this is necessary most of the time if you want to invoke an action multiple times, + // But won't be true in general (e.g. an aggregator that has to allocate more storage). + ensures Repr <= old(Repr) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) @@ -108,6 +111,7 @@ module Actions { requires CanConsume(consumed, produced, t) modifies Repr ensures Valid() + ensures Repr <= old(Repr) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) @@ -119,7 +123,21 @@ module Actions { } } - // TODO: Not strong enough, could still be infinite + method Main() { + var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); + var x := e.Invoke(()); + assert e.produced == [1]; + x := e.Invoke(()); + assert e.produced == [1, 2]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4, 5]; + } + + // TODO: Not strong enough, could still be infinite. exists n | terminated beyond n? // TODO: How to provide decreases clause for loops etc? type Enumerator = a: Action<(), Option> | ProducesTerminatedBy(a, None) witness * From 377de506dc1ce5c355331cd1e6eec1a9d6789403 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 16 May 2023 16:22:53 -0700 Subject: [PATCH 45/68] Sweet solution for enumerator termination measure --- src/Actions/Actions.dfy | 327 +++++++++++++++++++++++++++++++++++----- 1 file changed, 290 insertions(+), 37 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 0954b567..78aa99e1 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -23,7 +23,7 @@ module Actions { && CanProduce(consumed, produced) decreases Repr, 0 - ghost predicate CanConsume(consumed: seq, produced: seq, nextIn: T) + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) requires CanProduce(consumed, produced) ghost predicate CanProduce(consumed: seq, produced: seq) @@ -41,16 +41,19 @@ module Actions { requires Valid() requires CanConsume(consumed, produced, t) modifies Repr + decreases Repr ensures Valid() - // TODO: Might be too limiting, could have a similar predicate for valid Repr changes. - // At least this is necessary most of the time if you want to invoke an action multiple times, - // But won't be true in general (e.g. an aggregator that has to allocate more storage). + // TODO: Will cause trouble when composing, see FilteredEnumerator in Enumerators.dfy + // ensures fresh(Repr - old(Repr)) + // TODO: Not compatible with all actions ensures Repr <= old(Repr) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) } + // Common action invariants + ghost predicate OnlyProduces(i: Action, c: R) { forall consumed: seq, toProduce: seq :: i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c @@ -60,11 +63,64 @@ module Actions { forall i, j | 0 <= i < j < |s| :: s[i] == c ==> s[j] == c } - ghost predicate ProducesTerminatedBy(i: Action, c: R) { + ghost predicate TerminatedByBeyond(s: seq, c: T, n: nat) { + forall i | n <= i < |s| :: s[i] == c + } + + function Enumerated(produced: seq>): seq { + if |produced| == 0 || produced[0].None? then + [] + else + [produced[0].value] + Enumerated(produced[1..]) + } + + lemma TerminatedByBoundsEnumerated(s: seq>, n: nat) + requires TerminatedByBeyond(s, None, n) + ensures |Enumerated(s)| <= n + { + if |s| == 0 || s[0].None? { + } else { + TerminatedByBoundsEnumerated(s[1..], n - 1); + } + } + + ghost function ProducedForEnumerator(s: seq, n: nat): seq> { + var before := Math.Min(|s|, n); + var after := Math.Max(|s|, n); + Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) + } + + ghost predicate EnumeratesAtMost(e: Action<(), Option>, n: nat) { + forall consumed: seq<()>, toProduce: seq> :: + e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n + } + + + + ghost predicate ProducesTerminatedBy(i: Action, c: R, n: nat) { forall consumed: seq, toProduce: seq :: - i.CanProduce(consumed, toProduce) ==> TerminatedBy(toProduce, c) + i.CanProduce(consumed, toProduce) ==> TerminatedByBeyond(toProduce, c, n) + } + + ghost predicate ConsumesAnything(a: Action<(), Option>) { + forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) } + ghost predicate IsEnumerator(a: Action<(), Option>) { + ConsumesAnything(a) && exists n :: ProducesTerminatedBy(a, None, n) + } + + ghost function EnumerationDecreases(a: Action<(), Option>): nat + requires a.Valid() + reads a.Repr + requires IsEnumerator(a) + { + var limit: nat :| ProducesTerminatedBy(a, None, limit); + TerminatedByBoundsEnumerated(a.produced, limit); + limit - |Enumerated(a.produced)| + } + + // Potentially infinite enumerator type IEnumerator = Action<(), T> class SeqIEnumerator extends Action<(), T> { @@ -110,6 +166,7 @@ module Actions { requires Valid() requires CanConsume(consumed, produced, t) modifies Repr + decreases Repr ensures Valid() ensures Repr <= old(Repr) ensures consumed == old(consumed) + [t] @@ -123,44 +180,219 @@ module Actions { } } - method Main() { - var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); - var x := e.Invoke(()); - assert e.produced == [1]; - x := e.Invoke(()); - assert e.produced == [1, 2]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4, 5]; - } + class Filter extends Action<(), Option> { - // TODO: Not strong enough, could still be infinite. exists n | terminated beyond n? - // TODO: How to provide decreases clause for loops etc? - type Enumerator = a: Action<(), Option> | ProducesTerminatedBy(a, None) witness * + const wrapped: Action<(), Option> + + // TODO: Can we support --> or ~>? + const filter: T -> bool - function Enumerated(produced: seq>): seq { - if |produced| == 0 || produced[0].None? then - [] - else - [produced[0].value] + Enumerated(produced[1..]) - } + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + { + && this in Repr + && ValidComponent(wrapped) + && IsEnumerator(wrapped) + && CanProduce(consumed, produced) + } - ghost function ProducedForEnumerator(s: seq, n: nat): seq> { - var before := Math.Min(|s|, n); - var after := Math.Max(|s|, n); - Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) - } + constructor(filter: T -> bool, wrapped: Action<(), Option>) + requires wrapped.Valid() + requires IsEnumerator(wrapped) + requires wrapped.consumed == [] && wrapped.produced == [] + ensures Valid() + { + this.filter := filter; + this.wrapped := wrapped; - ghost predicate EnumeratesAtMost(e: Enumerator, n: nat) { - forall consumed: seq<()>, toProduce: seq> :: - e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n + consumed := []; + produced := []; + Repr := {this} + wrapped.Repr; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) { + exists producedByWrapped: seq> :: + && wrapped.CanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); + r := wrapped.Invoke(()); + while r.Some? && !filter(r.value) + invariant Valid() + invariant Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) + decreases EnumerationDecreases(wrapped) + { + r := wrapped.Invoke(()); + } + + Update(t, r); + } } + // class Compose extends Action { + + // const first: Action + // const second: Action + + // ghost predicate Valid() + // reads this, Repr + // ensures Valid() ==> this in Repr + // ensures Valid() ==> + // && CanProduce(consumed, produced) + // decreases Repr, 0 + // { + // && this in Repr + // && ValidComponent(first) + // && ValidComponent(second) + // && CanProduce(consumed, produced) + // } + + // constructor(second: Action, first: Action) + // requires first.Valid() + // requires second.Valid() + // requires first.Repr !! second.Repr + // requires first.CanProduce([], []) + // requires second.CanProduce([], []) + // ensures Valid() + // ensures produced == [] + // { + // this.first := first; + // this.second := second; + + // consumed := []; + // produced := []; + // Repr := {this} + first.Repr + second.Repr; + // } + + // ghost predicate CanConsume(consumed: seq, produced: seq, next: T) { + // exists vs: seq :: + // && first.CanProduce(consumed, vs) + // && first.CanConsume(consumed, vs, next) + // } + // ghost predicate CanProduce(consumed: seq, produced: seq) { + // exists vs: seq :: first.CanProduce(consumed, vs) && second.CanProduce(vs, produced) + // } + + // method Invoke(t: T) returns (r: R) + // requires Valid() + // requires CanConsume(consumed, produced, t) + // modifies Repr + // decreases Repr + // ensures Valid() + // ensures Repr <= old(Repr) + // ensures consumed == old(consumed) + [t] + // ensures produced == old(produced) + [r] + // ensures CanProduce(consumed, produced) + // { + // var v := first.Invoke(t); + // r := second.Invoke(v); + + // Update(t, r); + // } + + // } + type IAggregator = Action - type Aggregator = a: Action | ProducesTerminatedBy(a, false) witness * + type Aggregator = a: Action | exists n :: ProducesTerminatedBy(a, false, n) witness * + + class ArrayAggregator extends Action { + + var storage: array + var index: nat + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + { + && this in Repr + && storage in Repr + && 0 < storage.Length + && 0 <= index <= storage.Length + && consumed == storage[..index] + && produced == Seq.Repeat((), |consumed|) + } + + constructor() + ensures Valid() + ensures fresh(Repr - {this}) + ensures produced == [] + { + index := 0; + storage := new T[10]; + + consumed := []; + produced := []; + Repr := {this, storage}; + } + + ghost predicate CanConsume(consumed: seq, produced: seq<()>, next: T) { + true + } + ghost predicate CanProduce(consumed: seq, produced: seq<()>) { + produced == Seq.Repeat((), |consumed|) + } + + twostate predicate ValidReprChange(before: set, after: set) { + fresh(after - before) + } + + method Invoke(t: T) returns (r: ()) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases Repr + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + if index == storage.Length { + var newStorage := new T[storage.Length * 2]; + forall i | 0 <= i < index { + newStorage[i] := storage[i]; + } + storage := newStorage; + + Repr := {this, storage}; + } + storage[index] := t; + index := index + 1; + + r := (); + Update(t, r); + } + + function Values(): seq + requires Valid() + reads Repr + ensures Values() == consumed + { + storage[..index] + } + } // class SeqEnumerator extends Action<(), Option> { @@ -214,4 +446,25 @@ module Actions { + method Main() { + var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); + var x := e.Invoke(()); + assert e.produced == [1]; + x := e.Invoke(()); + assert e.produced == [1, 2]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4, 5]; + + var a := new ArrayAggregator(); + var _ := a.Invoke(1); + var _ := a.Invoke(2); + var _ := a.Invoke(3); + var _ := a.Invoke(4); + var _ := a.Invoke(5); + assert a.Values() == [1, 2, 3, 4, 5]; + } } \ No newline at end of file From e38755c579ecb8c3881e4c7f9589723a9a55429a Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 16 May 2023 22:06:08 -0700 Subject: [PATCH 46/68] SeqEnumerator --- src/Actions/Actions.dfy | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 78aa99e1..cad8a1a4 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -123,6 +123,78 @@ module Actions { // Potentially infinite enumerator type IEnumerator = Action<(), T> + class SeqEnumerator extends Action<(), Option> { + + const elements: seq + // TODO: size_t? It's a hell of a lot easier to prove correct + // if this can increase unbounded to stay at |consumed|, + // but it's not ghost so we care about bounding it. + var index: nat + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + { + && this in Repr + && index == |consumed| + && CanProduce(consumed, produced) + } + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + ensures produced == [] + ensures elements == s + { + elements := s; + index := 0; + + consumed := []; + produced := []; + Repr := {this}; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) { + var index := |consumed|; + var values := Math.Min(index, |elements|); + var nones := index - values; + produced == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + if index < |elements| { + r := Some(elements[index]); + } else { + r := None; + } + + index := index + 1; + Update((), r); + } + } + + lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) + ensures IsEnumerator(e) + { + assert ProducesTerminatedBy(e, None, |e.elements|); + } + class SeqIEnumerator extends Action<(), T> { const elements: seq @@ -459,6 +531,7 @@ module Actions { x := e.Invoke(()); assert e.produced == [1, 2, 3, 4, 5]; + var a := new ArrayAggregator(); var _ := a.Invoke(1); var _ := a.Invoke(2); From 5f6e167764c2749d7cdc4813fefcc7017a86160f Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 22 May 2023 14:31:49 -0700 Subject: [PATCH 47/68] Use height instead of Repr for termination, split up more files --- src/Actions/Actions.dfy | 306 ++--------------------------- src/Actions/Enumerators.dfy | 290 +++++++++++++++++++++++++++ src/Actions/FilteredEnumerator.dfy | 112 +++++++++++ src/Frames.dfy | 4 +- 4 files changed, 426 insertions(+), 286 deletions(-) create mode 100644 src/Actions/Enumerators.dfy create mode 100644 src/Actions/FilteredEnumerator.dfy diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index cad8a1a4..1b4dd3ca 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -11,7 +11,7 @@ module Actions { import opened Seq import opened Math - trait Action extends Validatable { + trait {:termination false} Action extends Validatable { ghost var consumed: seq ghost var produced: seq @@ -21,12 +21,14 @@ module Actions { ensures Valid() ==> this in Repr ensures Valid() ==> && CanProduce(consumed, produced) - decreases Repr, 0 - + decreases height, 0 + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) requires CanProduce(consumed, produced) + decreases height ghost predicate CanProduce(consumed: seq, produced: seq) + decreases height ghost method Update(t: T, r: R) modifies `consumed, `produced @@ -41,12 +43,9 @@ module Actions { requires Valid() requires CanConsume(consumed, produced, t) modifies Repr - decreases Repr + decreases height ensures Valid() - // TODO: Will cause trouble when composing, see FilteredEnumerator in Enumerators.dfy - // ensures fresh(Repr - old(Repr)) - // TODO: Not compatible with all actions - ensures Repr <= old(Repr) + ensures fresh(Repr - old(Repr)) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) @@ -59,265 +58,13 @@ module Actions { i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c } - ghost predicate TerminatedBy(s: seq, c: T) { - forall i, j | 0 <= i < j < |s| :: s[i] == c ==> s[j] == c - } - - ghost predicate TerminatedByBeyond(s: seq, c: T, n: nat) { - forall i | n <= i < |s| :: s[i] == c - } - - function Enumerated(produced: seq>): seq { - if |produced| == 0 || produced[0].None? then - [] - else - [produced[0].value] + Enumerated(produced[1..]) - } - - lemma TerminatedByBoundsEnumerated(s: seq>, n: nat) - requires TerminatedByBeyond(s, None, n) - ensures |Enumerated(s)| <= n - { - if |s| == 0 || s[0].None? { - } else { - TerminatedByBoundsEnumerated(s[1..], n - 1); - } - } - - ghost function ProducedForEnumerator(s: seq, n: nat): seq> { - var before := Math.Min(|s|, n); - var after := Math.Max(|s|, n); - Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) - } - - ghost predicate EnumeratesAtMost(e: Action<(), Option>, n: nat) { - forall consumed: seq<()>, toProduce: seq> :: - e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n - } - - - - ghost predicate ProducesTerminatedBy(i: Action, c: R, n: nat) { - forall consumed: seq, toProduce: seq :: - i.CanProduce(consumed, toProduce) ==> TerminatedByBeyond(toProduce, c, n) - } - - ghost predicate ConsumesAnything(a: Action<(), Option>) { - forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) - } - - ghost predicate IsEnumerator(a: Action<(), Option>) { - ConsumesAnything(a) && exists n :: ProducesTerminatedBy(a, None, n) - } - - ghost function EnumerationDecreases(a: Action<(), Option>): nat - requires a.Valid() - reads a.Repr - requires IsEnumerator(a) - { - var limit: nat :| ProducesTerminatedBy(a, None, limit); - TerminatedByBoundsEnumerated(a.produced, limit); - limit - |Enumerated(a.produced)| - } - - // Potentially infinite enumerator - type IEnumerator = Action<(), T> - - class SeqEnumerator extends Action<(), Option> { - - const elements: seq - // TODO: size_t? It's a hell of a lot easier to prove correct - // if this can increase unbounded to stay at |consumed|, - // but it's not ghost so we care about bounding it. - var index: nat - - ghost predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - ensures Valid() ==> - && CanProduce(consumed, produced) - decreases Repr, 0 - { - && this in Repr - && index == |consumed| - && CanProduce(consumed, produced) - } - - constructor(s: seq) - ensures Valid() - ensures fresh(Repr - {this}) - ensures produced == [] - ensures elements == s - { - elements := s; - index := 0; - - consumed := []; - produced := []; - Repr := {this}; - } - - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) { - true - } - ghost predicate CanProduce(consumed: seq<()>, produced: seq>) { - var index := |consumed|; - var values := Math.Min(index, |elements|); - var nones := index - values; - produced == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) - } - - method Invoke(t: ()) returns (r: Option) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) - { - if index < |elements| { - r := Some(elements[index]); - } else { - r := None; - } - - index := index + 1; - Update((), r); - } + ghost predicate Terminated(s: seq, c: T, n: nat) { + forall i | 0 <= i < |s| :: n <= i <==> s[i] == c } - lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) - ensures IsEnumerator(e) - { - assert ProducesTerminatedBy(e, None, |e.elements|); - } - - class SeqIEnumerator extends Action<(), T> { - - const elements: seq - var index: nat - - ghost predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - ensures Valid() ==> - && CanProduce(consumed, produced) - decreases Repr, 0 - { - && this in Repr - && 0 <= index <= |elements| - && |consumed| == index - && produced == elements[0..index] - } - - constructor(s: seq) - ensures Valid() - ensures fresh(Repr - {this}) - ensures produced == [] - ensures elements == s - { - elements := s; - index := 0; - - consumed := []; - produced := []; - Repr := {this}; - } - - ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) { - |consumed| + 1 <= |elements| - } - ghost predicate CanProduce(consumed: seq<()>, produced: seq) { - |consumed| <= |elements| && produced == elements[..|consumed|] - } - - method Invoke(t: ()) returns (r: T) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) - { - r := elements[index]; - index := index + 1; - - Update((), r); - } - } - - class Filter extends Action<(), Option> { - - const wrapped: Action<(), Option> - - // TODO: Can we support --> or ~>? - const filter: T -> bool - - ghost predicate Valid() - reads this, Repr - ensures Valid() ==> this in Repr - ensures Valid() ==> - && CanProduce(consumed, produced) - decreases Repr, 0 - { - && this in Repr - && ValidComponent(wrapped) - && IsEnumerator(wrapped) - && CanProduce(consumed, produced) - } - - constructor(filter: T -> bool, wrapped: Action<(), Option>) - requires wrapped.Valid() - requires IsEnumerator(wrapped) - requires wrapped.consumed == [] && wrapped.produced == [] - ensures Valid() - { - this.filter := filter; - this.wrapped := wrapped; - - consumed := []; - produced := []; - Repr := {this} + wrapped.Repr; - } - - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) { - true - } - ghost predicate CanProduce(consumed: seq<()>, produced: seq>) { - exists producedByWrapped: seq> :: - && wrapped.CanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) - && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) - } - - method Invoke(t: ()) returns (r: Option) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases Repr - ensures Valid() - ensures Repr <= old(Repr) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) - { - assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); - r := wrapped.Invoke(()); - while r.Some? && !filter(r.value) - invariant Valid() - invariant Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) - decreases EnumerationDecreases(wrapped) - { - r := wrapped.Invoke(()); - } - - Update(t, r); - } + ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { + forall consumed: seq, produced: seq :: + i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) } // class Compose extends Action { @@ -396,7 +143,7 @@ module Actions { ensures Valid() ==> this in Repr ensures Valid() ==> && CanProduce(consumed, produced) - decreases Repr, 0 + decreases height, 0 { && this in Repr && storage in Repr @@ -419,10 +166,14 @@ module Actions { Repr := {this, storage}; } - ghost predicate CanConsume(consumed: seq, produced: seq<()>, next: T) { + ghost predicate CanConsume(consumed: seq, produced: seq<()>, next: T) + decreases height + { true } - ghost predicate CanProduce(consumed: seq, produced: seq<()>) { + ghost predicate CanProduce(consumed: seq, produced: seq<()>) + decreases height + { produced == Seq.Repeat((), |consumed|) } @@ -434,7 +185,7 @@ module Actions { requires Valid() requires CanConsume(consumed, produced, t) modifies Repr - decreases Repr + decreases height ensures Valid() ensures fresh(Repr - old(Repr)) ensures consumed == old(consumed) + [t] @@ -516,22 +267,7 @@ module Actions { // } // } - - - method Main() { - var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); - var x := e.Invoke(()); - assert e.produced == [1]; - x := e.Invoke(()); - assert e.produced == [1, 2]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4]; - x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4, 5]; - - + method AggregatorExample() { var a := new ArrayAggregator(); var _ := a.Invoke(1); var _ := a.Invoke(2); diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy new file mode 100644 index 00000000..35eab3d0 --- /dev/null +++ b/src/Actions/Enumerators.dfy @@ -0,0 +1,290 @@ + +include "Actions.dfy" + +module Enumerators { + + import opened Actions + import opened Wrappers + import opened Seq + import opened Math + + function Enumerated(produced: seq>): seq { + if |produced| == 0 || produced[0].None? then + [] + else + [produced[0].value] + Enumerated(produced[1..]) + } + + lemma TerminatedLimitBoundsEnumerated(s: seq>, limit: nat) + requires exists n: nat | n <= limit :: Terminated(s, None, n) + ensures |Enumerated(s)| <= limit + { + if |s| == 0 || s[0].None? { + } else { + var n: nat :| n <= limit && Terminated(s, None, n); + assert Terminated(s[1..], None, n - 1); + TerminatedLimitBoundsEnumerated(s[1..], limit - 1); + } + } + + lemma TerminatedBoundsEnumerated(s: seq>, n: nat) + requires Terminated(s, None, n) + ensures Math.Min(|s|, n) <= |Enumerated(s)| + { + if n == 0 || |s| == 0 { + } else { + assert Terminated(s, None, n); + assert s[0] != None; + assert Terminated(s[1..], None, n - 1); + TerminatedLimitBoundsEnumerated(s[1..], n - 1); + } + } + + lemma TerminatedDistributesOverConcat(left: seq, right: seq, c: T, n: nat) + requires Terminated(left, c, |left|) + requires Terminated(right, c, n) + ensures Terminated(left + right, c, |left| + n) + {} + + ghost function ProducedForEnumerator(s: seq, n: nat): seq> { + var before := Math.Min(|s|, n); + var after := Math.Max(|s|, n); + Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) + } + + ghost predicate EnumeratesAtMost(e: Action<(), Option>, n: nat) { + forall consumed: seq<()>, toProduce: seq> :: + e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n + } + + ghost predicate ConsumesAnything(a: Action<(), Option>) { + forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) + } + + ghost predicate IsEnumerator(a: Action<(), Option>) { + ConsumesAnything(a) && exists n :: ProducesTerminatedBy(a, None, n) + } + + ghost function EnumerationLimit(a: Action<(), Option>): nat + requires IsEnumerator(a) + { + var limit: nat :| ProducesTerminatedBy(a, None, limit); + limit + } + + ghost function EnumerationTerminationMetric(a: Action<(), Option>): nat + requires a.Valid() + reads a.Repr + requires IsEnumerator(a) + { + var limit := EnumerationLimit(a); + TerminatedLimitBoundsEnumerated(a.produced, limit); + limit - |Enumerated(a.produced)| + } + + twostate lemma EnumerationTerminationMetricDecreased(a: Action<(), Option>, nextProduced: Option) + requires old(a.Valid()) + requires old(a.CanProduce(a.consumed, a.produced)) + requires a.Valid() + requires a.CanProduce(a.consumed, a.produced) + requires IsEnumerator(a) + requires a.produced == old(a.produced) + [nextProduced]; + requires nextProduced.Some? + ensures EnumerationTerminationMetric(a) < old(EnumerationTerminationMetric(a)) + { + var before := old(a.produced); + var n: nat :| n <= |before| && Terminated(before, None, n); + var m: nat :| Terminated(a.produced, None, m); + if n < |before| { + assert before[|before| - 1] == None; + assert a.produced[|a.produced| - 1] != None; + assert |a.produced| <= m; + assert a.produced[|before| - 1] != None; + assert false; + } + assert |before| <= n; + TerminatedLimitBoundsEnumerated(before, n); + assert |Enumerated(before)| <= n; + TerminatedDistributesOverConcat(before, [nextProduced], None, 1); + assert Terminated(a.produced, None, |a.produced|); + TerminatedBoundsEnumerated(a.produced, |a.produced|); + assert |Enumerated(a.produced)| >= |a.produced|; + } + + // Potentially infinite enumerator + type IEnumerator = Action<(), T> + + class SeqEnumerator extends Action<(), Option> { + + const elements: seq + // TODO: size_t? It's a hell of a lot easier to prove correct + // if this can increase unbounded to stay at |consumed|, + // but it's not ghost so we care about bounding it. + var index: nat + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && index == |consumed| + && CanProduce(consumed, produced) + } + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + ensures produced == [] + ensures elements == s + { + elements := s; + index := 0; + + consumed := []; + produced := []; + Repr := {this}; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + decreases height + { + var index := |consumed|; + var values := Math.Min(index, |elements|); + var nones := index - values; + produced == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures Repr <= old(Repr) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + if index < |elements| { + r := Some(elements[index]); + } else { + r := None; + } + + index := index + 1; + Update((), r); + } + } + + lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) + ensures IsEnumerator(e) + { + assert ProducesTerminatedBy(e, None, |e.elements|); + } + + class SeqIEnumerator extends Action<(), T> { + + const elements: seq + var index: nat + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && 0 <= index <= |elements| + && |consumed| == index + && produced == elements[0..index] + } + + constructor(s: seq) + ensures Valid() + ensures fresh(Repr - {this}) + ensures produced == [] + ensures elements == s + { + elements := s; + index := 0; + + consumed := []; + produced := []; + Repr := {this}; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) + decreases height + { + |consumed| + 1 <= |elements| + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq) + decreases height + { + |consumed| <= |elements| && produced == elements[..|consumed|] + } + + method Invoke(t: ()) returns (r: T) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures Repr <= old(Repr) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + r := elements[index]; + index := index + 1; + + Update((), r); + } + } + + method EnumeratorExample() { + var e2: SeqEnumerator := new SeqEnumerator([1, 2, 3, 4, 5]); + SeqEnumeratorIsEnumerator(e2); + + // Could be `while next :- e2.Invoke(())` instead :) + while true + invariant e2.Valid() + invariant fresh(e2.Repr) + decreases EnumerationTerminationMetric(e2) + { + label beforeLoop: + var next: Option := e2.Invoke(()); + if next.None? { break; } + EnumerationTerminationMetricDecreased@beforeLoop(e2, next); + + print next.value; + } + } + + method IEnumeratorExample() { + var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); + var x := e.Invoke(()); + assert e.produced == [1]; + x := e.Invoke(()); + assert e.produced == [1, 2]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4]; + x := e.Invoke(()); + assert e.produced == [1, 2, 3, 4, 5]; + } + + method Main() { + EnumeratorExample(); + } +} \ No newline at end of file diff --git a/src/Actions/FilteredEnumerator.dfy b/src/Actions/FilteredEnumerator.dfy new file mode 100644 index 00000000..5f8cf565 --- /dev/null +++ b/src/Actions/FilteredEnumerator.dfy @@ -0,0 +1,112 @@ + +include "Enumerators.dfy" + +module Filtered { + + import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + + class Filter extends Action<(), Option> { + + const wrapped: Action<(), Option> + ghost const wrappedCanProduce: (seq<()>, seq>) -> bool + + // TODO: Can we support --> or ~>? + const filter: T -> bool + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && ValidComponent(wrapped) + && IsEnumerator(wrapped) + && CanProduce(consumed, produced) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) + } + + constructor(filter: T -> bool, wrapped: Action<(), Option>) + requires wrapped.Valid() + requires IsEnumerator(wrapped) + requires wrapped.consumed == [] && wrapped.produced == [] + ensures Valid() + { + this.filter := filter; + this.wrapped := wrapped; + + consumed := []; + produced := []; + Repr := {this} + wrapped.Repr; + height := wrapped.height + 1; + wrappedCanProduce := wrapped.CanProduce; + new; + assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + decreases height + { + exists producedByWrapped: seq> :: + && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) + } + + method {:vcs_split_on_every_assert} Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + while true + invariant Valid() + invariant fresh(wrapped.Repr - old(wrapped.Repr)) + invariant produced == old(produced) + invariant consumed == old(consumed) + invariant Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) + decreases EnumerationTerminationMetric(wrapped) + { + assert IsEnumerator(wrapped); + assert ConsumesAnything(wrapped); + assert wrapped.Valid(); + assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); + label before: + var r' := wrapped.Invoke(()); + + Repr := {this} + wrapped.Repr; + r := r'; + + if r.None? || filter(r.value) { + break; + } + + var wrappedEnumeratedBefore := Enumerated(old@before(wrapped.produced)); + assert wrapped.produced == old@before(wrapped.produced) + [r']; + assert Terminated(old@before(wrapped.produced), None, |old@before(wrapped.produced)|); + TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); + assert Enumerated(wrapped.produced) == wrappedEnumeratedBefore + [r.value]; + LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [r.value]); + assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); + + EnumerationTerminationMetricDecreased@before(wrapped, r); + } + + + Update(t, r); + } + } +} \ No newline at end of file diff --git a/src/Frames.dfy b/src/Frames.dfy index 1b8cd085..53856fe4 100644 --- a/src/Frames.dfy +++ b/src/Frames.dfy @@ -10,11 +10,12 @@ module Frames { // Ghost state tracking the common set of objects most // methods need to read. ghost var Repr: set + ghost const height: nat ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - decreases Repr, 0 + decreases height, 0 // Convenience predicate for when your object's validity depends on one // or more other objects. @@ -25,6 +26,7 @@ module Frames { && component.Repr <= Repr && this !in component.Repr && component.Valid() + && component.height < height } // Convenience predicate, since you often want to assert that From 006a553208909c44e7a39eb94b728dda99272279 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 23 May 2023 10:21:22 -0700 Subject: [PATCH 48/68] Progress --- src/Actions/Enumerators.dfy | 16 ++++++++++++---- src/Actions/FilteredEnumerator.dfy | 2 -- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 35eab3d0..3d4dc95e 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -33,10 +33,11 @@ module Enumerators { { if n == 0 || |s| == 0 { } else { - assert Terminated(s, None, n); - assert s[0] != None; - assert Terminated(s[1..], None, n - 1); - TerminatedLimitBoundsEnumerated(s[1..], n - 1); + if s[0] == None { + } else { + assert 1 <= |Enumerated(s)|; + TerminatedBoundsEnumerated(s[1..], n - 1); + } } } @@ -187,6 +188,13 @@ module Enumerators { lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) ensures IsEnumerator(e) { + assert ConsumesAnything(e); + forall consumed, produced | e.CanProduce(consumed, produced) + { + assert Terminated(produced, None, |e.elements|); + TerminatedLimitBoundsEnumerated(produced, |e.elements|); + assert |Enumerated(produced)| <= |e.elements|; + } assert ProducesTerminatedBy(e, None, |e.elements|); } diff --git a/src/Actions/FilteredEnumerator.dfy b/src/Actions/FilteredEnumerator.dfy index 5f8cf565..187ec1b6 100644 --- a/src/Actions/FilteredEnumerator.dfy +++ b/src/Actions/FilteredEnumerator.dfy @@ -44,8 +44,6 @@ module Filtered { Repr := {this} + wrapped.Repr; height := wrapped.height + 1; wrappedCanProduce := wrapped.CanProduce; - new; - assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); } ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) From f339ada7378ae34847fe1c98bc25ada02bc46f26 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 23 May 2023 14:49:59 -0700 Subject: [PATCH 49/68] Remove unnecessary nested exists --- src/Actions/Actions.dfy | 4 ++-- src/Actions/Enumerators.dfy | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 1b4dd3ca..fd8dc3ed 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -62,9 +62,9 @@ module Actions { forall i | 0 <= i < |s| :: n <= i <==> s[i] == c } - ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { + ghost predicate ProducesTerminatedBy(i: Action, c: R, n: nat) { forall consumed: seq, produced: seq :: - i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) + i.CanProduce(consumed, produced) ==> Terminated(produced, c, n) } // class Compose extends Action { diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 3d4dc95e..9037df33 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -188,13 +188,6 @@ module Enumerators { lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) ensures IsEnumerator(e) { - assert ConsumesAnything(e); - forall consumed, produced | e.CanProduce(consumed, produced) - { - assert Terminated(produced, None, |e.elements|); - TerminatedLimitBoundsEnumerated(produced, |e.elements|); - assert |Enumerated(produced)| <= |e.elements|; - } assert ProducesTerminatedBy(e, None, |e.elements|); } From 8c88b21d71e240d1d76248b775359cd69a695367 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Tue, 23 May 2023 16:53:24 -0700 Subject: [PATCH 50/68] More progress --- src/Actions/Enumerators.dfy | 63 ++++++++++++++++++++++-------- src/Actions/FilteredEnumerator.dfy | 20 +++++----- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 9037df33..3e93ccb0 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -15,21 +15,9 @@ module Enumerators { [produced[0].value] + Enumerated(produced[1..]) } - lemma TerminatedLimitBoundsEnumerated(s: seq>, limit: nat) - requires exists n: nat | n <= limit :: Terminated(s, None, n) - ensures |Enumerated(s)| <= limit - { - if |s| == 0 || s[0].None? { - } else { - var n: nat :| n <= limit && Terminated(s, None, n); - assert Terminated(s[1..], None, n - 1); - TerminatedLimitBoundsEnumerated(s[1..], limit - 1); - } - } - lemma TerminatedBoundsEnumerated(s: seq>, n: nat) requires Terminated(s, None, n) - ensures Math.Min(|s|, n) <= |Enumerated(s)| + ensures Math.Min(|s|, n) <= |Enumerated(s)| <= n { if n == 0 || |s| == 0 { } else { @@ -47,6 +35,26 @@ module Enumerators { ensures Terminated(left + right, c, |left| + n) {} + lemma EnumeratedDistributesOverConcat(left: seq>, right: seq>, n: nat) + requires Terminated(left, None, |left|) + requires Terminated(right, None, n) + ensures Enumerated(left + right) == Enumerated(left) + Enumerated(right) + decreases |left| + |right| + { + TerminatedDistributesOverConcat(left, right, None, n); + if |left| == 0 { + assert left + right == right; + } else if |right| == 0 { + assert left + right == left; + } else { + assert Terminated(left[1..], None, |left[1..]|); + EnumeratedDistributesOverConcat(left[1..], right, n); + assert left == [left[0]] + left[1..]; + EnumeratedDistributesOverConcat([left[0]], left[1..], |left| - 1); + assert ([left[0]] + left[1..]) + right == [left[0]] + (left[1..] + right); + } + } + ghost function ProducedForEnumerator(s: seq, n: nat): seq> { var before := Math.Min(|s|, n); var after := Math.Max(|s|, n); @@ -79,10 +87,33 @@ module Enumerators { requires IsEnumerator(a) { var limit := EnumerationLimit(a); - TerminatedLimitBoundsEnumerated(a.produced, limit); + TerminatedBoundsEnumerated(a.produced, limit); limit - |Enumerated(a.produced)| } + twostate lemma ProducingSomeImpliesTerminated(a: Action<(), Option>, nextProduced: Option) + requires old(a.Valid()) + requires old(a.CanProduce(a.consumed, a.produced)) + requires a.Valid() + requires a.CanProduce(a.consumed, a.produced) + requires IsEnumerator(a) + requires a.produced == old(a.produced) + [nextProduced]; + requires nextProduced.Some? + ensures Terminated(a.produced, None, |a.produced|) + { + var before := old(a.produced); + var n: nat :| n <= |before| && Terminated(before, None, n); + var m: nat :| Terminated(a.produced, None, m); + if n < |before| { + assert before[|before| - 1] == None; + assert a.produced[|a.produced| - 1] != None; + assert |a.produced| <= m; + assert a.produced[|before| - 1] != None; + assert false; + } + assert |before| <= n; + } + twostate lemma EnumerationTerminationMetricDecreased(a: Action<(), Option>, nextProduced: Option) requires old(a.Valid()) requires old(a.CanProduce(a.consumed, a.produced)) @@ -104,12 +135,12 @@ module Enumerators { assert false; } assert |before| <= n; - TerminatedLimitBoundsEnumerated(before, n); + + TerminatedBoundsEnumerated(before, n); assert |Enumerated(before)| <= n; TerminatedDistributesOverConcat(before, [nextProduced], None, 1); assert Terminated(a.produced, None, |a.produced|); TerminatedBoundsEnumerated(a.produced, |a.produced|); - assert |Enumerated(a.produced)| >= |a.produced|; } // Potentially infinite enumerator diff --git a/src/Actions/FilteredEnumerator.dfy b/src/Actions/FilteredEnumerator.dfy index 187ec1b6..4cb306ad 100644 --- a/src/Actions/FilteredEnumerator.dfy +++ b/src/Actions/FilteredEnumerator.dfy @@ -68,10 +68,9 @@ module Filtered { ensures fresh(Repr - old(Repr)) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) { while true - invariant Valid() + invariant wrapped.Valid() invariant fresh(wrapped.Repr - old(wrapped.Repr)) invariant produced == old(produced) invariant consumed == old(consumed) @@ -83,27 +82,30 @@ module Filtered { assert wrapped.Valid(); assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); label before: - var r' := wrapped.Invoke(()); + r := wrapped.Invoke(()); Repr := {this} + wrapped.Repr; - r := r'; if r.None? || filter(r.value) { break; } var wrappedEnumeratedBefore := Enumerated(old@before(wrapped.produced)); - assert wrapped.produced == old@before(wrapped.produced) + [r']; - assert Terminated(old@before(wrapped.produced), None, |old@before(wrapped.produced)|); - TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); - assert Enumerated(wrapped.produced) == wrappedEnumeratedBefore + [r.value]; + assert wrapped.produced == old@before(wrapped.produced) + [r]; + + ProducingSomeImpliesTerminated@before(wrapped, r); + TerminatedBoundsEnumerated(wrapped.produced, |wrapped.produced|); + // TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); + assert |Enumerated(wrapped.produced)| == |wrapped.produced|; + assert wrapped.produced == old@before(wrapped.produced) + [r]; + EnumeratedDistributesOverConcat(old@before(wrapped.produced), [r], 1); + assert Enumerated(wrapped.produced) == Enumerated(old@before(wrapped.produced)) + [r.value]; LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [r.value]); assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); EnumerationTerminationMetricDecreased@before(wrapped, r); } - Update(t, r); } } From 4cae43973b80a2348ee69e8cb66e1704944bd7da Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 27 May 2023 09:05:31 -0700 Subject: [PATCH 51/68] EnumeratedDistributesOverConcat! --- src/Actions/Actions.dfy | 121 ++--------------------------- src/Actions/ComposedAction.dfy | 70 +++++++++++++++++ src/Actions/Enumerators.dfy | 71 +++++++++++++---- src/Actions/FilteredEnumerator.dfy | 49 ++++++++---- src/Actions/MappingEnumerator.dfy | 90 +++++++++++++++++++++ 5 files changed, 254 insertions(+), 147 deletions(-) create mode 100644 src/Actions/ComposedAction.dfy create mode 100644 src/Actions/MappingEnumerator.dfy diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index fd8dc3ed..e8dee27f 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -62,74 +62,19 @@ module Actions { forall i | 0 <= i < |s| :: n <= i <==> s[i] == c } + lemma TerminatedMinConcat(left: seq, right: seq, c: T, n: nat, m: nat) + requires Terminated(left, c, n) + requires Terminated(left + right, c, m) + ensures n < m + { + + } + ghost predicate ProducesTerminatedBy(i: Action, c: R, n: nat) { forall consumed: seq, produced: seq :: i.CanProduce(consumed, produced) ==> Terminated(produced, c, n) } - // class Compose extends Action { - - // const first: Action - // const second: Action - - // ghost predicate Valid() - // reads this, Repr - // ensures Valid() ==> this in Repr - // ensures Valid() ==> - // && CanProduce(consumed, produced) - // decreases Repr, 0 - // { - // && this in Repr - // && ValidComponent(first) - // && ValidComponent(second) - // && CanProduce(consumed, produced) - // } - - // constructor(second: Action, first: Action) - // requires first.Valid() - // requires second.Valid() - // requires first.Repr !! second.Repr - // requires first.CanProduce([], []) - // requires second.CanProduce([], []) - // ensures Valid() - // ensures produced == [] - // { - // this.first := first; - // this.second := second; - - // consumed := []; - // produced := []; - // Repr := {this} + first.Repr + second.Repr; - // } - - // ghost predicate CanConsume(consumed: seq, produced: seq, next: T) { - // exists vs: seq :: - // && first.CanProduce(consumed, vs) - // && first.CanConsume(consumed, vs, next) - // } - // ghost predicate CanProduce(consumed: seq, produced: seq) { - // exists vs: seq :: first.CanProduce(consumed, vs) && second.CanProduce(vs, produced) - // } - - // method Invoke(t: T) returns (r: R) - // requires Valid() - // requires CanConsume(consumed, produced, t) - // modifies Repr - // decreases Repr - // ensures Valid() - // ensures Repr <= old(Repr) - // ensures consumed == old(consumed) + [t] - // ensures produced == old(produced) + [r] - // ensures CanProduce(consumed, produced) - // { - // var v := first.Invoke(t); - // r := second.Invoke(v); - - // Update(t, r); - // } - - // } - type IAggregator = Action type Aggregator = a: Action | exists n :: ProducesTerminatedBy(a, false, n) witness * @@ -217,56 +162,6 @@ module Actions { } } - // class SeqEnumerator extends Action<(), Option> { - - // const elements: seq - // var index: nat - - // ghost predicate Valid() - // reads this, Repr - // ensures Valid() ==> this in Repr - // ensures Valid() ==> - // && CanProduce(consumed, produced) - // decreases Repr, 0 - // { - // && this in Repr - // && 0 <= index <= |elements| - // && Enumerated(produced) == elements[0..index] - // } - - // constructor(s: seq) - // ensures Valid() - // ensures fresh(Repr - {this}) - // ensures produced == [] - // ensures elements == s - // { - // elements := s; - // index := 0; - - // consumed := []; - // produced := []; - // Repr := {this}; - // } - - // ghost predicate CanConsume(toConsume: seq<()>, produced: seq>, next: ()) { - // true - // } - // ghost predicate CanProduce(consumed: seq<()>, toProduce: seq>) { - // toProduce == ProducedForEnumerator(elements, |consumed|) - // } - - // method Invoke(t: ()) returns (r: Option) - // requires Valid() - // requires CanConsume(consumed, produced, t) - // ensures Valid() - // ensures consumed == old(consumed) + [t] - // ensures produced == old(produced) + [r] - // ensures CanProduce(consumed, produced) - // { - - // } - // } - method AggregatorExample() { var a := new ArrayAggregator(); var _ := a.Invoke(1); diff --git a/src/Actions/ComposedAction.dfy b/src/Actions/ComposedAction.dfy new file mode 100644 index 00000000..35e4933c --- /dev/null +++ b/src/Actions/ComposedAction.dfy @@ -0,0 +1,70 @@ + +include "Actions.dfy" + +module Composed { + + import opened Actions + + class Compose extends Action { + + const first: Action + const second: Action + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases Repr, 0 + { + && this in Repr + && ValidComponent(first) + && ValidComponent(second) + && CanProduce(consumed, produced) + } + + constructor(second: Action, first: Action) + requires first.Valid() + requires second.Valid() + requires first.Repr !! second.Repr + requires first.CanProduce([], []) + requires second.CanProduce([], []) + ensures Valid() + ensures produced == [] + { + this.first := first; + this.second := second; + + consumed := []; + produced := []; + Repr := {this} + first.Repr + second.Repr; + } + + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) { + exists vs: seq :: + && first.CanProduce(consumed, vs) + && first.CanConsume(consumed, vs, next) + } + ghost predicate CanProduce(consumed: seq, produced: seq) { + exists vs: seq :: first.CanProduce(consumed, vs) && second.CanProduce(vs, produced) + } + + method Invoke(t: T) returns (r: R) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases Repr + ensures Valid() + ensures Repr <= old(Repr) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + ensures CanProduce(consumed, produced) + { + var v := first.Invoke(t); + r := second.Invoke(v); + + Update(t, r); + } + + } +} \ No newline at end of file diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 3e93ccb0..cbf1c6ca 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -15,16 +15,18 @@ module Enumerators { [produced[0].value] + Enumerated(produced[1..]) } - lemma TerminatedBoundsEnumerated(s: seq>, n: nat) + lemma TerminatedDefinesEnumerated(s: seq>, n: nat) requires Terminated(s, None, n) - ensures Math.Min(|s|, n) <= |Enumerated(s)| <= n + ensures + && var length := Math.Min(|s|, n); + && Enumerated(s) == seq(length, i requires 0 <= i < length => s[i].value) { if n == 0 || |s| == 0 { } else { if s[0] == None { } else { assert 1 <= |Enumerated(s)|; - TerminatedBoundsEnumerated(s[1..], n - 1); + TerminatedDefinesEnumerated(s[1..], n - 1); } } } @@ -36,22 +38,36 @@ module Enumerators { {} lemma EnumeratedDistributesOverConcat(left: seq>, right: seq>, n: nat) - requires Terminated(left, None, |left|) - requires Terminated(right, None, n) - ensures Enumerated(left + right) == Enumerated(left) + Enumerated(right) + requires Terminated(left, None, n) + ensures + if n < |left| then + Enumerated(left + right) == Enumerated(left) + else + Enumerated(left + right) == Enumerated(left) + Enumerated(right) decreases |left| + |right| { - TerminatedDistributesOverConcat(left, right, None, n); if |left| == 0 { assert left + right == right; } else if |right| == 0 { assert left + right == left; } else { - assert Terminated(left[1..], None, |left[1..]|); - EnumeratedDistributesOverConcat(left[1..], right, n); - assert left == [left[0]] + left[1..]; - EnumeratedDistributesOverConcat([left[0]], left[1..], |left| - 1); - assert ([left[0]] + left[1..]) + right == [left[0]] + (left[1..] + right); + if n < |left| { + if n == 0 { + + } else { + assert Terminated(left[1..], None, n - 1); + EnumeratedDistributesOverConcat(left[1..], right, n - 1); + assert left == [left[0]] + left[1..]; + EnumeratedDistributesOverConcat([left[0]], left[1..], 1); + assert ([left[0]] + left[1..]) + right == [left[0]] + (left[1..] + right); + } + } else { + assert Terminated(left[1..], None, |left[1..]|); + EnumeratedDistributesOverConcat(left[1..], right, n); + assert left == [left[0]] + left[1..]; + EnumeratedDistributesOverConcat([left[0]], left[1..], 1); + assert ([left[0]] + left[1..]) + right == [left[0]] + (left[1..] + right); + } } } @@ -87,7 +103,7 @@ module Enumerators { requires IsEnumerator(a) { var limit := EnumerationLimit(a); - TerminatedBoundsEnumerated(a.produced, limit); + TerminatedDefinesEnumerated(a.produced, limit); limit - |Enumerated(a.produced)| } @@ -116,9 +132,7 @@ module Enumerators { twostate lemma EnumerationTerminationMetricDecreased(a: Action<(), Option>, nextProduced: Option) requires old(a.Valid()) - requires old(a.CanProduce(a.consumed, a.produced)) requires a.Valid() - requires a.CanProduce(a.consumed, a.produced) requires IsEnumerator(a) requires a.produced == old(a.produced) + [nextProduced]; requires nextProduced.Some? @@ -136,13 +150,36 @@ module Enumerators { } assert |before| <= n; - TerminatedBoundsEnumerated(before, n); + TerminatedDefinesEnumerated(before, n); assert |Enumerated(before)| <= n; TerminatedDistributesOverConcat(before, [nextProduced], None, 1); assert Terminated(a.produced, None, |a.produced|); - TerminatedBoundsEnumerated(a.produced, |a.produced|); + TerminatedDefinesEnumerated(a.produced, |a.produced|); } + twostate lemma EnumeratedDistributes(a: Action<(), Option>, nextProduced: Option) + requires old(a.Valid()) + requires a.Valid() + requires IsEnumerator(a) + requires a.produced == old(a.produced) + [nextProduced]; + ensures Enumerated(a.produced) == Enumerated(old(a.produced)) + Enumerated([nextProduced]) + { + var before := old(a.produced); + var n: nat :| n <= |before| && Terminated(before, None, n); + EnumeratedDistributesOverConcat(before, [nextProduced], n); + + var m: nat :| Terminated(a.produced, None, m); + if n < |before| { + TerminatedMinConcat(before, [nextProduced], None, n, m); + assert nextProduced == a.produced[|a.produced| - 1]; + assert before[|before| - 1] == None; + assert nextProduced == None; + assert Terminated(a.produced, None, n); + assert Enumerated(a.produced) == Enumerated(old(a.produced)); + } + } + + // Potentially infinite enumerator type IEnumerator = Action<(), T> diff --git a/src/Actions/FilteredEnumerator.dfy b/src/Actions/FilteredEnumerator.dfy index 4cb306ad..ef1d4142 100644 --- a/src/Actions/FilteredEnumerator.dfy +++ b/src/Actions/FilteredEnumerator.dfy @@ -54,9 +54,12 @@ module Filtered { ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { - exists producedByWrapped: seq> :: - && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) - && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) + exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) + } + + ghost predicate ValidWrappedProduced(produced: seq>, producedByWrapped: seq>) { + && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) } method {:vcs_split_on_every_assert} Invoke(t: ()) returns (r: Option) @@ -72,6 +75,7 @@ module Filtered { while true invariant wrapped.Valid() invariant fresh(wrapped.Repr - old(wrapped.Repr)) + invariant ValidWrappedProduced(produced, wrapped.produced) invariant produced == old(produced) invariant consumed == old(consumed) invariant Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) @@ -89,23 +93,34 @@ module Filtered { if r.None? || filter(r.value) { break; } - - var wrappedEnumeratedBefore := Enumerated(old@before(wrapped.produced)); - assert wrapped.produced == old@before(wrapped.produced) + [r]; - - ProducingSomeImpliesTerminated@before(wrapped, r); - TerminatedBoundsEnumerated(wrapped.produced, |wrapped.produced|); - // TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); - assert |Enumerated(wrapped.produced)| == |wrapped.produced|; - assert wrapped.produced == old@before(wrapped.produced) + [r]; - EnumeratedDistributesOverConcat(old@before(wrapped.produced), [r], 1); - assert Enumerated(wrapped.produced) == Enumerated(old@before(wrapped.produced)) + [r.value]; - LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [r.value]); - assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); - EnumerationTerminationMetricDecreased@before(wrapped, r); + + // var wrappedEnumeratedBefore := Enumerated(old@before(wrapped.produced)); + // assert wrapped.produced == old@before(wrapped.produced) + [r]; + + // ProducingSomeImpliesTerminated@before(wrapped, r); + // TerminatedBoundsEnumerated(wrapped.produced, |wrapped.produced|); + // // TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); + // assert |Enumerated(wrapped.produced)| == |wrapped.produced|; + // assert wrapped.produced == old@before(wrapped.produced) + [r]; + // EnumeratedDistributesOverConcat(old@before(wrapped.produced), [r], 1); + // assert Enumerated(wrapped.produced) == Enumerated(old@before(wrapped.produced)) + [r.value]; + + // assert Enumerated(old(produced)) == Seq.Filter(filter, Enumerated(old(wrapped.produced))); + // calc { + // Seq.Filter(filter, Enumerated(wrapped.produced)); + // Seq.Filter(filter, Enumerated(old(wrapped.produced) + [r])); + // Seq.Filter(filter, Enumerated(old(wrapped.produced)) + Enumerated([r])); + // Seq.Filter(filter, Enumerated(old(wrapped.produced)) + [r.value]); + // { LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [r.value]); } + // Seq.Filter(filter, Enumerated(old(wrapped.produced))) + Seq.Filter(filter, [r.value]); + // Seq.Filter(filter, Enumerated(old(wrapped.produced))); + // } + // assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); } + assert r.Some? ==> filter(r.value); + assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); Update(t, r); } } diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy new file mode 100644 index 00000000..38994133 --- /dev/null +++ b/src/Actions/MappingEnumerator.dfy @@ -0,0 +1,90 @@ + +include "Enumerators.dfy" + +module Mapped { + + import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + + class Map extends Action<(), Option> { + + const wrapped: Action<(), Option> + ghost const wrappedCanProduce: (seq<()>, seq>) -> bool + + // TODO: Can we support --> or ~>? + const f: T -> R + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && ValidComponent(wrapped) + && IsEnumerator(wrapped) + && CanProduce(consumed, produced) + && Enumerated(produced) == Seq.Map(f, Enumerated(wrapped.produced)) + } + + constructor(f: T -> R, wrapped: Action<(), Option>) + requires wrapped.Valid() + requires IsEnumerator(wrapped) + requires wrapped.consumed == [] && wrapped.produced == [] + ensures Valid() + { + this.f := f; + this.wrapped := wrapped; + + consumed := []; + produced := []; + Repr := {this} + wrapped.Repr; + height := wrapped.height + 1; + wrappedCanProduce := wrapped.CanProduce; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + decreases height + { + exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) + } + + ghost predicate ValidWrappedProduced(produced: seq>, producedByWrapped: seq>) { + && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && Enumerated(produced) == Seq.Map(f, Enumerated(producedByWrapped)) + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + assert ConsumesAnything(wrapped); + assert wrapped.Valid(); + assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); + var x := wrapped.Invoke(()); + match x { + case Some(v) => r := Some(f(v)); + case None => r := None; + } + Repr := {this} + wrapped.Repr; + Update(t, r); + assert wrapped.produced == old(wrapped.produced) + [x]; + assert Enumerated(wrapped.produced) == Enumerated(old(wrapped.produced)) + Enumerated([x]); + Seq.LemmaMapDistributesOverConcat(f, Enumerated(old(wrapped.produced)), Enumerated([x])); + } + } +} \ No newline at end of file From f321710af965c964a03c8f5605b69d8fc3320386 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 29 May 2023 09:59:53 -0700 Subject: [PATCH 52/68] Partial work to fix definition of IsEnumerator() --- src/Actions/Actions.dfy | 6 +++--- src/Actions/Enumerators.dfy | 8 +++++--- src/Actions/MappingEnumerator.dfy | 26 +++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index e8dee27f..e59f70fb 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -70,13 +70,13 @@ module Actions { } - ghost predicate ProducesTerminatedBy(i: Action, c: R, n: nat) { + ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { forall consumed: seq, produced: seq :: - i.CanProduce(consumed, produced) ==> Terminated(produced, c, n) + i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) } type IAggregator = Action - type Aggregator = a: Action | exists n :: ProducesTerminatedBy(a, false, n) witness * + type Aggregator = a: Action | exists limit :: ProducesTerminatedBy(a, false, limit) witness * class ArrayAggregator extends Action { diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index cbf1c6ca..a90167d1 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -87,11 +87,12 @@ module Enumerators { } ghost predicate IsEnumerator(a: Action<(), Option>) { - ConsumesAnything(a) && exists n :: ProducesTerminatedBy(a, None, n) + ConsumesAnything(a) && exists limit :: ProducesTerminatedBy(a, None, limit) } - ghost function EnumerationLimit(a: Action<(), Option>): nat + ghost function EnumerationLimit(a: Action<(), Option>): (limit: nat) requires IsEnumerator(a) + ensures ProducesTerminatedBy(a, None, limit) { var limit: nat :| ProducesTerminatedBy(a, None, limit); limit @@ -103,7 +104,8 @@ module Enumerators { requires IsEnumerator(a) { var limit := EnumerationLimit(a); - TerminatedDefinesEnumerated(a.produced, limit); + var n :| Terminated(a.produced, None, n); + TerminatedDefinesEnumerated(a.produced, n); limit - |Enumerated(a.produced)| } diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index 38994133..f91ab082 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -44,6 +44,8 @@ module Mapped { Repr := {this} + wrapped.Repr; height := wrapped.height + 1; wrappedCanProduce := wrapped.CanProduce; + new; + assert ValidWrappedProduced(produced, wrapped.produced); } ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) @@ -82,9 +84,31 @@ module Mapped { } Repr := {this} + wrapped.Repr; Update(t, r); - assert wrapped.produced == old(wrapped.produced) + [x]; + EnumeratedDistributes(wrapped, x); assert Enumerated(wrapped.produced) == Enumerated(old(wrapped.produced)) + Enumerated([x]); Seq.LemmaMapDistributesOverConcat(f, Enumerated(old(wrapped.produced)), Enumerated([x])); + assert Enumerated(old(produced)) == Seq.Map(f, Enumerated(old(wrapped.produced))); + ThisIsEnumerator(); + calc { + Enumerated(produced); + { EnumeratedDistributes(this, r); } + Enumerated(old(produced)) + Enumerated([r]); + } + assert ValidWrappedProduced(produced, wrapped.produced); + } + + lemma ThisIsEnumerator() + requires Valid() + ensures IsEnumerator(this) + { + assert IsEnumerator(wrapped); + var limit := EnumerationLimit(wrapped); + forall consumed, produced, next | CanProduce(consumed, produced) { + assert CanConsume(consumed, produced, next); + + var producedByWrapped: seq> :| ValidWrappedProduced(produced, producedByWrapped); + assert |Enumerated(producedByWrapped)| <= limit; + } } } } \ No newline at end of file From bd81878054cd0365f874e05cbd154a8f45e0bbe6 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 29 May 2023 11:04:06 -0700 Subject: [PATCH 53/68] Enumerators.dfy verifying again --- src/Actions/Actions.dfy | 11 ++-------- src/Actions/Enumerators.dfy | 35 ++++++++++++++++--------------- src/Actions/MappingEnumerator.dfy | 2 +- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index e59f70fb..713a5d7c 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -58,16 +58,9 @@ module Actions { i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c } - ghost predicate Terminated(s: seq, c: T, n: nat) { - forall i | 0 <= i < |s| :: n <= i <==> s[i] == c - } - - lemma TerminatedMinConcat(left: seq, right: seq, c: T, n: nat, m: nat) - requires Terminated(left, c, n) - requires Terminated(left + right, c, m) - ensures n < m + ghost predicate Terminated(s: seq, c: T, n: nat) { - + forall i | 0 <= i < |s| :: n <= i <==> s[i] == c } ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index a90167d1..804c9126 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -77,9 +77,9 @@ module Enumerators { Seq.Map(x => Some(x), s[..before]) + Seq.Repeat(None, after - before) } - ghost predicate EnumeratesAtMost(e: Action<(), Option>, n: nat) { - forall consumed: seq<()>, toProduce: seq> :: - e.CanProduce(consumed, toProduce) ==> |Enumerated(toProduce)| <= n + ghost predicate EnumerationBoundedBy(e: Action<(), Option>, limit: nat) { + forall consumed: seq<()>, produced: seq> :: + e.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, None, n) } ghost predicate ConsumesAnything(a: Action<(), Option>) { @@ -87,14 +87,15 @@ module Enumerators { } ghost predicate IsEnumerator(a: Action<(), Option>) { - ConsumesAnything(a) && exists limit :: ProducesTerminatedBy(a, None, limit) + && ConsumesAnything(a) + && exists limit :: EnumerationBoundedBy(a, limit) } - ghost function EnumerationLimit(a: Action<(), Option>): (limit: nat) + ghost function EnumerationBound(a: Action<(), Option>): (limit: nat) requires IsEnumerator(a) - ensures ProducesTerminatedBy(a, None, limit) + ensures EnumerationBoundedBy(a, limit) { - var limit: nat :| ProducesTerminatedBy(a, None, limit); + var limit: nat :| EnumerationBoundedBy(a, limit); limit } @@ -103,8 +104,8 @@ module Enumerators { reads a.Repr requires IsEnumerator(a) { - var limit := EnumerationLimit(a); - var n :| Terminated(a.produced, None, n); + var limit := EnumerationBound(a); + var n: nat :| n <= limit && Terminated(a.produced, None, n); TerminatedDefinesEnumerated(a.produced, n); limit - |Enumerated(a.produced)| } @@ -170,14 +171,10 @@ module Enumerators { var n: nat :| n <= |before| && Terminated(before, None, n); EnumeratedDistributesOverConcat(before, [nextProduced], n); - var m: nat :| Terminated(a.produced, None, m); + var m: nat :| m <= |a.produced| && Terminated(a.produced, None, m); if n < |before| { - TerminatedMinConcat(before, [nextProduced], None, n, m); - assert nextProduced == a.produced[|a.produced| - 1]; - assert before[|before| - 1] == None; - assert nextProduced == None; - assert Terminated(a.produced, None, n); - assert Enumerated(a.produced) == Enumerated(old(a.produced)); + assert a.produced[|before| - 1] == None; + assert a.produced[|a.produced| - 1] == nextProduced; } } @@ -258,7 +255,11 @@ module Enumerators { lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) ensures IsEnumerator(e) { - assert ProducesTerminatedBy(e, None, |e.elements|); + forall consumed, produced | e.CanProduce(consumed, produced) + ensures Terminated(produced, None, |e.elements|) + { + } + assert EnumerationBoundedBy(e, |e.elements|); } class SeqIEnumerator extends Action<(), T> { diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index f91ab082..a8e1c82e 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -102,7 +102,7 @@ module Mapped { ensures IsEnumerator(this) { assert IsEnumerator(wrapped); - var limit := EnumerationLimit(wrapped); + var limit := EnumerationBound(wrapped); forall consumed, produced, next | CanProduce(consumed, produced) { assert CanConsume(consumed, produced, next); From e066a2d578a802d5266730ef282ffa0af583934b Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 29 May 2023 12:41:40 -0700 Subject: [PATCH 54/68] NoOp verifies! --- src/Actions/Enumerators.dfy | 19 ------- src/Actions/NoOpEnumerator.dfy | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 src/Actions/NoOpEnumerator.dfy diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 804c9126..11ee423e 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -160,25 +160,6 @@ module Enumerators { TerminatedDefinesEnumerated(a.produced, |a.produced|); } - twostate lemma EnumeratedDistributes(a: Action<(), Option>, nextProduced: Option) - requires old(a.Valid()) - requires a.Valid() - requires IsEnumerator(a) - requires a.produced == old(a.produced) + [nextProduced]; - ensures Enumerated(a.produced) == Enumerated(old(a.produced)) + Enumerated([nextProduced]) - { - var before := old(a.produced); - var n: nat :| n <= |before| && Terminated(before, None, n); - EnumeratedDistributesOverConcat(before, [nextProduced], n); - - var m: nat :| m <= |a.produced| && Terminated(a.produced, None, m); - if n < |before| { - assert a.produced[|before| - 1] == None; - assert a.produced[|a.produced| - 1] == nextProduced; - } - } - - // Potentially infinite enumerator type IEnumerator = Action<(), T> diff --git a/src/Actions/NoOpEnumerator.dfy b/src/Actions/NoOpEnumerator.dfy new file mode 100644 index 00000000..4b3cdec2 --- /dev/null +++ b/src/Actions/NoOpEnumerator.dfy @@ -0,0 +1,92 @@ + +include "Enumerators.dfy" + +module NoOp { + + import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + + class NoOp extends Action<(), Option> { + + const wrapped: Action<(), Option> + ghost const wrappedCanProduce: (seq<()>, seq>) -> bool + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && ValidComponent(wrapped) + && IsEnumerator(wrapped) + && CanProduce(consumed, produced) + && consumed == wrapped.consumed + && produced == wrapped.produced + && wrappedCanProduce == wrapped.CanProduce + && ValidWrappedProduced(consumed, produced, wrapped.produced) + } + + constructor(wrapped: Action<(), Option>) + requires wrapped.Valid() + requires IsEnumerator(wrapped) + requires wrapped.consumed == [] && wrapped.produced == [] + ensures Valid() + { + this.wrapped := wrapped; + + consumed := []; + produced := []; + Repr := {this} + wrapped.Repr; + height := wrapped.height + 1; + wrappedCanProduce := wrapped.CanProduce; + new; + assert ValidWrappedProduced(consumed, produced, wrapped.produced); + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + decreases height + { + exists producedByWrapped: seq> :: ValidWrappedProduced(consumed, produced, producedByWrapped) + } + + ghost predicate ValidWrappedProduced(consumed: seq<()>, produced: seq>, producedByWrapped: seq>) { + && wrappedCanProduce(consumed, producedByWrapped) + && produced == producedByWrapped + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + r := wrapped.Invoke(()); + + Repr := {this} + wrapped.Repr; + Update(t, r); + + assert ValidWrappedProduced(consumed, produced, wrapped.produced); + } + + lemma ThisIsEnumerator() + requires Valid() + ensures IsEnumerator(this) + { + var limit := EnumerationBound(wrapped); + assert EnumerationBoundedBy(this, limit); + } + } +} \ No newline at end of file From cf6ac72b5a589387a2f2c052f79e76a7ece1a4b7 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 29 May 2023 14:07:21 -0700 Subject: [PATCH 55/68] Mapping enumerator verifies (avoiding Enumerated lemmas for now) --- src/Actions/Actions.dfy | 12 +++++- src/Actions/Enumerators.dfy | 10 +++++ src/Actions/MappingEnumerator.dfy | 63 +++++++++++++++++++------------ src/Actions/NoOpEnumerator.dfy | 1 - 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 713a5d7c..ebe09f7e 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -58,11 +58,19 @@ module Actions { i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c } - ghost predicate Terminated(s: seq, c: T, n: nat) - { + ghost predicate Terminated(s: seq, c: T, n: nat) { forall i | 0 <= i < |s| :: n <= i <==> s[i] == c } + lemma TerminatedUndistributes(left: seq, right: seq, c: T, n: nat) + requires Terminated(left + right, c, n) + ensures Terminated(left, c, n) + ensures Terminated(right, c, Math.Max(0, n - |left|)) + { + assert forall i | 0 <= i < |left| :: left[i] == (left + right)[i]; + assert forall i | 0 <= i < |right| :: right[i] == (left + right)[i + |left|]; + } + ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { forall consumed: seq, produced: seq :: i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 11ee423e..96dfd617 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -71,6 +71,16 @@ module Enumerators { } } + lemma EnumeratedDistributesOverConcat2(left: seq>, right: seq>, n: nat) + requires Terminated(left + right, None, n) + ensures Enumerated(left + right) == Enumerated(left) + Enumerated(right) + { + TerminatedUndistributes(left, right, None, n); + TerminatedDefinesEnumerated(left + right, n); + + } + + ghost function ProducedForEnumerator(s: seq, n: nat): seq> { var before := Math.Min(|s|, n); var after := Math.Max(|s|, n); diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index a8e1c82e..33d7511a 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -27,7 +27,9 @@ module Mapped { && ValidComponent(wrapped) && IsEnumerator(wrapped) && CanProduce(consumed, produced) - && Enumerated(produced) == Seq.Map(f, Enumerated(wrapped.produced)) + && consumed == wrapped.consumed + && produced == Seq.Map(maybeF, wrapped.produced) + && wrappedCanProduce == wrapped.CanProduce } constructor(f: T -> R, wrapped: Action<(), Option>) @@ -35,6 +37,9 @@ module Mapped { requires IsEnumerator(wrapped) requires wrapped.consumed == [] && wrapped.produced == [] ensures Valid() + ensures fresh(Repr - (wrapped.Repr)) + ensures this.wrapped == wrapped; + ensures this.f == f; { this.f := f; this.wrapped := wrapped; @@ -45,7 +50,7 @@ module Mapped { height := wrapped.height + 1; wrappedCanProduce := wrapped.CanProduce; new; - assert ValidWrappedProduced(produced, wrapped.produced); + assert ValidWrappedProduced(consumed, produced, wrapped.produced); } ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) @@ -56,12 +61,12 @@ module Mapped { ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { - exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) + exists producedByWrapped: seq> :: ValidWrappedProduced(consumed, produced, producedByWrapped) } - ghost predicate ValidWrappedProduced(produced: seq>, producedByWrapped: seq>) { - && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) - && Enumerated(produced) == Seq.Map(f, Enumerated(producedByWrapped)) + ghost predicate ValidWrappedProduced(consumed: seq<()>, produced: seq>, producedByWrapped: seq>) { + && wrappedCanProduce(consumed, producedByWrapped) + && produced == Seq.Map(maybeF, producedByWrapped) } method Invoke(t: ()) returns (r: Option) @@ -74,9 +79,7 @@ module Mapped { ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] { - assert ConsumesAnything(wrapped); assert wrapped.Valid(); - assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); var x := wrapped.Invoke(()); match x { case Some(v) => r := Some(f(v)); @@ -84,31 +87,41 @@ module Mapped { } Repr := {this} + wrapped.Repr; Update(t, r); - EnumeratedDistributes(wrapped, x); - assert Enumerated(wrapped.produced) == Enumerated(old(wrapped.produced)) + Enumerated([x]); - Seq.LemmaMapDistributesOverConcat(f, Enumerated(old(wrapped.produced)), Enumerated([x])); - assert Enumerated(old(produced)) == Seq.Map(f, Enumerated(old(wrapped.produced))); - ThisIsEnumerator(); - calc { - Enumerated(produced); - { EnumeratedDistributes(this, r); } - Enumerated(old(produced)) + Enumerated([r]); - } - assert ValidWrappedProduced(produced, wrapped.produced); + + Seq.LemmaMapDistributesOverConcat(maybeF, old(wrapped.produced), [x]); + assert ValidWrappedProduced(consumed, produced, wrapped.produced); + } + + function maybeF(t: Option): Option { + match t + case Some(v) => Some(f(v)) + case None => None } lemma ThisIsEnumerator() requires Valid() ensures IsEnumerator(this) { - assert IsEnumerator(wrapped); var limit := EnumerationBound(wrapped); - forall consumed, produced, next | CanProduce(consumed, produced) { - assert CanConsume(consumed, produced, next); - - var producedByWrapped: seq> :| ValidWrappedProduced(produced, producedByWrapped); - assert |Enumerated(producedByWrapped)| <= limit; + forall consumed, produced | CanProduce(consumed, produced) + ensures exists n: nat | n <= limit :: Terminated(produced, None, n) + { + var producedByWrapped :| ValidWrappedProduced(consumed, produced, producedByWrapped); + var n :| n <= limit && Terminated(producedByWrapped, None, n); + assert Terminated(produced, None, n); } + assert EnumerationBoundedBy(this, limit); } } + + method Example() { + var e: SeqEnumerator := new SeqEnumerator([1, 2, 3, 4, 5]); + SeqEnumeratorIsEnumerator(e); + var f: Map := new Map(x => x * 2, e); + + var x := f.Invoke(()); + assert f.produced == [Some(2)]; + assert [x] == [Some(2)]; + assert x == Some(2); + } } \ No newline at end of file diff --git a/src/Actions/NoOpEnumerator.dfy b/src/Actions/NoOpEnumerator.dfy index 4b3cdec2..ad6d5a1e 100644 --- a/src/Actions/NoOpEnumerator.dfy +++ b/src/Actions/NoOpEnumerator.dfy @@ -27,7 +27,6 @@ module NoOp { && consumed == wrapped.consumed && produced == wrapped.produced && wrappedCanProduce == wrapped.CanProduce - && ValidWrappedProduced(consumed, produced, wrapped.produced) } constructor(wrapped: Action<(), Option>) From 300c012e7bb07d7bcc2e9ce8088758aec1a91b73 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 29 May 2023 17:46:35 -0700 Subject: [PATCH 56/68] Progress on composed action --- src/Actions/ComposedAction.dfy | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Actions/ComposedAction.dfy b/src/Actions/ComposedAction.dfy index 35e4933c..ba4f7bc5 100644 --- a/src/Actions/ComposedAction.dfy +++ b/src/Actions/ComposedAction.dfy @@ -1,11 +1,10 @@ - include "Actions.dfy" module Composed { import opened Actions - class Compose extends Action { + class Compose extends Action { const first: Action const second: Action @@ -15,12 +14,15 @@ module Composed { ensures Valid() ==> this in Repr ensures Valid() ==> && CanProduce(consumed, produced) - decreases Repr, 0 + decreases height, 0 { && this in Repr && ValidComponent(first) && ValidComponent(second) + && first.Repr !! second.Repr && CanProduce(consumed, produced) + && forall ts, vs | first.CanProduce(ts, vs) :: + exists rs :: second.CanProduce(vs, rs) } constructor(second: Action, first: Action) @@ -38,32 +40,41 @@ module Composed { consumed := []; produced := []; Repr := {this} + first.Repr + second.Repr; + height := first.height + second.height + 1; } - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) { + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + decreases height + { exists vs: seq :: && first.CanProduce(consumed, vs) && first.CanConsume(consumed, vs, next) } - ghost predicate CanProduce(consumed: seq, produced: seq) { - exists vs: seq :: first.CanProduce(consumed, vs) && second.CanProduce(vs, produced) + ghost predicate CanProduce(consumed: seq, produced: seq) + decreases height + { + exists vs: seq :: + && first.CanProduce(consumed, vs) + && second.CanProduce(vs, produced) } method Invoke(t: T) returns (r: R) requires Valid() requires CanConsume(consumed, produced, t) modifies Repr - decreases Repr + decreases height ensures Valid() - ensures Repr <= old(Repr) + ensures fresh(Repr - old(Repr)) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) { var v := first.Invoke(t); + r := second.Invoke(v); Update(t, r); + Repr := {this} + first.Repr + second.Repr; } } From bad305482ed02b715d2b9c6c5b510398e31297f3 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 7 Jun 2023 06:55:39 -0700 Subject: [PATCH 57/68] Experimenting with no CanConsume --- src/Actions/Actions.dfy | 9 +++++---- src/Actions/Enumerators.dfy | 27 ++++++++++++--------------- src/Actions/MappingEnumerator.dfy | 7 +------ 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index ebe09f7e..233412c3 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -23,12 +23,13 @@ module Actions { && CanProduce(consumed, produced) decreases height, 0 - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) - requires CanProduce(consumed, produced) + ghost predicate CanProduce(ins: seq, outs: seq) decreases height - ghost predicate CanProduce(consumed: seq, produced: seq) + ghost predicate CanConsume(ins: seq, outs: seq, nextIn: T) decreases height + ensures CanConsume(ins, outs, nextIn) + <==> exists nextOut :: CanProduce(ins + [nextIn], outs + [nextOut]) ghost method Update(t: T, r: R) modifies `consumed, `produced @@ -41,7 +42,7 @@ module Actions { method Invoke(t: T) returns (r: R) requires Valid() - requires CanConsume(consumed, produced, t) + requires exists r :: CanProduce(consumed + [t], produced + [r]) modifies Repr decreases height ensures Valid() diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 96dfd617..c6710f2c 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -92,12 +92,14 @@ module Enumerators { e.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, None, n) } - ghost predicate ConsumesAnything(a: Action<(), Option>) { - forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) + ghost predicate ConsumesAnything(a: Action, ins: seq, outs: seq, nextIn: T) + requires a.CanProduce(ins, outs) + { + exists nextOut :: a.CanProduce(ins + [nextIn], outs + [nextOut]) } ghost predicate IsEnumerator(a: Action<(), Option>) { - && ConsumesAnything(a) + && forall ins, outs, nextIn | a.CanProduce(ins, outs) :: ConsumesAnything(a, ins, outs, nextIn) && exists limit :: EnumerationBoundedBy(a, limit) } @@ -207,11 +209,6 @@ module Enumerators { Repr := {this}; } - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) - decreases height - { - true - } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { @@ -223,7 +220,7 @@ module Enumerators { method Invoke(t: ()) returns (r: Option) requires Valid() - requires CanConsume(consumed, produced, t) + requires exists r :: CanProduce(consumed + [t], produced + [r]) modifies Repr decreases height ensures Valid() @@ -285,11 +282,6 @@ module Enumerators { Repr := {this}; } - ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) - decreases height - { - |consumed| + 1 <= |elements| - } ghost predicate CanProduce(consumed: seq<()>, produced: seq) decreases height { @@ -298,7 +290,7 @@ module Enumerators { method Invoke(t: ()) returns (r: T) requires Valid() - requires CanConsume(consumed, produced, t) + requires exists r :: CanProduce(consumed + [t], produced + [r]) modifies Repr decreases height ensures Valid() @@ -325,6 +317,11 @@ module Enumerators { decreases EnumerationTerminationMetric(e2) { label beforeLoop: + + assert IsEnumerator(e2); + assert e2.CanProduce(e2.consumed, e2.produced); + assert ConsumesAnything(e2, e2.consumed, e2.produced, ()); + assert exists r :: e2.CanProduce(e2.consumed + [()], e2.produced + [r]); var next: Option := e2.Invoke(()); if next.None? { break; } EnumerationTerminationMetricDecreased@beforeLoop(e2, next); diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index 33d7511a..8ba2ddd4 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -53,11 +53,6 @@ module Mapped { assert ValidWrappedProduced(consumed, produced, wrapped.produced); } - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) - decreases height - { - true - } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { @@ -71,7 +66,7 @@ module Mapped { method Invoke(t: ()) returns (r: Option) requires Valid() - requires CanConsume(consumed, produced, t) + requires exists r :: CanProduce(consumed + [t], produced + [r]) modifies Repr decreases height ensures Valid() From bcb450d46875541eb962dcfa38ce149fd3459541 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 11 Jan 2024 20:19:20 -0800 Subject: [PATCH 58/68] Revert "Experimenting with no CanConsume" This reverts commit bad305482ed02b715d2b9c6c5b510398e31297f3. --- src/Actions/Actions.dfy | 9 ++++----- src/Actions/Enumerators.dfy | 27 +++++++++++++++------------ src/Actions/MappingEnumerator.dfy | 7 ++++++- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 233412c3..ebe09f7e 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -23,13 +23,12 @@ module Actions { && CanProduce(consumed, produced) decreases height, 0 - ghost predicate CanProduce(ins: seq, outs: seq) + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires CanProduce(consumed, produced) decreases height - ghost predicate CanConsume(ins: seq, outs: seq, nextIn: T) + ghost predicate CanProduce(consumed: seq, produced: seq) decreases height - ensures CanConsume(ins, outs, nextIn) - <==> exists nextOut :: CanProduce(ins + [nextIn], outs + [nextOut]) ghost method Update(t: T, r: R) modifies `consumed, `produced @@ -42,7 +41,7 @@ module Actions { method Invoke(t: T) returns (r: R) requires Valid() - requires exists r :: CanProduce(consumed + [t], produced + [r]) + requires CanConsume(consumed, produced, t) modifies Repr decreases height ensures Valid() diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index c6710f2c..96dfd617 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -92,14 +92,12 @@ module Enumerators { e.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, None, n) } - ghost predicate ConsumesAnything(a: Action, ins: seq, outs: seq, nextIn: T) - requires a.CanProduce(ins, outs) - { - exists nextOut :: a.CanProduce(ins + [nextIn], outs + [nextOut]) + ghost predicate ConsumesAnything(a: Action<(), Option>) { + forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) } ghost predicate IsEnumerator(a: Action<(), Option>) { - && forall ins, outs, nextIn | a.CanProduce(ins, outs) :: ConsumesAnything(a, ins, outs, nextIn) + && ConsumesAnything(a) && exists limit :: EnumerationBoundedBy(a, limit) } @@ -209,6 +207,11 @@ module Enumerators { Repr := {this}; } + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { @@ -220,7 +223,7 @@ module Enumerators { method Invoke(t: ()) returns (r: Option) requires Valid() - requires exists r :: CanProduce(consumed + [t], produced + [r]) + requires CanConsume(consumed, produced, t) modifies Repr decreases height ensures Valid() @@ -282,6 +285,11 @@ module Enumerators { Repr := {this}; } + ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) + decreases height + { + |consumed| + 1 <= |elements| + } ghost predicate CanProduce(consumed: seq<()>, produced: seq) decreases height { @@ -290,7 +298,7 @@ module Enumerators { method Invoke(t: ()) returns (r: T) requires Valid() - requires exists r :: CanProduce(consumed + [t], produced + [r]) + requires CanConsume(consumed, produced, t) modifies Repr decreases height ensures Valid() @@ -317,11 +325,6 @@ module Enumerators { decreases EnumerationTerminationMetric(e2) { label beforeLoop: - - assert IsEnumerator(e2); - assert e2.CanProduce(e2.consumed, e2.produced); - assert ConsumesAnything(e2, e2.consumed, e2.produced, ()); - assert exists r :: e2.CanProduce(e2.consumed + [()], e2.produced + [r]); var next: Option := e2.Invoke(()); if next.None? { break; } EnumerationTerminationMetricDecreased@beforeLoop(e2, next); diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index 8ba2ddd4..33d7511a 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -53,6 +53,11 @@ module Mapped { assert ValidWrappedProduced(consumed, produced, wrapped.produced); } + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) decreases height { @@ -66,7 +71,7 @@ module Mapped { method Invoke(t: ()) returns (r: Option) requires Valid() - requires exists r :: CanProduce(consumed + [t], produced + [r]) + requires CanConsume(consumed, produced, t) modifies Repr decreases height ensures Valid() From 8139631af89dea18612eebed5b77f631363a2701 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 11 Jan 2024 20:32:47 -0800 Subject: [PATCH 59/68] Complete EnumeratedDistributesOverConcat2 --- src/Actions/Actions.dfy | 1 + src/Actions/Enumerators.dfy | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index ebe09f7e..09f33290 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -79,6 +79,7 @@ module Actions { type IAggregator = Action type Aggregator = a: Action | exists limit :: ProducesTerminatedBy(a, false, limit) witness * + // TODO: Refactor to use DynamicArray class ArrayAggregator extends Action { var storage: array diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 96dfd617..b72d6ee3 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -77,7 +77,7 @@ module Enumerators { { TerminatedUndistributes(left, right, None, n); TerminatedDefinesEnumerated(left + right, n); - + EnumeratedDistributesOverConcat(left, right, n); } From 8607ee708ea27665ac62b2051982dbafa32667d9 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 15 Jan 2024 11:54:06 -0800 Subject: [PATCH 60/68] More progress --- src/Actions/Actions.dfy | 8 +++ src/Actions/ComposedAction.dfy | 31 ++++++---- src/Actions/Enumerators.dfy | 4 +- src/Actions/FunctionAction.dfy | 81 ++++++++++++++++++++++++++ src/Actions/MappingEnumerator.dfy | 95 ++++++++++++++++--------------- src/Actions/NoOpEnumerator.dfy | 61 +++++++++++--------- 6 files changed, 192 insertions(+), 88 deletions(-) create mode 100644 src/Actions/FunctionAction.dfy diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 09f33290..195cd337 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -23,6 +23,14 @@ module Actions { && CanProduce(consumed, produced) decreases height, 0 + // KEY DESIGN POINT: these predicates specifically avoid reading the current + // state of the action. + // That's so extrisnic properties of an action do NOT depend on their current state. + // This is key to ensure that you can prove properties of a given action that + // will continue to hold as the Dafny heap changes. + // The downside is that these are then forced to use quantifiers + // to talk about all possible states of an action. + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) requires CanProduce(consumed, produced) decreases height diff --git a/src/Actions/ComposedAction.dfy b/src/Actions/ComposedAction.dfy index ba4f7bc5..2e3cc480 100644 --- a/src/Actions/ComposedAction.dfy +++ b/src/Actions/ComposedAction.dfy @@ -11,7 +11,7 @@ module Composed { ghost predicate Valid() reads this, Repr - ensures Valid() ==> this in Repr + ensures Valid() ==> this in Repr ensures Valid() ==> && CanProduce(consumed, produced) decreases height, 0 @@ -21,16 +21,18 @@ module Composed { && ValidComponent(second) && first.Repr !! second.Repr && CanProduce(consumed, produced) - && forall ts, vs | first.CanProduce(ts, vs) :: - exists rs :: second.CanProduce(vs, rs) + && consumed == first.consumed + && produced == second.produced } constructor(second: Action, first: Action) requires first.Valid() requires second.Valid() requires first.Repr !! second.Repr - requires first.CanProduce([], []) - requires second.CanProduce([], []) + requires first.consumed == [] + requires first.produced == [] + requires second.consumed == [] + requires second.produced == [] ensures Valid() ensures produced == [] { @@ -44,18 +46,23 @@ module Composed { } ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires CanProduce(consumed, produced) decreases height { - exists vs: seq :: - && first.CanProduce(consumed, vs) - && first.CanConsume(consumed, vs, next) + forall vs: seq | first.CanProduce(consumed, vs) :: + first.CanConsume(consumed, vs, next) + + // Note that you can't compose any arbitrary first with a second: + // if you need to read first.produced to know if you can consume another input, + // that won't work here because this outer CanConsume predicate doesn't take that as input. + // (...unless there's a way of inferring what was produced from second.produced??) } + ghost predicate CanProduce(consumed: seq, produced: seq) decreases height { - exists vs: seq :: - && first.CanProduce(consumed, vs) - && second.CanProduce(vs, produced) + forall vs: seq | first.CanProduce(consumed, vs) :: + second.CanProduce(vs, produced) } method Invoke(t: T) returns (r: R) @@ -69,8 +76,10 @@ module Composed { ensures produced == old(produced) + [r] ensures CanProduce(consumed, produced) { + assert first.Valid(); var v := first.Invoke(t); + assert second.Valid(); r := second.Invoke(v); Update(t, r); diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index b72d6ee3..535c4481 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -88,8 +88,8 @@ module Enumerators { } ghost predicate EnumerationBoundedBy(e: Action<(), Option>, limit: nat) { - forall consumed: seq<()>, produced: seq> :: - e.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, None, n) + forall consumed: seq<()>, produced: seq> | e.CanProduce(consumed, produced) :: + exists n: nat | n <= limit :: Terminated(produced, None, n) } ghost predicate ConsumesAnything(a: Action<(), Option>) { diff --git a/src/Actions/FunctionAction.dfy b/src/Actions/FunctionAction.dfy new file mode 100644 index 00000000..540997e6 --- /dev/null +++ b/src/Actions/FunctionAction.dfy @@ -0,0 +1,81 @@ + +include "Enumerators.dfy" + +module Mapped { + + import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + + class FunctionAction extends Action { + + // TODO: Can we support ~>? + const f: T --> R + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && CanProduce(consumed, produced) + && produced == Seq.Map(f, consumed) + } + + constructor(f: T -> R) + ensures Valid() + ensures this.f == f + ensures fresh(Repr) + ensures consumed == [] + { + this.f := f; + + consumed := []; + produced := []; + Repr := {this}; + } + + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires CanProduce(consumed, produced) + decreases height + { + f.requires(next) + } + ghost predicate CanProduce(consumed: seq, produced: seq) + decreases height + { + && (forall t <- consumed :: f.requires(t)) + && produced == Seq.Map(f, consumed) + } + + method Invoke(t: T) returns (r: R) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + r := f(t); + + Update(t, r); + } + } + + method Example() { + var doubler := new FunctionAction(v => v + v); + + assert doubler.consumed == []; + assert doubler.produced == []; + + var x := doubler.Invoke(2); + assert doubler.produced == [x]; + assert [x] == [4]; + assert x == 4; + } +} \ No newline at end of file diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index 33d7511a..02efd82f 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -8,13 +8,14 @@ module Mapped { import opened Wrappers import opened Seq - class Map extends Action<(), Option> { + class Map extends Action { - const wrapped: Action<(), Option> - ghost const wrappedCanProduce: (seq<()>, seq>) -> bool + const wrapped: Action + ghost const wrappedCanConsume: (seq, seq, T) --> bool + ghost const wrappedCanProduce: (seq, seq) -> bool // TODO: Can we support --> or ~>? - const f: T -> R + const f: R -> R' ghost predicate Valid() reads this, Repr @@ -25,21 +26,20 @@ module Mapped { { && this in Repr && ValidComponent(wrapped) - && IsEnumerator(wrapped) && CanProduce(consumed, produced) && consumed == wrapped.consumed - && produced == Seq.Map(maybeF, wrapped.produced) + && produced == Seq.Map(f, wrapped.produced) + && wrappedCanConsume == wrapped.CanConsume && wrappedCanProduce == wrapped.CanProduce } - constructor(f: T -> R, wrapped: Action<(), Option>) + constructor(f: R -> R', wrapped: Action) requires wrapped.Valid() - requires IsEnumerator(wrapped) requires wrapped.consumed == [] && wrapped.produced == [] ensures Valid() ensures fresh(Repr - (wrapped.Repr)) - ensures this.wrapped == wrapped; - ensures this.f == f; + ensures this.wrapped == wrapped + ensures this.f == f { this.f := f; this.wrapped := wrapped; @@ -48,28 +48,32 @@ module Mapped { produced := []; Repr := {this} + wrapped.Repr; height := wrapped.height + 1; + wrappedCanConsume := wrapped.CanConsume; wrappedCanProduce := wrapped.CanProduce; new; assert ValidWrappedProduced(consumed, produced, wrapped.produced); } - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires CanProduce(consumed, produced) decreases height { - true + forall producedByWrapped: seq | ValidWrappedProduced(consumed, produced, producedByWrapped) :: + assert wrapped.CanProduce(consumed, producedByWrapped); + wrapped.CanConsume(consumed, producedByWrapped, next) } - ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + ghost predicate CanProduce(consumed: seq, produced: seq) decreases height { - exists producedByWrapped: seq> :: ValidWrappedProduced(consumed, produced, producedByWrapped) + exists producedByWrapped: seq :: ValidWrappedProduced(consumed, produced, producedByWrapped) } - ghost predicate ValidWrappedProduced(consumed: seq<()>, produced: seq>, producedByWrapped: seq>) { + ghost predicate ValidWrappedProduced(consumed: seq, produced: seq, producedByWrapped: seq) { && wrappedCanProduce(consumed, producedByWrapped) - && produced == Seq.Map(maybeF, producedByWrapped) + && produced == Seq.Map(f, producedByWrapped) } - method Invoke(t: ()) returns (r: Option) + method Invoke(t: T) returns (r: R') requires Valid() requires CanConsume(consumed, produced, t) modifies Repr @@ -80,47 +84,44 @@ module Mapped { ensures produced == old(produced) + [r] { assert wrapped.Valid(); - var x := wrapped.Invoke(()); - match x { - case Some(v) => r := Some(f(v)); - case None => r := None; - } + var x := wrapped.Invoke(t); + r := f(x); + Repr := {this} + wrapped.Repr; Update(t, r); - Seq.LemmaMapDistributesOverConcat(maybeF, old(wrapped.produced), [x]); + Seq.LemmaMapDistributesOverConcat(f, old(wrapped.produced), [x]); assert ValidWrappedProduced(consumed, produced, wrapped.produced); } - - function maybeF(t: Option): Option { - match t - case Some(v) => Some(f(v)) - case None => None - } - - lemma ThisIsEnumerator() - requires Valid() - ensures IsEnumerator(this) - { - var limit := EnumerationBound(wrapped); - forall consumed, produced | CanProduce(consumed, produced) - ensures exists n: nat | n <= limit :: Terminated(produced, None, n) - { - var producedByWrapped :| ValidWrappedProduced(consumed, produced, producedByWrapped); - var n :| n <= limit && Terminated(producedByWrapped, None, n); - assert Terminated(produced, None, n); - } - assert EnumerationBoundedBy(this, limit); - } } + // lemma EnumeratornessCommutes(mapped: Map<(), Option, Option>) + // requires mapped.Valid() + // requires IsEnumerator(mapped.wrapped) + // ensures IsEnumerator(mapped) + // { + // var limit := EnumerationBound(mapped.wrapped); + // forall consumed, produced | mapped.CanProduce(consumed, produced) + // ensures exists n: nat | n <= limit :: Terminated(produced, None, n) + // { + // var producedByWrapped :| mapped.ValidWrappedProduced(consumed, produced, producedByWrapped); + // var n :| n <= limit && Terminated(producedByWrapped, None, n); + // assert Terminated(produced, None, n); + // } + // assert EnumerationBoundedBy(mapped, limit); + // } + method Example() { var e: SeqEnumerator := new SeqEnumerator([1, 2, 3, 4, 5]); SeqEnumeratorIsEnumerator(e); - var f: Map := new Map(x => x * 2, e); + var f := (x: Option) => match x { + case Some(v) => Some(v + v) + case None => None + }; + var mapped: Map<(), Option, Option> := new Map(f, e); - var x := f.Invoke(()); - assert f.produced == [Some(2)]; + var x := mapped.Invoke(()); + assert mapped.produced == [Some(2)]; assert [x] == [Some(2)]; assert x == Some(2); } diff --git a/src/Actions/NoOpEnumerator.dfy b/src/Actions/NoOpEnumerator.dfy index ad6d5a1e..1564e7a2 100644 --- a/src/Actions/NoOpEnumerator.dfy +++ b/src/Actions/NoOpEnumerator.dfy @@ -8,10 +8,12 @@ module NoOp { import opened Wrappers import opened Seq - class NoOp extends Action<(), Option> { + // Useful test case to ensure it's possible to compose Actions in general: + // if you can't simply wrap an Action and implement the same specification, + // there's no hope of changing behavior or composing Actions. :) + class NoOp extends Action { - const wrapped: Action<(), Option> - ghost const wrappedCanProduce: (seq<()>, seq>) -> bool + const wrapped: Action ghost predicate Valid() reads this, Repr @@ -22,16 +24,13 @@ module NoOp { { && this in Repr && ValidComponent(wrapped) - && IsEnumerator(wrapped) && CanProduce(consumed, produced) && consumed == wrapped.consumed && produced == wrapped.produced - && wrappedCanProduce == wrapped.CanProduce } - constructor(wrapped: Action<(), Option>) + constructor(wrapped: Action) requires wrapped.Valid() - requires IsEnumerator(wrapped) requires wrapped.consumed == [] && wrapped.produced == [] ensures Valid() { @@ -41,28 +40,23 @@ module NoOp { produced := []; Repr := {this} + wrapped.Repr; height := wrapped.height + 1; - wrappedCanProduce := wrapped.CanProduce; new; - assert ValidWrappedProduced(consumed, produced, wrapped.produced); } - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires CanProduce(consumed, produced) decreases height { - true + wrapped.CanConsume(consumed, produced, next) } - ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + + ghost predicate CanProduce(consumed: seq, produced: seq) decreases height { - exists producedByWrapped: seq> :: ValidWrappedProduced(consumed, produced, producedByWrapped) - } - - ghost predicate ValidWrappedProduced(consumed: seq<()>, produced: seq>, producedByWrapped: seq>) { - && wrappedCanProduce(consumed, producedByWrapped) - && produced == producedByWrapped + wrapped.CanProduce(consumed, produced) } - method Invoke(t: ()) returns (r: Option) + method Invoke(t: T) returns (r: R) requires Valid() requires CanConsume(consumed, produced, t) modifies Repr @@ -72,20 +66,31 @@ module NoOp { ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] { - r := wrapped.Invoke(()); + r := wrapped.Invoke(t); Repr := {this} + wrapped.Repr; Update(t, r); - - assert ValidWrappedProduced(consumed, produced, wrapped.produced); + } + } + + lemma EnumeratornessCommutes(noop: NoOp<(), Option>) + requires noop.Valid() + requires IsEnumerator(noop.wrapped) + ensures IsEnumerator(noop) + { + // TODO: working around the fact that triggers don't automatically apply + // because noop has the type NoOp as opposed to Action. + // Not sure if there's a more reusable workaround to the problem (which is going to recur frequently in this library) + var noopAsAction: Action<(), Option> := noop; + + forall consumed, produced, next | noop.CanProduce(consumed, produced) ensures noopAsAction.CanConsume(consumed, produced, next) { + assert noop.CanConsume(consumed, produced, next); } - lemma ThisIsEnumerator() - requires Valid() - ensures IsEnumerator(this) - { - var limit := EnumerationBound(wrapped); - assert EnumerationBoundedBy(this, limit); + var limit := EnumerationBound(noop.wrapped); + forall consumed: seq<()>, produced: seq> | noopAsAction.CanProduce(consumed, produced) ensures exists n: nat | n <= limit :: Terminated(produced, None, n) { + assert noop.CanProduce(consumed, produced); } + assert EnumerationBoundedBy(noop, limit); } } \ No newline at end of file From 2efc280bdedbc09831d4e9f10fbc3c368d677771 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Mon, 15 Jan 2024 15:00:21 -0800 Subject: [PATCH 61/68] Got ComposedAction to verify! --- src/Actions/Actions.dfy | 25 +++++++++++++----- src/Actions/ComposedAction.dfy | 42 ++++++++++++++++++++++++------- src/Actions/Enumerators.dfy | 2 ++ src/Actions/FunctionAction.dfy | 2 +- src/Actions/MappingEnumerator.dfy | 1 + src/Actions/NoOpEnumerator.dfy | 5 +++- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 195cd337..c2e93a37 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -19,8 +19,7 @@ module Actions { ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - ensures Valid() ==> - && CanProduce(consumed, produced) + ensures Valid() ==> CanProduce(consumed, produced) decreases height, 0 // KEY DESIGN POINT: these predicates specifically avoid reading the current @@ -28,14 +27,19 @@ module Actions { // That's so extrisnic properties of an action do NOT depend on their current state. // This is key to ensure that you can prove properties of a given action that // will continue to hold as the Dafny heap changes. + // This approach works because Dafny understands that for a given object, + // the implementation of CanConsume/CanProduce cannot change over time. + // // The downside is that these are then forced to use quantifiers // to talk about all possible states of an action. ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires |consumed| == |produced| requires CanProduce(consumed, produced) decreases height ghost predicate CanProduce(consumed: seq, produced: seq) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height ghost method Update(t: T, r: R) @@ -56,14 +60,13 @@ module Actions { ensures fresh(Repr - old(Repr)) ensures consumed == old(consumed) + [t] ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) } // Common action invariants ghost predicate OnlyProduces(i: Action, c: R) { - forall consumed: seq, toProduce: seq :: - i.CanProduce(consumed, toProduce) <==> forall x <- toProduce :: x == c + forall consumed: seq, produced: seq | |consumed| == |produced| :: + i.CanProduce(consumed, produced) <==> forall x <- produced :: x == c } ghost predicate Terminated(s: seq, c: T, n: nat) { @@ -80,10 +83,18 @@ module Actions { } ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { - forall consumed: seq, produced: seq :: + forall consumed: seq, produced: seq | |consumed| == |produced| :: i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) } + // Class of actions whose precondition doesn't depend on history (probably needs a better name) + ghost predicate ContextFree(a: Action, p: T -> bool) { + forall consumed, produced, next | a.CanProduce(consumed, produced) + :: a.CanConsume(consumed, produced, next) <==> p(next) + } + + // Aggregators + type IAggregator = Action type Aggregator = a: Action | exists limit :: ProducesTerminatedBy(a, false, limit) witness * @@ -97,6 +108,7 @@ module Actions { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> + && |consumed| == |produced| && CanProduce(consumed, produced) decreases height, 0 { @@ -127,6 +139,7 @@ module Actions { true } ghost predicate CanProduce(consumed: seq, produced: seq<()>) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { produced == Seq.Repeat((), |consumed|) diff --git a/src/Actions/ComposedAction.dfy b/src/Actions/ComposedAction.dfy index 2e3cc480..71ebe5fd 100644 --- a/src/Actions/ComposedAction.dfy +++ b/src/Actions/ComposedAction.dfy @@ -1,8 +1,14 @@ include "Actions.dfy" +include "Enumerators.dfy" +include "FunctionAction.dfy" module Composed { import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + import opened Functions class Compose extends Action { @@ -12,8 +18,7 @@ module Composed { ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - ensures Valid() ==> - && CanProduce(consumed, produced) + ensures Valid() ==> CanProduce(consumed, produced) decreases height, 0 { && this in Repr @@ -22,6 +27,7 @@ module Composed { && first.Repr !! second.Repr && CanProduce(consumed, produced) && consumed == first.consumed + && first.produced == second.consumed && produced == second.produced } @@ -49,20 +55,24 @@ module Composed { requires CanProduce(consumed, produced) decreases height { - forall vs: seq | first.CanProduce(consumed, vs) :: - first.CanConsume(consumed, vs, next) + forall piped: seq | first.CanProduce(consumed, piped) && second.CanProduce(piped, produced) :: + && first.CanConsume(consumed, piped, next) + && forall pipedNext: V | first.CanProduce(consumed + [next], piped + [pipedNext]) :: + && second.CanConsume(piped, produced, pipedNext) // Note that you can't compose any arbitrary first with a second: // if you need to read first.produced to know if you can consume another input, // that won't work here because this outer CanConsume predicate doesn't take that as input. - // (...unless there's a way of inferring what was produced from second.produced??) + // (...unless there's a way of inferring what was produced from second.produced) } ghost predicate CanProduce(consumed: seq, produced: seq) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { - forall vs: seq | first.CanProduce(consumed, vs) :: - second.CanProduce(vs, produced) + && |consumed| == |produced| + && exists piped: seq | first.CanProduce(consumed, piped) :: + second.CanProduce(piped, produced) } method Invoke(t: T) returns (r: R) @@ -78,13 +88,27 @@ module Composed { { assert first.Valid(); var v := first.Invoke(t); - - assert second.Valid(); r := second.Invoke(v); Update(t, r); Repr := {this} + first.Repr + second.Repr; } + } + + method Example() { + var e: SeqEnumerator := new SeqEnumerator([1, 2, 3, 4, 5]); + SeqEnumeratorIsEnumerator(e); + var f := (x: Option) => match x { + case Some(v) => Some(v + v) + case None => None + }; + var doubler := new FunctionAction(f); + var mapped: Compose<(), Option, Option> := new Compose(doubler, e); + // TODO: Need some lemmas + var x := mapped.Invoke(()); + assert mapped.produced == [Some(2)]; + assert [x] == [Some(2)]; + assert x == Some(2); } } \ No newline at end of file diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 535c4481..2ed146ad 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -213,6 +213,7 @@ module Enumerators { true } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { var index := |consumed|; @@ -291,6 +292,7 @@ module Enumerators { |consumed| + 1 <= |elements| } ghost predicate CanProduce(consumed: seq<()>, produced: seq) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { |consumed| <= |elements| && produced == elements[..|consumed|] diff --git a/src/Actions/FunctionAction.dfy b/src/Actions/FunctionAction.dfy index 540997e6..f4c4531e 100644 --- a/src/Actions/FunctionAction.dfy +++ b/src/Actions/FunctionAction.dfy @@ -1,7 +1,7 @@ include "Enumerators.dfy" -module Mapped { +module Functions { import opened Actions import opened Enumerators diff --git a/src/Actions/MappingEnumerator.dfy b/src/Actions/MappingEnumerator.dfy index 02efd82f..3a43c5c1 100644 --- a/src/Actions/MappingEnumerator.dfy +++ b/src/Actions/MappingEnumerator.dfy @@ -8,6 +8,7 @@ module Mapped { import opened Wrappers import opened Seq + // TODO: should be unnecessary with FunctionAction, ComposedAction, and the right lemmas class Map extends Action { const wrapped: Action diff --git a/src/Actions/NoOpEnumerator.dfy b/src/Actions/NoOpEnumerator.dfy index 1564e7a2..652f80ca 100644 --- a/src/Actions/NoOpEnumerator.dfy +++ b/src/Actions/NoOpEnumerator.dfy @@ -11,7 +11,7 @@ module NoOp { // Useful test case to ensure it's possible to compose Actions in general: // if you can't simply wrap an Action and implement the same specification, // there's no hope of changing behavior or composing Actions. :) - class NoOp extends Action { + class NoOp extends Action { const wrapped: Action @@ -19,6 +19,7 @@ module NoOp { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> + && |consumed| == |produced| && CanProduce(consumed, produced) decreases height, 0 { @@ -44,6 +45,7 @@ module NoOp { } ghost predicate CanConsume(consumed: seq, produced: seq, next: T) + requires |consumed| == |produced| requires CanProduce(consumed, produced) decreases height { @@ -51,6 +53,7 @@ module NoOp { } ghost predicate CanProduce(consumed: seq, produced: seq) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { wrapped.CanProduce(consumed, produced) From 259f8991cbb3e9a1b9677cf0be289e024c6e0420 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Sat, 17 Feb 2024 10:48:40 -0800 Subject: [PATCH 62/68] =?UTF-8?q?Lots=20more=20Actions,=20partial=20refact?= =?UTF-8?q?oring=20for=20=E2=80=9Chistory=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Actions/Actions.dfy | 106 +++++++++++++-------- src/Actions/Combinators.dfy | 51 ++++++++++ src/Actions/DecreasesClauses.dfy | 58 ++++++++++++ src/Actions/Enumerators.dfy | 86 ++++++++++++----- src/Actions/FilteredEnumerator.dfy | 8 +- src/Actions/GenericAction.dfy | 31 ++++++ src/Actions/IteratorAction.dfy | 116 +++++++++++++++++++++++ src/Actions/NestedEnumerator.dfy | 135 +++++++++++++++++++++++++++ src/Actions/ObjectQuantification.dfy | 9 ++ 9 files changed, 533 insertions(+), 67 deletions(-) create mode 100644 src/Actions/Combinators.dfy create mode 100644 src/Actions/DecreasesClauses.dfy create mode 100644 src/Actions/GenericAction.dfy create mode 100644 src/Actions/IteratorAction.dfy create mode 100644 src/Actions/NestedEnumerator.dfy create mode 100644 src/Actions/ObjectQuantification.dfy diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index c2e93a37..644422f8 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -11,15 +11,22 @@ module Actions { import opened Seq import opened Math + + // TODO: NOT a fully general-purpose handle on any arbitrary Dafny method, + // because gaps in Dafny expressiveness make that impossible for now + // (e.g. field references in framing clauses aren't expressions, + // decreases metrics aren't directly expressible in user code) + // Consider naming this something more specific, related to the assumptions: + // 1. Validatable (and doesn't modify anything not in Repr) + // 2. Behavior specified only by referring to consumed and produced. trait {:termination false} Action extends Validatable { - ghost var consumed: seq - ghost var produced: seq + ghost var history: seq<(T, R)> ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - ensures Valid() ==> CanProduce(consumed, produced) + ensures Valid() ==> CanProduce(history) decreases height, 0 // KEY DESIGN POINT: these predicates specifically avoid reading the current @@ -33,40 +40,53 @@ module Actions { // The downside is that these are then forced to use quantifiers // to talk about all possible states of an action. - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) - requires |consumed| == |produced| - requires CanProduce(consumed, produced) + // TODO: Necessary but not sufficient that: + // CanConsume(history, nextIn) ==> exists nextOut :: CanProduce(history + [(nextIn, nextOut)]) + // Does that need to be explicitly part of the spec? + ghost predicate CanConsume(history: seq<(T, R)>, next: T) + requires CanProduce(history) decreases height - ghost predicate CanProduce(consumed: seq, produced: seq) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<(T, R)>) decreases height ghost method Update(t: T, r: R) - modifies `consumed, `produced - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] + modifies `history + ensures history == old(history) + [(t, r)] { - consumed := consumed + [t]; - produced := produced + [r]; + history := history + [(t, r)]; } method Invoke(t: T) returns (r: R) requires Valid() - requires CanConsume(consumed, produced, t) + requires CanConsume(history, t) modifies Repr decreases height ensures Valid() ensures fresh(Repr - old(Repr)) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] + ensures history == old(history) + [(t, r)] } // Common action invariants - ghost predicate OnlyProduces(i: Action, c: R) { - forall consumed: seq, produced: seq | |consumed| == |produced| :: - i.CanProduce(consumed, produced) <==> forall x <- produced :: x == c + function Consumed(history: seq<(T, R)>): seq { + Seq.Map((e: (T, R)) => e.0, history) + } + + function Produced(history: seq<(T, R)>): seq { + Seq.Map((e: (T, R)) => e.1, history) + } + + ghost predicate OnlyProduces(i: Action, history: seq<(T, R)>, c: R) + { + i.CanProduce(history) <==> forall e <- history :: e.1 == c + } + + ghost predicate CanConsumeAll(a: Action, input: seq) { + forall i | 0 < i < |input| :: + var consumed := input[..(i - 1)]; + var next := input[i]; + forall history | a.CanProduce(history) && Consumed(history) == consumed :: a.CanConsume(history, next) } ghost predicate Terminated(s: seq, c: T, n: nat) { @@ -82,15 +102,16 @@ module Actions { assert forall i | 0 <= i < |right| :: right[i] == (left + right)[i + |left|]; } + // TODO: generalize to "EventuallyProducesSequence"? ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { - forall consumed: seq, produced: seq | |consumed| == |produced| :: - i.CanProduce(consumed, produced) ==> exists n: nat | n <= limit :: Terminated(produced, c, n) + forall history: seq<(T, R)> | i.CanProduce(history) + :: exists n: nat | n <= limit :: Terminated(Produced(history), c, n) } // Class of actions whose precondition doesn't depend on history (probably needs a better name) ghost predicate ContextFree(a: Action, p: T -> bool) { - forall consumed, produced, next | a.CanProduce(consumed, produced) - :: a.CanConsume(consumed, produced, next) <==> p(next) + forall history, next | a.CanProduce(history) + :: a.CanConsume(history, next) <==> p(next) } // Aggregators @@ -108,41 +129,37 @@ module Actions { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> - && |consumed| == |produced| - && CanProduce(consumed, produced) + && CanProduce(history) decreases height, 0 { && this in Repr && storage in Repr && 0 < storage.Length && 0 <= index <= storage.Length - && consumed == storage[..index] - && produced == Seq.Repeat((), |consumed|) + && Consumed(history) == storage[..index] } constructor() ensures Valid() ensures fresh(Repr - {this}) - ensures produced == [] + ensures history == [] { index := 0; storage := new T[10]; - consumed := []; - produced := []; + history := []; Repr := {this, storage}; } - ghost predicate CanConsume(consumed: seq, produced: seq<()>, next: T) + ghost predicate CanConsume(history: seq<(T, ())>, next: T) decreases height { true } - ghost predicate CanProduce(consumed: seq, produced: seq<()>) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<(T, ())>) decreases height { - produced == Seq.Repeat((), |consumed|) + true } twostate predicate ValidReprChange(before: set, after: set) { @@ -151,14 +168,13 @@ module Actions { method Invoke(t: T) returns (r: ()) requires Valid() - requires CanConsume(consumed, produced, t) + requires CanConsume(history, t) modifies Repr decreases height ensures Valid() ensures fresh(Repr - old(Repr)) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) + ensures history == old(history) + [(t, r)] + ensures CanProduce(history) { if index == storage.Length { var newStorage := new T[storage.Length * 2]; @@ -179,7 +195,7 @@ module Actions { function Values(): seq requires Valid() reads Repr - ensures Values() == consumed + ensures Values() == Consumed(history) { storage[..index] } @@ -194,4 +210,16 @@ module Actions { var _ := a.Invoke(5); assert a.Values() == [1, 2, 3, 4, 5]; } + + // Other primitives/examples todo: + // * Eliminate all the (!new) restrictions - look into "older" parameters? + // * How to state the invariant that a constructor as an action creates a new object every time? + // * Lemma that takes produced as input, instead of forall produced? + // * Enumerable ==> defines e.Enumerator() + // * BUT can have infinite containers, probably need IEnumerable as well? (different T for the Action) + // * Expressing that an Action "Eventually produces something" (look at how VMC models this for randomness) + // * IsEnumerator(a) == "a eventually produces None" && "a then only produces None" + // * Build on that to make CrossProduct(enumerable1, enumerable2) + // * Example of adapting an iterator + } \ No newline at end of file diff --git a/src/Actions/Combinators.dfy b/src/Actions/Combinators.dfy new file mode 100644 index 00000000..c57d5ff3 --- /dev/null +++ b/src/Actions/Combinators.dfy @@ -0,0 +1,51 @@ +include "Actions.dfy" +include "Enumerators.dfy" + +module ActionCombinators { + + import opened Actions + import opened Wrappers + import opened Enumerators + + // a.produced == Seq.Map(f, a.consumed) + method Function(f: T --> R) + returns (a: Action) + ensures fresh(a.Repr) + + method EnumeratorOfSeq(s: seq) + returns (e: Enumerator) + + // a.k.a. Chain(first, second) + // + // To map a function f over the values of an action a: + // Compose(Function(f), a) + // + // Or over an enumerator e: + // var maybeF := (maybeT: Option) => match maybeT { + // case Some(t) => Some(f(t)) + // case None => None + // }; + // Compose(Function(maybeF), e) + // + // To map an Action a over a sequence s: + // Collect(Compose(a, EnumeratorOfSeq(s))) + method Compose(second: Action, first: Action) + returns (composed: Action) + requires second.Repr !! first.Repr + + // Produces Seq.Filter(what a produces, p) + // a has to be an Enumerator to ensure Invoke() eventually terminates + method Filter(e: Enumerator, p: T -> bool) + returns (filtered: Enumerator) + + // Produces Seq.Flatten(Actions.Map(inner, (what outer produces))) + // inner needs to be an action rather than just a function + // since enumerators are usually objects that need allocation. + method Nested(outer: Enumerator, inner: Action>) + returns (nested: Enumerator) + + method ForEach(source: Enumerator, sink: Aggregator) + + method Collect(source: Enumerator) + returns (s: seq) +} \ No newline at end of file diff --git a/src/Actions/DecreasesClauses.dfy b/src/Actions/DecreasesClauses.dfy new file mode 100644 index 00000000..61c61f4e --- /dev/null +++ b/src/Actions/DecreasesClauses.dfy @@ -0,0 +1,58 @@ + +module Termination { + + datatype ClauseTail = More(next: TerminationMetric) | Top + + datatype TerminationMetric = TerminationMetric(first: TMValue, rest: ClauseTail) { + predicate IsSmallerThan(other: TerminationMetric) { + if first == other.first then + match (rest, other.rest) { + case (Top, _) => false + case (More(_), Top) => true + case (More(next), More(otherNext)) => next.IsSmallerThan(otherNext) + } + else + first.IsSmallerThan(other.first) + } + + ghost function {:axiom} Ordinal(): ORDINAL + } + + lemma {:axiom} OrdinalOrdered(left: TerminationMetric, right: TerminationMetric) + requires left.IsSmallerThan(right) + ensures left.Ordinal() < right.Ordinal() + + datatype TMValue = + | TMNat(natValue: nat) + | TMChar(charValue: nat) + | TMSeq(seqValue: seq) + // TODO: etc + { + predicate IsSmallerThan(other: TMValue) { + match (this, other) { + case (TMNat(left), TMNat(right)) => left < right + case (TMChar(left), TMChar(right)) => left < right + case (TMSeq(left), TMSeq(right)) => left < right // TODO: should be Seq.IsSubsequenceOf + // TODO: etc + case _ => false + } + } + } +} + +module Example { + + datatype D = Less(x: nat) | More(x: nat) + + function Foo(d: D): nat + decreases d + { + if d.More? then + Foo(Less(d.x)) + else + if d.x == 0 then + 42 + else + Foo(Less(d.x - 1)) + } +} \ No newline at end of file diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 2ed146ad..806f9283 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -173,6 +173,10 @@ module Enumerators { // Potentially infinite enumerator type IEnumerator = Action<(), T> + // TODO: There's + + type Enumerator = e: IEnumerator> | IsEnumerator(e) witness * + class SeqEnumerator extends Action<(), Option> { const elements: seq @@ -207,31 +211,29 @@ module Enumerators { Repr := {this}; } - ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + ghost predicate CanConsume(history: seq<((), Option)>, next: ()) decreases height { true } - ghost predicate CanProduce(consumed: seq<()>, produced: seq>) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<((), Option)>) decreases height { - var index := |consumed|; + var index := |history|; var values := Math.Min(index, |elements|); var nones := index - values; - produced == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) + Produced(history) == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) } method Invoke(t: ()) returns (r: Option) requires Valid() - requires CanConsume(consumed, produced, t) + requires CanConsume(history, t) modifies Repr decreases height ensures Valid() ensures Repr <= old(Repr) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) + ensures history == old(history) + [(t, r)] + ensures CanProduce(history) { if index < |elements| { r := Some(elements[index]); @@ -263,51 +265,48 @@ module Enumerators { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> - && CanProduce(consumed, produced) + && CanProduce(history) decreases height, 0 { && this in Repr && 0 <= index <= |elements| - && |consumed| == index - && produced == elements[0..index] + && |history| == index + && Produced(history) == elements[0..index] } constructor(s: seq) ensures Valid() ensures fresh(Repr - {this}) - ensures produced == [] + ensures history == [] ensures elements == s { elements := s; index := 0; - consumed := []; - produced := []; + history := []; Repr := {this}; } - ghost predicate CanConsume(consumed: seq<()>, produced: seq, next: ()) + ghost predicate CanConsume(history: seq<((), T)>, next: ()) decreases height { - |consumed| + 1 <= |elements| + |history| + 1 <= |elements| } - ghost predicate CanProduce(consumed: seq<()>, produced: seq) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<((), T)>) decreases height { - |consumed| <= |elements| && produced == elements[..|consumed|] + |history| <= |elements| && Produced(history) == elements[..|history|] } method Invoke(t: ()) returns (r: T) requires Valid() - requires CanConsume(consumed, produced, t) + requires CanConsume(history, t) modifies Repr decreases height ensures Valid() ensures Repr <= old(Repr) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) + ensures history == old(history) + [(t, r)] + ensures CanProduce(history) { r := elements[index]; index := index + 1; @@ -316,6 +315,45 @@ module Enumerators { } } + // Note that this means "possibly infinite" as opposed to "definitely infinite", + // but if a value is finite its enumerator has to communicate + trait IEnumerable { + method IEnumerator() returns (e: IEnumerator) + } + + trait Enumerable extends IEnumerable> { + method Enumerator() returns (e: Enumerator) + } + + // method ForEach(source: Enumerator, sink: Aggregator) + // requires source.Valid() + // requires sink.Valid() + // requires forall produced + // | source.CanProduce(Seq.Repeat((), |produced|), produced) + // :: sink.CanConsume(produced, Seq.Repeat((), |produced|)) + // requires source.Repr !! sink.Repr + // modifies source.Repr, sink.Repr + // { + // while true + // invariant source.Valid() + // invariant fresh(source.Repr - old(source.Repr)) + // invariant sink.Valid() + // invariant fresh(sink.Repr - old(sink.Repr)) + // invariant source.Repr !! sink.Repr + // modifies source.Repr, sink.Repr + // decreases EnumerationTerminationMetric(source) + // { + // label beforeLoop: + // var next: Option := source.Invoke(()); + // if next.None? { break; } + // EnumerationTerminationMetricDecreased@beforeLoop(source, next); + + // var _ := sink.Invoke(next.value); + // } + // } + + // Examples + method EnumeratorExample() { var e2: SeqEnumerator := new SeqEnumerator([1, 2, 3, 4, 5]); SeqEnumeratorIsEnumerator(e2); diff --git a/src/Actions/FilteredEnumerator.dfy b/src/Actions/FilteredEnumerator.dfy index ef1d4142..420c7930 100644 --- a/src/Actions/FilteredEnumerator.dfy +++ b/src/Actions/FilteredEnumerator.dfy @@ -11,7 +11,6 @@ module Filtered { class Filter extends Action<(), Option> { const wrapped: Action<(), Option> - ghost const wrappedCanProduce: (seq<()>, seq>) -> bool // TODO: Can we support --> or ~>? const filter: T -> bool @@ -43,7 +42,6 @@ module Filtered { produced := []; Repr := {this} + wrapped.Repr; height := wrapped.height + 1; - wrappedCanProduce := wrapped.CanProduce; } ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) @@ -52,13 +50,15 @@ module Filtered { true } ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| decreases height { - exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) + && |consumed| == |produced| + && exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) } ghost predicate ValidWrappedProduced(produced: seq>, producedByWrapped: seq>) { - && wrappedCanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && wrapped.CanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) } diff --git a/src/Actions/GenericAction.dfy b/src/Actions/GenericAction.dfy new file mode 100644 index 00000000..8c353cab --- /dev/null +++ b/src/Actions/GenericAction.dfy @@ -0,0 +1,31 @@ + +include "../Wrappers.dfy" +include "../Frames.dfy" +include "../Math.dfy" +include "../Collections/Sequences/Seq.dfy" + +include "DecreasesClauses.dfy" + +module GenericActions { + + import opened Wrappers + import opened Frames + import opened Seq + import opened Math + import opened Termination + + trait {:termination false} Action extends Validatable { + + ghost predicate Requires(t: T) + ghost function Reads(t: T): set + ghost function Modifies(t: T): set + ghost function Decreases(t: T): TerminationMetric + ghost predicate Ensures(t: T, r: R) + + method Invoke(t: T) returns (r: R) + requires Requires(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + } + +} \ No newline at end of file diff --git a/src/Actions/IteratorAction.dfy b/src/Actions/IteratorAction.dfy new file mode 100644 index 00000000..ce0bc177 --- /dev/null +++ b/src/Actions/IteratorAction.dfy @@ -0,0 +1,116 @@ +include "Enumerators.dfy" + +module IteratorActionExample { + + import opened Actions + import opened Enumerators + import opened Wrappers + + iterator Iter(s: set) yields (x: T) + yield ensures x in s && x !in xs[..|xs|-1] + ensures s == set z | z in xs + { + var r := s; + while (r != {}) + invariant r !! set z | z in xs + invariant s == r + set z | z in xs + { + var y :| y in r; + assert y !in xs; + r, x := r - {y}, y; + assert y !in xs; + yield; + assert y == xs[|xs|-1]; // a lemma to help prove loop invariant + } + } + + class IterAction extends Action<(), Option> { + + const i: Iter + var more: bool + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && i in Repr + && i._new <= Repr + && i._reads <= Repr + && i._modifies <= Repr + && this !in i._reads + && this !in i._new + && (more ==> i.Valid()) + && CanProduce(consumed, produced) + } + + constructor(i: Iter) + requires i.Valid() + requires i.xs == [] + ensures Valid() + { + this.i := i; + this.more := true; + + Repr := {this, i} + i._modifies + i._reads + i._new; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + requires |consumed| == |produced| + requires CanProduce(consumed, produced) + decreases height + { + true + } + + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + decreases height + { + && |consumed| == |produced| + && var enumeratedSet := (set x | x in Enumerated(produced)); + && enumeratedSet < i.s + } + + method Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + assert this !in i._reads; + if more { + r := Some(i.x); + assert this !in i._reads; + + var more := i.MoveNext(); + assert this !in i._reads; + assert more ==> i.Valid(); + assert this !in i._new; + } else { + assert this !in i._new; + r := None; + assert this !in i._new; + } + assert this !in i._new; + assert more ==> i.Valid(); + + Update(t, r); + Repr := {this, i} + i._reads + i._new + i._modifies; + + assert this in Repr; + assert i in Repr; + assert i._reads <= Repr; + assert i._new <= Repr; + assert more ==> i.Valid(); + // assert CanProduce(consumed, produced); + } + } + +} \ No newline at end of file diff --git a/src/Actions/NestedEnumerator.dfy b/src/Actions/NestedEnumerator.dfy new file mode 100644 index 00000000..43ccb1a2 --- /dev/null +++ b/src/Actions/NestedEnumerator.dfy @@ -0,0 +1,135 @@ + +include "Enumerators.dfy" + +module Nested { + + import opened Actions + import opened Enumerators + import opened Wrappers + import opened Seq + + // The equivalent of SelectMany from LINQ + class Nested extends Action<(), Option> { + + const first: Action<(), V> + const secondConstr: Action>> + + ghost predicate Valid() + reads this, Repr + ensures Valid() ==> this in Repr + ensures Valid() ==> + && CanProduce(consumed, produced) + decreases height, 0 + { + && this in Repr + && ValidComponent(first) + && ValidComponent(secondConstr) + && first.Repr !! secondConstr.Repr + && IsEnumerator(wrapped) + && CanProduce(consumed, produced) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) + } + + constructor(filter: T -> bool, wrapped: Action<(), Option>) + requires wrapped.Valid() + requires IsEnumerator(wrapped) + requires wrapped.consumed == [] && wrapped.produced == [] + ensures Valid() + { + this.filter := filter; + this.wrapped := wrapped; + + consumed := []; + produced := []; + Repr := {this} + wrapped.Repr; + height := wrapped.height + 1; + } + + ghost predicate CanConsume(consumed: seq<()>, produced: seq>, next: ()) + decreases height + { + true + } + ghost predicate CanProduce(consumed: seq<()>, produced: seq>) + ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + decreases height + { + && |consumed| == |produced| + && exists producedByWrapped: seq> :: ValidWrappedProduced(produced, producedByWrapped) + } + + ghost predicate ValidWrappedProduced(produced: seq>, producedByWrapped: seq>) { + && wrapped.CanProduce(Seq.Repeat((), |producedByWrapped|), producedByWrapped) + && Enumerated(produced) == Seq.Filter(filter, Enumerated(producedByWrapped)) + } + + // + method {:vcs_split_on_every_assert} Invoke(t: ()) returns (r: Option) + requires Valid() + requires CanConsume(consumed, produced, t) + modifies Repr + decreases height + ensures Valid() + ensures fresh(Repr - old(Repr)) + ensures consumed == old(consumed) + [t] + ensures produced == old(produced) + [r] + { + // while true: + // if + + while true + invariant wrapped.Valid() + invariant fresh(wrapped.Repr - old(wrapped.Repr)) + invariant ValidWrappedProduced(produced, wrapped.produced) + invariant produced == old(produced) + invariant consumed == old(consumed) + invariant Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)) + decreases EnumerationTerminationMetric(wrapped) + { + assert IsEnumerator(wrapped); + assert ConsumesAnything(wrapped); + assert wrapped.Valid(); + assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); + label before: + r := wrapped.Invoke(()); + + Repr := {this} + wrapped.Repr; + + if r.None? || filter(r.value) { + break; + } + EnumerationTerminationMetricDecreased@before(wrapped, r); + + // var wrappedEnumeratedBefore := Enumerated(old@before(wrapped.produced)); + // assert wrapped.produced == old@before(wrapped.produced) + [r]; + + // ProducingSomeImpliesTerminated@before(wrapped, r); + // TerminatedBoundsEnumerated(wrapped.produced, |wrapped.produced|); + // // TerminatedDistributesOverConcat(old@before(wrapped.produced), [r], None, 1); + // assert |Enumerated(wrapped.produced)| == |wrapped.produced|; + // assert wrapped.produced == old@before(wrapped.produced) + [r]; + // EnumeratedDistributesOverConcat(old@before(wrapped.produced), [r], 1); + // assert Enumerated(wrapped.produced) == Enumerated(old@before(wrapped.produced)) + [r.value]; + + // assert Enumerated(old(produced)) == Seq.Filter(filter, Enumerated(old(wrapped.produced))); + // calc { + // Seq.Filter(filter, Enumerated(wrapped.produced)); + // Seq.Filter(filter, Enumerated(old(wrapped.produced) + [r])); + // Seq.Filter(filter, Enumerated(old(wrapped.produced)) + Enumerated([r])); + // Seq.Filter(filter, Enumerated(old(wrapped.produced)) + [r.value]); + // { LemmaFilterDistributesOverConcat(filter, wrappedEnumeratedBefore, [r.value]); } + // Seq.Filter(filter, Enumerated(old(wrapped.produced))) + Seq.Filter(filter, [r.value]); + // Seq.Filter(filter, Enumerated(old(wrapped.produced))); + // } + // assert Enumerated(produced) == Seq.Filter(filter, Enumerated(wrapped.produced)); + } + + assert r.Some? ==> filter(r.value); + assert wrapped.CanProduce(wrapped.consumed, wrapped.produced); + Update(t, r); + } + } + + // TODO: Lemma that if first is an enumerator (which requires V == Option for some V') + // then Nested(first, secondConstr) is too +} \ No newline at end of file diff --git a/src/Actions/ObjectQuantification.dfy b/src/Actions/ObjectQuantification.dfy new file mode 100644 index 00000000..b3de1fa0 --- /dev/null +++ b/src/Actions/ObjectQuantification.dfy @@ -0,0 +1,9 @@ +module Testing { + + trait SimpleAction { + method Invoke() returns (t: T) + } + + method + +} \ No newline at end of file From b036c6646cdb7dd154f644e664085f9db3fb9a85 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 21 Feb 2024 10:35:40 -0800 Subject: [PATCH 63/68] Got GenericAction to work as a base type --- src/Actions/Actions.dfy | 76 ++++++++++++++++++++++---------- src/Actions/DecreasesClauses.dfy | 12 +++++ src/Actions/Enumerators.dfy | 6 +-- src/Actions/GenericAction.dfy | 24 +++++++--- 4 files changed, 87 insertions(+), 31 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index 644422f8..a13069cb 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -4,13 +4,16 @@ include "../Frames.dfy" include "../Math.dfy" include "../Collections/Sequences/Seq.dfy" +include "GenericAction.dfy" + module Actions { import opened Wrappers import opened Frames import opened Seq import opened Math - + import opened GenericActions + import opened Termination // TODO: NOT a fully general-purpose handle on any arbitrary Dafny method, // because gaps in Dafny expressiveness make that impossible for now @@ -19,7 +22,7 @@ module Actions { // Consider naming this something more specific, related to the assumptions: // 1. Validatable (and doesn't modify anything not in Repr) // 2. Behavior specified only by referring to consumed and produced. - trait {:termination false} Action extends Validatable { + trait {:termination false} Action extends GenericAction, Validatable { ghost var history: seq<(T, R)> @@ -50,21 +53,54 @@ module Actions { ghost predicate CanProduce(history: seq<(T, R)>) decreases height - ghost method Update(t: T, r: R) + ghost predicate Requires(t: T) + reads Reads(t) + { + && Valid() + && CanConsume(history, t) + } + ghost function Reads(t: T): set + reads this + ensures this in Reads(t) + { + {this} + Repr + } + ghost function Modifies(t: T): set + reads Reads(t) + { + Repr + } + ghost function Decreases(t: T): TerminationMetric + reads Reads(t) + { + NatTerminationMetric(height) + } + ghost predicate Ensures(t: T, r: R) + reads Reads(t) + { + && Valid() + && 0 < |history| + && history[|history| - 1] == (t, r) + } + twostate predicate EnsuresTwostate(t: T) + reads Reads(t) + { + && 0 < |history| + && history[..|history| - 1] == old(history) + && CanProduce(history) + && fresh(Repr - old(Repr)) + } + + // Helper method for updating history + ghost method Update(t: T, r: R) + reads `history modifies `history ensures history == old(history) + [(t, r)] { history := history + [(t, r)]; } - method Invoke(t: T) returns (r: R) - requires Valid() - requires CanConsume(history, t) - modifies Repr - decreases height - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures history == old(history) + [(t, r)] + } // Common action invariants @@ -162,19 +198,13 @@ module Actions { true } - twostate predicate ValidReprChange(before: set, after: set) { - fresh(after - before) - } - method Invoke(t: T) returns (r: ()) - requires Valid() - requires CanConsume(history, t) - modifies Repr - decreases height - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures history == old(history) + [(t, r)] - ensures CanProduce(history) + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { if index == storage.Length { var newStorage := new T[storage.Length * 2]; diff --git a/src/Actions/DecreasesClauses.dfy b/src/Actions/DecreasesClauses.dfy index 61c61f4e..3549f4fd 100644 --- a/src/Actions/DecreasesClauses.dfy +++ b/src/Actions/DecreasesClauses.dfy @@ -17,6 +17,18 @@ module Termination { ghost function {:axiom} Ordinal(): ORDINAL } + + // Convenience constructors + function TerminationMetric1(value1: TMValue): TerminationMetric { + TerminationMetric(value1, Top) + } + function TerminationMetric2(value1: TMValue, value2: TMValue): TerminationMetric { + TerminationMetric(value1, More(TerminationMetric(value2, Top))) + } + function NatTerminationMetric(m: nat): TerminationMetric { + TerminationMetric1(TMNat(m)) + } + lemma {:axiom} OrdinalOrdered(left: TerminationMetric, right: TerminationMetric) requires left.IsSmallerThan(right) diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index 806f9283..cec9c0f1 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -88,12 +88,12 @@ module Enumerators { } ghost predicate EnumerationBoundedBy(e: Action<(), Option>, limit: nat) { - forall consumed: seq<()>, produced: seq> | e.CanProduce(consumed, produced) :: - exists n: nat | n <= limit :: Terminated(produced, None, n) + forall history: seq<((), Option)> | e.CanProduce(history) :: + exists n: nat | n <= limit :: Terminated(Produced(history), None, n) } ghost predicate ConsumesAnything(a: Action<(), Option>) { - forall consumed, produced, next | a.CanProduce(consumed, produced) :: a.CanConsume(consumed, produced, next) + forall history, next | a.CanProduce(history) :: a.CanConsume(history, next) } ghost predicate IsEnumerator(a: Action<(), Option>) { diff --git a/src/Actions/GenericAction.dfy b/src/Actions/GenericAction.dfy index 8c353cab..961f5390 100644 --- a/src/Actions/GenericAction.dfy +++ b/src/Actions/GenericAction.dfy @@ -8,24 +8,38 @@ include "DecreasesClauses.dfy" module GenericActions { - import opened Wrappers - import opened Frames - import opened Seq - import opened Math import opened Termination - trait {:termination false} Action extends Validatable { + trait {:termination false} GenericAction { + + // Specification predicates ghost predicate Requires(t: T) + reads Reads(t) ghost function Reads(t: T): set + reads this + ensures this in Reads(t) ghost function Modifies(t: T): set + reads Reads(t) ghost function Decreases(t: T): TerminationMetric + reads Reads(t) + // These are split in two because we can't pass r + // to a twostate predicate (since it might not be allocated + // in the previous state) ghost predicate Ensures(t: T, r: R) + reads Reads(t) + twostate predicate EnsuresTwostate(t: T) + reads Reads(t) + + // Action action implementation method Invoke(t: T) returns (r: R) requires Requires(t) + reads Reads(t) modifies Modifies(t) decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) } } \ No newline at end of file From 627ec2a144ab3fc9f9527a18878e5a4c30f68870 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 21 Feb 2024 16:27:58 -0800 Subject: [PATCH 64/68] Updated most cases to deal with history change --- src/Actions/Actions.dfy | 28 +++++--- src/Actions/Combinators.dfy | 17 +++-- src/Actions/ComposedAction.dfy | 67 ++++++++++--------- src/Actions/Enumerators.dfy | 118 ++++++++++++++++++--------------- src/Actions/FunctionAction.dfy | 40 +++++------ src/Actions/NoOpEnumerator.dfy | 50 ++++++-------- 6 files changed, 167 insertions(+), 153 deletions(-) diff --git a/src/Actions/Actions.dfy b/src/Actions/Actions.dfy index a13069cb..91aaa402 100644 --- a/src/Actions/Actions.dfy +++ b/src/Actions/Actions.dfy @@ -91,7 +91,8 @@ module Actions { && fresh(Repr - old(Repr)) } - // Helper method for updating history + // Helpers + ghost method Update(t: T, r: R) reads `history modifies `history @@ -100,16 +101,26 @@ module Actions { history := history + [(t, r)]; } - + ghost function Consumed(): seq + reads this + { + Inputs(history) + } + + ghost function Produced(): seq + reads this + { + Outputs(history) + } } // Common action invariants - function Consumed(history: seq<(T, R)>): seq { + function Inputs(history: seq<(T, R)>): seq { Seq.Map((e: (T, R)) => e.0, history) } - function Produced(history: seq<(T, R)>): seq { + function Outputs(history: seq<(T, R)>): seq { Seq.Map((e: (T, R)) => e.1, history) } @@ -122,7 +133,7 @@ module Actions { forall i | 0 < i < |input| :: var consumed := input[..(i - 1)]; var next := input[i]; - forall history | a.CanProduce(history) && Consumed(history) == consumed :: a.CanConsume(history, next) + forall history | a.CanProduce(history) && Inputs(history) == consumed :: a.CanConsume(history, next) } ghost predicate Terminated(s: seq, c: T, n: nat) { @@ -141,7 +152,7 @@ module Actions { // TODO: generalize to "EventuallyProducesSequence"? ghost predicate ProducesTerminatedBy(i: Action, c: R, limit: nat) { forall history: seq<(T, R)> | i.CanProduce(history) - :: exists n: nat | n <= limit :: Terminated(Produced(history), c, n) + :: exists n: nat | n <= limit :: Terminated(Outputs(history), c, n) } // Class of actions whose precondition doesn't depend on history (probably needs a better name) @@ -172,7 +183,7 @@ module Actions { && storage in Repr && 0 < storage.Length && 0 <= index <= storage.Length - && Consumed(history) == storage[..index] + && Consumed() == storage[..index] } constructor() @@ -220,12 +231,13 @@ module Actions { r := (); Update(t, r); + assert Valid(); } function Values(): seq requires Valid() reads Repr - ensures Values() == Consumed(history) + ensures Values() == Consumed() { storage[..index] } diff --git a/src/Actions/Combinators.dfy b/src/Actions/Combinators.dfy index c57d5ff3..ec1ee199 100644 --- a/src/Actions/Combinators.dfy +++ b/src/Actions/Combinators.dfy @@ -7,13 +7,16 @@ module ActionCombinators { import opened Wrappers import opened Enumerators - // a.produced == Seq.Map(f, a.consumed) method Function(f: T --> R) - returns (a: Action) + returns (a: SimpleAction) ensures fresh(a.Repr) + ensures forall history: seq<(T, R)> | a.CanProduce(history), e <- history :: + f.requires(e.0) && e.1 == f(e.0) - method EnumeratorOfSeq(s: seq) + method EnumeratorOfSeq(s: seq) returns (e: Enumerator) + ensures forall history: seq<((), Option)> | e.CanProduce(history) :: + Enumerated(Outputs(history)) == // a.k.a. Chain(first, second) // @@ -35,17 +38,17 @@ module ActionCombinators { // Produces Seq.Filter(what a produces, p) // a has to be an Enumerator to ensure Invoke() eventually terminates - method Filter(e: Enumerator, p: T -> bool) + method Filter(e: Enumerator, p: T -> bool) returns (filtered: Enumerator) // Produces Seq.Flatten(Actions.Map(inner, (what outer produces))) // inner needs to be an action rather than just a function // since enumerators are usually objects that need allocation. - method Nested(outer: Enumerator, inner: Action>) + method Nested(outer: Enumerator, inner: Action>) returns (nested: Enumerator) - method ForEach(source: Enumerator, sink: Aggregator) + method ForEach(source: Enumerator, sink: Aggregator) - method Collect(source: Enumerator) + method Collect(source: Enumerator) returns (s: seq) } \ No newline at end of file diff --git a/src/Actions/ComposedAction.dfy b/src/Actions/ComposedAction.dfy index 71ebe5fd..9bcbe97c 100644 --- a/src/Actions/ComposedAction.dfy +++ b/src/Actions/ComposedAction.dfy @@ -18,47 +18,46 @@ module Composed { ghost predicate Valid() reads this, Repr ensures Valid() ==> this in Repr - ensures Valid() ==> CanProduce(consumed, produced) + ensures Valid() ==> CanProduce(history) decreases height, 0 { && this in Repr && ValidComponent(first) && ValidComponent(second) && first.Repr !! second.Repr - && CanProduce(consumed, produced) - && consumed == first.consumed - && first.produced == second.consumed - && produced == second.produced + && CanProduce(history) + && Consumed() == first.Consumed() + && first.Produced() == second.Consumed() + && Produced() == second.Produced() } constructor(second: Action, first: Action) requires first.Valid() requires second.Valid() requires first.Repr !! second.Repr - requires first.consumed == [] - requires first.produced == [] - requires second.consumed == [] - requires second.produced == [] + requires first.history == [] + requires second.history == [] ensures Valid() - ensures produced == [] + ensures history == [] { this.first := first; this.second := second; - consumed := []; - produced := []; + history := []; Repr := {this} + first.Repr + second.Repr; height := first.height + second.height + 1; } - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) - requires CanProduce(consumed, produced) + ghost predicate CanConsume(history: seq<(T, R)>, next: T) + requires CanProduce(history) decreases height { - forall piped: seq | first.CanProduce(consumed, piped) && second.CanProduce(piped, produced) :: - && first.CanConsume(consumed, piped, next) - && forall pipedNext: V | first.CanProduce(consumed + [next], piped + [pipedNext]) :: - && second.CanConsume(piped, produced, pipedNext) + forall piped: seq | CanPipe(history, piped) :: + && var firstHistory := Seq.Zip(Inputs(history), piped); + && var secondHistory := Seq.Zip(piped, Outputs(history)); + && first.CanConsume(firstHistory, next) + && forall pipedNext: V | first.CanProduce(firstHistory + [(next, pipedNext)]) :: + && second.CanConsume(secondHistory, pipedNext) // Note that you can't compose any arbitrary first with a second: // if you need to read first.produced to know if you can consume another input, @@ -66,25 +65,27 @@ module Composed { // (...unless there's a way of inferring what was produced from second.produced) } - ghost predicate CanProduce(consumed: seq, produced: seq) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<(T, R)>) decreases height { - && |consumed| == |produced| - && exists piped: seq | first.CanProduce(consumed, piped) :: - second.CanProduce(piped, produced) + exists piped: seq :: CanPipe(history, piped) + } + + ghost predicate CanPipe(history: seq<(T, R)>, piped: seq) + decreases height, 0 + { + && |piped| == |history| + && first.CanProduce(Seq.Zip(Inputs(history), piped)) + && second.CanProduce(Seq.Zip(piped, Outputs(history))) } method Invoke(t: T) returns (r: R) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases height - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] - ensures CanProduce(consumed, produced) + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { assert first.Valid(); var v := first.Invoke(t); @@ -107,7 +108,7 @@ module Composed { // TODO: Need some lemmas var x := mapped.Invoke(()); - assert mapped.produced == [Some(2)]; + assert mapped.Produced() == [Some(2)]; assert [x] == [Some(2)]; assert x == Some(2); } diff --git a/src/Actions/Enumerators.dfy b/src/Actions/Enumerators.dfy index cec9c0f1..b461d430 100644 --- a/src/Actions/Enumerators.dfy +++ b/src/Actions/Enumerators.dfy @@ -15,6 +15,15 @@ module Enumerators { [produced[0].value] + Enumerated(produced[1..]) } + // TODO: Feels like there should be a cleaner expression of this + ghost predicate EnumeratesSeq(a: Action<(), Option>, s: seq) { + forall history | a.CanProduce(history) :: + var produced := Outputs(history); + var enumerated := Enumerated(produced); + && enumerated <= s + && (|enumerated| < |produced| ==> enumerated == s) + } + lemma TerminatedDefinesEnumerated(s: seq>, n: nat) requires Terminated(s, None, n) ensures @@ -89,7 +98,7 @@ module Enumerators { ghost predicate EnumerationBoundedBy(e: Action<(), Option>, limit: nat) { forall history: seq<((), Option)> | e.CanProduce(history) :: - exists n: nat | n <= limit :: Terminated(Produced(history), None, n) + exists n: nat | n <= limit :: Terminated(Outputs(history), None, n) } ghost predicate ConsumesAnything(a: Action<(), Option>) { @@ -115,29 +124,29 @@ module Enumerators { requires IsEnumerator(a) { var limit := EnumerationBound(a); - var n: nat :| n <= limit && Terminated(a.produced, None, n); - TerminatedDefinesEnumerated(a.produced, n); - limit - |Enumerated(a.produced)| + var n: nat :| n <= limit && Terminated(Outputs(a.history), None, n); + TerminatedDefinesEnumerated(a.Produced(), n); + limit - |Enumerated(a.Produced())| } twostate lemma ProducingSomeImpliesTerminated(a: Action<(), Option>, nextProduced: Option) requires old(a.Valid()) - requires old(a.CanProduce(a.consumed, a.produced)) + requires old(a.CanProduce(a.history)) requires a.Valid() - requires a.CanProduce(a.consumed, a.produced) + requires a.CanProduce(a.history) requires IsEnumerator(a) - requires a.produced == old(a.produced) + [nextProduced]; + requires a.Produced() == old(a.Produced()) + [nextProduced] requires nextProduced.Some? - ensures Terminated(a.produced, None, |a.produced|) + ensures Terminated(a.Produced(), None, |a.Produced()|) { - var before := old(a.produced); + var before := old(a.Produced()); var n: nat :| n <= |before| && Terminated(before, None, n); - var m: nat :| Terminated(a.produced, None, m); + var m: nat :| Terminated(a.Produced(), None, m); if n < |before| { assert before[|before| - 1] == None; - assert a.produced[|a.produced| - 1] != None; - assert |a.produced| <= m; - assert a.produced[|before| - 1] != None; + assert a.Produced()[|a.Produced()| - 1] != None; + assert |a.Produced()| <= m; + assert a.Produced()[|before| - 1] != None; assert false; } assert |before| <= n; @@ -147,18 +156,18 @@ module Enumerators { requires old(a.Valid()) requires a.Valid() requires IsEnumerator(a) - requires a.produced == old(a.produced) + [nextProduced]; + requires a.Produced() == old(a.Produced()) + [nextProduced] requires nextProduced.Some? ensures EnumerationTerminationMetric(a) < old(EnumerationTerminationMetric(a)) { - var before := old(a.produced); + var before := old(a.Produced()); var n: nat :| n <= |before| && Terminated(before, None, n); - var m: nat :| Terminated(a.produced, None, m); + var m: nat :| Terminated(a.Produced(), None, m); if n < |before| { assert before[|before| - 1] == None; - assert a.produced[|a.produced| - 1] != None; - assert |a.produced| <= m; - assert a.produced[|before| - 1] != None; + assert a.Produced()[|a.Produced()| - 1] != None; + assert |a.Produced()| <= m; + assert a.Produced()[|before| - 1] != None; assert false; } assert |before| <= n; @@ -166,15 +175,13 @@ module Enumerators { TerminatedDefinesEnumerated(before, n); assert |Enumerated(before)| <= n; TerminatedDistributesOverConcat(before, [nextProduced], None, 1); - assert Terminated(a.produced, None, |a.produced|); - TerminatedDefinesEnumerated(a.produced, |a.produced|); + assert Terminated(a.Produced(), None, |a.Produced()|); + TerminatedDefinesEnumerated(a.Produced(), |a.Produced()|); } // Potentially infinite enumerator type IEnumerator = Action<(), T> - // TODO: There's - type Enumerator = e: IEnumerator> | IsEnumerator(e) witness * class SeqEnumerator extends Action<(), Option> { @@ -189,25 +196,24 @@ module Enumerators { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> - && CanProduce(consumed, produced) + && CanProduce(history) decreases height, 0 { && this in Repr - && index == |consumed| - && CanProduce(consumed, produced) + && index == |history| + && CanProduce(history) } constructor(s: seq) ensures Valid() ensures fresh(Repr - {this}) - ensures produced == [] + ensures history == [] ensures elements == s { elements := s; index := 0; - consumed := []; - produced := []; + history := []; Repr := {this}; } @@ -222,19 +228,18 @@ module Enumerators { var index := |history|; var values := Math.Min(index, |elements|); var nones := index - values; - Produced(history) == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) + Outputs(history) == Seq.Map(x => Some(x), elements[..values]) + Seq.Repeat(None, nones) } method Invoke(t: ()) returns (r: Option) - requires Valid() - requires CanConsume(history, t) - modifies Repr - decreases height - ensures Valid() - ensures Repr <= old(Repr) - ensures history == old(history) + [(t, r)] - ensures CanProduce(history) + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { + assert Valid(); if index < |elements| { r := Some(elements[index]); } else { @@ -243,14 +248,18 @@ module Enumerators { index := index + 1; Update((), r); + assert this in Repr; + assert index == |history|; + // TODO: Need some resuable lemmas relating Inputs() and Outputs()? + assert CanProduce(history); } } lemma SeqEnumeratorIsEnumerator(e: SeqEnumerator) ensures IsEnumerator(e) { - forall consumed, produced | e.CanProduce(consumed, produced) - ensures Terminated(produced, None, |e.elements|) + forall history | e.CanProduce(history) + ensures Terminated(Outputs(history), None, |e.elements|) { } assert EnumerationBoundedBy(e, |e.elements|); @@ -271,7 +280,7 @@ module Enumerators { && this in Repr && 0 <= index <= |elements| && |history| == index - && Produced(history) == elements[0..index] + && Produced() == elements[0..index] } constructor(s: seq) @@ -295,19 +304,18 @@ module Enumerators { ghost predicate CanProduce(history: seq<((), T)>) decreases height { - |history| <= |elements| && Produced(history) == elements[..|history|] + |history| <= |elements| && Outputs(history) == elements[..|history|] } method Invoke(t: ()) returns (r: T) - requires Valid() - requires CanConsume(history, t) - modifies Repr - decreases height - ensures Valid() - ensures Repr <= old(Repr) - ensures history == old(history) + [(t, r)] - ensures CanProduce(history) + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { + assert Valid(); r := elements[index]; index := index + 1; @@ -376,15 +384,15 @@ module Enumerators { method IEnumeratorExample() { var e: Action<(), int> := new SeqIEnumerator([1, 2, 3, 4, 5]); var x := e.Invoke(()); - assert e.produced == [1]; + assert e.Produced() == [1]; x := e.Invoke(()); - assert e.produced == [1, 2]; + assert e.Produced() == [1, 2]; x := e.Invoke(()); - assert e.produced == [1, 2, 3]; + assert e.Produced() == [1, 2, 3]; x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4]; + assert e.Produced() == [1, 2, 3, 4]; x := e.Invoke(()); - assert e.produced == [1, 2, 3, 4, 5]; + assert e.Produced() == [1, 2, 3, 4, 5]; } method Main() { diff --git a/src/Actions/FunctionAction.dfy b/src/Actions/FunctionAction.dfy index f4c4531e..5d8c825d 100644 --- a/src/Actions/FunctionAction.dfy +++ b/src/Actions/FunctionAction.dfy @@ -17,50 +17,47 @@ module Functions { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> - && CanProduce(consumed, produced) + && CanProduce(history) decreases height, 0 { && this in Repr - && CanProduce(consumed, produced) - && produced == Seq.Map(f, consumed) + && CanProduce(history) + && Produced() == Seq.Map(f, Consumed()) } constructor(f: T -> R) ensures Valid() ensures this.f == f ensures fresh(Repr) - ensures consumed == [] + ensures history == [] { this.f := f; - consumed := []; - produced := []; + history := []; Repr := {this}; } - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) - requires CanProduce(consumed, produced) + ghost predicate CanConsume(history: seq<(T, R)>, next: T) + requires CanProduce(history) decreases height { f.requires(next) } - ghost predicate CanProduce(consumed: seq, produced: seq) + ghost predicate CanProduce(history: seq<(T, R)>) decreases height { - && (forall t <- consumed :: f.requires(t)) - && produced == Seq.Map(f, consumed) + forall e <- history :: f.requires(e.0) && e.1 == f(e.0) } method Invoke(t: T) returns (r: R) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases height - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { + assert Valid(); r := f(t); Update(t, r); @@ -70,11 +67,10 @@ module Functions { method Example() { var doubler := new FunctionAction(v => v + v); - assert doubler.consumed == []; - assert doubler.produced == []; + assert doubler.history == []; var x := doubler.Invoke(2); - assert doubler.produced == [x]; + assert doubler.Produced() == [x]; assert [x] == [4]; assert x == 4; } diff --git a/src/Actions/NoOpEnumerator.dfy b/src/Actions/NoOpEnumerator.dfy index 652f80ca..6dbf6266 100644 --- a/src/Actions/NoOpEnumerator.dfy +++ b/src/Actions/NoOpEnumerator.dfy @@ -1,4 +1,3 @@ - include "Enumerators.dfy" module NoOp { @@ -19,60 +18,55 @@ module NoOp { reads this, Repr ensures Valid() ==> this in Repr ensures Valid() ==> - && |consumed| == |produced| - && CanProduce(consumed, produced) + && CanProduce(history) decreases height, 0 { && this in Repr && ValidComponent(wrapped) - && CanProduce(consumed, produced) - && consumed == wrapped.consumed - && produced == wrapped.produced + && CanProduce(history) + && history == wrapped.history } constructor(wrapped: Action) requires wrapped.Valid() - requires wrapped.consumed == [] && wrapped.produced == [] + requires wrapped.history == [] ensures Valid() { this.wrapped := wrapped; - consumed := []; - produced := []; + history := []; Repr := {this} + wrapped.Repr; height := wrapped.height + 1; new; } - ghost predicate CanConsume(consumed: seq, produced: seq, next: T) - requires |consumed| == |produced| - requires CanProduce(consumed, produced) + ghost predicate CanConsume(history: seq<(T, R)>, next: T) + requires CanProduce(history) decreases height { - wrapped.CanConsume(consumed, produced, next) + wrapped.CanConsume(history, next) } - ghost predicate CanProduce(consumed: seq, produced: seq) - ensures CanProduce(consumed, produced) ==> |consumed| == |produced| + ghost predicate CanProduce(history: seq<(T, R)>) decreases height { - wrapped.CanProduce(consumed, produced) + wrapped.CanProduce(history) } method Invoke(t: T) returns (r: R) - requires Valid() - requires CanConsume(consumed, produced, t) - modifies Repr - decreases height - ensures Valid() - ensures fresh(Repr - old(Repr)) - ensures consumed == old(consumed) + [t] - ensures produced == old(produced) + [r] + requires Requires(t) + reads Reads(t) + modifies Modifies(t) + decreases Decreases(t).Ordinal() + ensures Ensures(t, r) + ensures EnsuresTwostate(t) { + assert CanConsume(history, t); r := wrapped.Invoke(t); Repr := {this} + wrapped.Repr; Update(t, r); + assert Valid(); } } @@ -86,13 +80,13 @@ module NoOp { // Not sure if there's a more reusable workaround to the problem (which is going to recur frequently in this library) var noopAsAction: Action<(), Option> := noop; - forall consumed, produced, next | noop.CanProduce(consumed, produced) ensures noopAsAction.CanConsume(consumed, produced, next) { - assert noop.CanConsume(consumed, produced, next); + forall history, next | noop.CanProduce(history) ensures noopAsAction.CanConsume(history, next) { + assert noop.CanConsume(history, next); } var limit := EnumerationBound(noop.wrapped); - forall consumed: seq<()>, produced: seq> | noopAsAction.CanProduce(consumed, produced) ensures exists n: nat | n <= limit :: Terminated(produced, None, n) { - assert noop.CanProduce(consumed, produced); + forall history | noopAsAction.CanProduce(history) ensures exists n: nat | n <= limit :: Terminated(Outputs(history), None, n) { + assert noop.CanProduce(history); } assert EnumerationBoundedBy(noop, limit); } From abe92336204f05560b2aa87fe1d064d934f06bb2 Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 21 Feb 2024 16:36:39 -0800 Subject: [PATCH 65/68] Improving combinator specs --- src/Actions/Combinators.dfy | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Actions/Combinators.dfy b/src/Actions/Combinators.dfy index ec1ee199..e9bda1d0 100644 --- a/src/Actions/Combinators.dfy +++ b/src/Actions/Combinators.dfy @@ -8,15 +8,15 @@ module ActionCombinators { import opened Enumerators method Function(f: T --> R) - returns (a: SimpleAction) + returns (a: Action) ensures fresh(a.Repr) ensures forall history: seq<(T, R)> | a.CanProduce(history), e <- history :: f.requires(e.0) && e.1 == f(e.0) method EnumeratorOfSeq(s: seq) returns (e: Enumerator) - ensures forall history: seq<((), Option)> | e.CanProduce(history) :: - Enumerated(Outputs(history)) == + ensures EnumeratesSeq(e, s) + ensures e.history == [] // a.k.a. Chain(first, second) // @@ -37,7 +37,8 @@ module ActionCombinators { requires second.Repr !! first.Repr // Produces Seq.Filter(what a produces, p) - // a has to be an Enumerator to ensure Invoke() eventually terminates + // a has to be an Enumerator rather than just an Action + // to ensure Invoke() eventually terminates method Filter(e: Enumerator, p: T -> bool) returns (filtered: Enumerator) @@ -48,7 +49,13 @@ module ActionCombinators { returns (nested: Enumerator) method ForEach(source: Enumerator, sink: Aggregator) + // Ensures that source is exhausted + // and that sink.Consumed() == Enumerated(source.Produced()) method Collect(source: Enumerator) returns (s: seq) + // Ensures that source is exhausted + // and that s == Enumerated(source.Produced()) + // Likely implemented by creating an ArrayAggregator sink + // and calling ForEach(source, sink) } \ No newline at end of file From 18a2ef4b5d84bc0b6e5ad3cf66d97b0e07e36ebc Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Wed, 21 Feb 2024 19:47:30 -0800 Subject: [PATCH 66/68] Replace IsSmallerThan with DecreasesTo, motivating example --- src/Actions/DecreasesClauses.dfy | 77 ++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Actions/DecreasesClauses.dfy b/src/Actions/DecreasesClauses.dfy index 3549f4fd..2c45a92b 100644 --- a/src/Actions/DecreasesClauses.dfy +++ b/src/Actions/DecreasesClauses.dfy @@ -4,15 +4,15 @@ module Termination { datatype ClauseTail = More(next: TerminationMetric) | Top datatype TerminationMetric = TerminationMetric(first: TMValue, rest: ClauseTail) { - predicate IsSmallerThan(other: TerminationMetric) { + predicate DecreasesTo(other: TerminationMetric) { if first == other.first then match (rest, other.rest) { - case (Top, _) => false - case (More(_), Top) => true - case (More(next), More(otherNext)) => next.IsSmallerThan(otherNext) + case (_, Top) => false + case (Top, More(_)) => true + case (More(next), More(otherNext)) => next.DecreasesTo(otherNext) } else - first.IsSmallerThan(other.first) + first.DecreasesTo(other.first) } ghost function {:axiom} Ordinal(): ORDINAL @@ -28,24 +28,45 @@ module Termination { function NatTerminationMetric(m: nat): TerminationMetric { TerminationMetric1(TMNat(m)) } - + // Assume a mapping exists from the DecreasesTo ordering onto the ordinals. + // This always exists, but is complicated to define concretely + // and technically has to be defined for a whole program. + // It's sound to just assume it exists to convince Dafny that + // `decreases terminationMetric.Ordinal()` is valid. lemma {:axiom} OrdinalOrdered(left: TerminationMetric, right: TerminationMetric) - requires left.IsSmallerThan(right) - ensures left.Ordinal() < right.Ordinal() + requires left.DecreasesTo(right) + ensures left.Ordinal() > right.Ordinal() + // Heterogeneous encoding of the essential features of individual + // decreases clause list elements. datatype TMValue = | TMNat(natValue: nat) | TMChar(charValue: nat) | TMSeq(seqValue: seq) - // TODO: etc + | TMDatatype(children: seq) + // TODO: All other supported kinds of Dafny values { - predicate IsSmallerThan(other: TMValue) { + predicate DecreasesTo(other: TMValue) { match (this, other) { - case (TMNat(left), TMNat(right)) => left < right - case (TMChar(left), TMChar(right)) => left < right - case (TMSeq(left), TMSeq(right)) => left < right // TODO: should be Seq.IsSubsequenceOf - // TODO: etc + // Simple well-ordered types + case (TMNat(left), TMNat(right)) => left > right + case (TMChar(left), TMChar(right)) => left > right + // TODO: etc. + // Other is a strict subsequence of this + case (TMSeq(left), TMSeq(right)) => + || (exists i | 0 <= i < |left| :: left[..i] == right) + || (exists i | 0 < i <= |left| :: left[i..] == right) + // This is a sequence and other is a datatype and structurally included + // (treating a sequence as a datatype with N children) + case (TMSeq(leftSeq), TMDatatype(_)) => + || other in leftSeq + // Structural inclusion inside a datatype + // TODO: Does other have to be a datatype too? + case (TMDatatype(leftChildren), _) => + || other in leftChildren + + // TODO: other cases case _ => false } } @@ -54,17 +75,25 @@ module Termination { module Example { - datatype D = Less(x: nat) | More(x: nat) + datatype Tree = Node(children: seq>) | Nil - function Foo(d: D): nat - decreases d - { - if d.More? then - Foo(Less(d.x)) + function Count(t: Tree): nat { + match t + case Node(children) => + // assert t decreases to children; + CountSum(children) + case Nil => + 0 + } + + function CountSum(children: seq>): nat { + if |children| == 0 then + 0 else - if d.x == 0 then - 42 - else - Foo(Less(d.x - 1)) + // assert children decreases to children[0]; + var firstCount := Count(children[0]); + // assert children decreases to children[1..]; + var restCount := CountSum(children[1..]); + firstCount + restCount } } \ No newline at end of file From fd231a127fffa372a6f492595c30805c12f41ceb Mon Sep 17 00:00:00 2001 From: Robin Salkeld Date: Thu, 22 Feb 2024 15:02:54 -0800 Subject: [PATCH 67/68] Tweaks --- src/Actions/DecreasesClauses.dfy | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Actions/DecreasesClauses.dfy b/src/Actions/DecreasesClauses.dfy index 2c45a92b..d939e494 100644 --- a/src/Actions/DecreasesClauses.dfy +++ b/src/Actions/DecreasesClauses.dfy @@ -29,15 +29,6 @@ module Termination { TerminationMetric1(TMNat(m)) } - // Assume a mapping exists from the DecreasesTo ordering onto the ordinals. - // This always exists, but is complicated to define concretely - // and technically has to be defined for a whole program. - // It's sound to just assume it exists to convince Dafny that - // `decreases terminationMetric.Ordinal()` is valid. - lemma {:axiom} OrdinalOrdered(left: TerminationMetric, right: TerminationMetric) - requires left.DecreasesTo(right) - ensures left.Ordinal() > right.Ordinal() - // Heterogeneous encoding of the essential features of individual // decreases clause list elements. datatype TMValue = @@ -55,8 +46,9 @@ module Termination { // TODO: etc. // Other is a strict subsequence of this case (TMSeq(left), TMSeq(right)) => - || (exists i | 0 <= i < |left| :: left[..i] == right) - || (exists i | 0 < i <= |left| :: left[i..] == right) + || (exists i | 0 <= i < |left| :: left[..i] == right) + || (exists i | 0 < i <= |left| :: left[i..] == right) + || (exists i, j | 0 <= i < j <= |left| :: left[..i] + left[j..] == right) // This is a sequence and other is a datatype and structurally included // (treating a sequence as a datatype with N children) case (TMSeq(leftSeq), TMDatatype(_)) => @@ -71,6 +63,18 @@ module Termination { } } } + + // TODO: prove DecreasesTo is a well-founded ordering + // (useful exercise and helps catch typos inconsistent with Dafny's ordering) + + // Assume a mapping exists from the DecreasesTo ordering onto the ordinals. + // This always exists, but is complicated to define concretely + // and technically has to be defined for a whole program. + // It's sound to just assume it exists to convince Dafny that + // `decreases terminationMetric.Ordinal()` is valid. + lemma {:axiom} OrdinalOrdered(left: TerminationMetric, right: TerminationMetric) + requires left.DecreasesTo(right) + ensures left.Ordinal() > right.Ordinal() } module Example { From be37f1015fbde71d76c69dbb9adf67360e9a7ee7 Mon Sep 17 00:00:00 2001 From: Siva Somayyajula Date: Wed, 6 Mar 2024 22:47:10 +0000 Subject: [PATCH 68/68] added new type characteristic to get things to check --- src/Collections/Sequences/MergeSort.dfy | 4 ++-- src/Collections/Sequences/Seq.dfy | 8 ++++---- src/Relations.dfy | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Collections/Sequences/MergeSort.dfy b/src/Collections/Sequences/MergeSort.dfy index 42be3a36..f4121e5f 100644 --- a/src/Collections/Sequences/MergeSort.dfy +++ b/src/Collections/Sequences/MergeSort.dfy @@ -12,7 +12,7 @@ module {:options "-functionSyntax:4"} Seq.MergeSort { import opened Relations //Splits a sequence in two, sorts the two subsequences using itself, and merge the two sorted sequences using `MergeSortedWith` - function MergeSortBy(a: seq, lessThanOrEq: (T, T) -> bool): (result :seq) + function MergeSortBy(a: seq, lessThanOrEq: (T, T) -> bool): (result :seq) requires TotalOrdering(lessThanOrEq) ensures multiset(a) == multiset(result) ensures SortedBy(result, lessThanOrEq) @@ -31,7 +31,7 @@ module {:options "-functionSyntax:4"} Seq.MergeSort { MergeSortedWith(leftSorted, rightSorted, lessThanOrEq) } - function {:tailrecursion} MergeSortedWith(left: seq, right: seq, lessThanOrEq: (T, T) -> bool) : (result :seq) + function {:tailrecursion} MergeSortedWith(left: seq, right: seq, lessThanOrEq: (T, T) -> bool) : (result :seq) requires SortedBy(left, lessThanOrEq) requires SortedBy(right, lessThanOrEq) requires TotalOrdering(lessThanOrEq) diff --git a/src/Collections/Sequences/Seq.dfy b/src/Collections/Sequences/Seq.dfy index 815970b9..df3a4414 100644 --- a/src/Collections/Sequences/Seq.dfy +++ b/src/Collections/Sequences/Seq.dfy @@ -728,7 +728,7 @@ module {:options "-functionSyntax:4"} Seq { } /* inv(b, xs) ==> inv(FoldLeft(f, b, xs), []). */ - lemma LemmaInvFoldLeft(inv: (B, seq) -> bool, + lemma LemmaInvFoldLeft(inv: (B, seq) -> bool, stp: (B, A, B) -> bool, f: (B, A) -> B, b: B, @@ -786,7 +786,7 @@ module {:options "-functionSyntax:4"} Seq { } /* inv([], b) ==> inv(xs, FoldRight(f, xs, b)) */ - lemma LemmaInvFoldRight(inv: (seq, B) -> bool, + lemma LemmaInvFoldRight(inv: (seq, B) -> bool, stp: (A, B, B) -> bool, f: (A, B) -> B, b: B, @@ -833,7 +833,7 @@ module {:options "-functionSyntax:4"} Seq { } /* Proves that any two sequences that are sorted by a total order and that have the same elements are equal. */ - lemma SortedUnique(xs: seq, ys: seq, R: (T, T) -> bool) + lemma SortedUnique(xs: seq, ys: seq, R: (T, T) -> bool) requires SortedBy(xs, R) requires SortedBy(ys, R) requires TotalOrdering(R) @@ -853,7 +853,7 @@ module {:options "-functionSyntax:4"} Seq { } /* Converts a set to a sequence that is ordered w.r.t. a given total order. */ - function SetToSortedSeq(s: set, R: (T, T) -> bool): (xs: seq) + function SetToSortedSeq(s: set, R: (T, T) -> bool): (xs: seq) requires TotalOrdering(R) ensures multiset(s) == multiset(xs) ensures SortedBy(xs, R) diff --git a/src/Relations.dfy b/src/Relations.dfy index 9112a83b..05ddc704 100644 --- a/src/Relations.dfy +++ b/src/Relations.dfy @@ -92,7 +92,7 @@ module {:options "-functionSyntax:4"} Relations { max in s && forall x | x in s && R(max, x) :: R(x, max) } - lemma LemmaNewFirstElementStillSortedBy(x: T, s: seq, lessThan: (T, T) -> bool) + lemma LemmaNewFirstElementStillSortedBy(x: T, s: seq, lessThan: (T, T) -> bool) requires SortedBy(s, lessThan) requires |s| == 0 || lessThan(x, s[0]) requires TotalOrdering(lessThan)