/* CS:5810 Formal Methods in Software Engineering Fall 2020 The University of Iowa Instructor: Cesare Tinelli */ // Parametric FIFO queue // using two internal sequences to store queue elements // The use of two sequences, one to enqueue to and one dequeue from, // allow the implementation of a enqueue and a dequeue operation // whose runtine, in _both_ cases, is linear in the size of the queue. class Queue { // Elements of the queue in order from less to most recent ghost var Content: seq; var incoming: seq; // enqueued values are inserted at the front here var outgoing: seq; // values are dequeued from the front here // Specification function: // returns reverse of input sequence function reverse(s: seq): seq decreases s; ensures |s| == |reverse(s)|; ensures forall i:nat :: i < |s| ==> s[i] == reverse(s)[|s|-1-i]; { if s == [] then [] else reverse(s[1..]) + s[0..1] } // Class invariant predicate predicate Valid() reads this; { Content == outgoing + reverse(incoming) } // Empty queue constructor // Starts with an empty queue constructor () ensures Valid(); ensures Content == []; { incoming := []; outgoing := []; Content := []; } // Enqueuing operation method enqueue(e: T) modifies this; requires Valid(); ensures Content == old(Content) + [e]; ensures Valid(); { incoming := [e] + incoming; // constant time operation // (because the cost of (s1 + s2) is linear // in the size of s1) Content := Content + [e]; } // Axiliary (private) method: // it progressively moves any elements from the front of incoming // to the front of outgoing // Note that its contract is expressed in terms of the concrete state // That is OK since this is a private method method moveFromIncoming() modifies this; requires Valid(); requires outgoing == []; ensures incoming == []; ensures outgoing == reverse(old(incoming)); ensures Valid(); { while incoming != [] decreases |incoming|; invariant reverse(incoming) + outgoing == reverse(old(incoming)); { outgoing := incoming[0..1] + outgoing; // all constant time operations incoming := incoming[1..]; // all constant time operations } Content := outgoing; } // Front operation: it returns the oldest element of the queue // It does not modify the queue method front() returns (e:T) modifies this; requires Valid(); requires Content != []; ensures Valid(); ensures Content == old(Content); ensures e == Content[0]; { if outgoing == [] { moveFromIncoming(); } e := outgoing[0]; // constant time operation } // Dequeuing operation: it removes and returns the oldest element of the queue method dequeue() returns (e:T) modifies this; requires Valid(); requires Content != []; ensures Content == old(Content[1..]); ensures [e] + Content == old(Content); ensures Valid(); { if outgoing == [] { moveFromIncoming(); } e := outgoing[0]; // constant time operation outgoing := outgoing[1..]; // constant time operation Content := Content[1..]; } } method test2 () { var q := new Queue(); assert q.Content == []; q.enqueue(1); assert q.Content == [1]; q.enqueue(2); assert q.Content == [1,2]; q.enqueue(3); assert q.Content == [1,2,3]; q.enqueue(2); assert q.Content == [1,2,3,2]; q.enqueue(4); assert q.Content == [1,2,3,2,4]; var f := q.front(); assert f == 1 && q.Content == [1,2,3,2,4]; f := q.dequeue(); assert q.Content == [2,3,2,4]; q.enqueue(5); assert q.Content == [2,3,2,4,5]; }