\message{OK, entering \string\batchmode...} \batchmode \overfullrule0pt \input enumerate \def\VC#1#2{{\enumerate\item #1\endenumerate\par}{$>\!\!>$\ #2\par}} %\parindent=0pt\parskip=0.5\baselineskip \let\maps\rightarrow \def\title{A Generic Stack Package} @*Stacks. The idea is to implement, and to prove part of, a simple stack package. We write a Larch trait for the stacks, we use terms of that trait to specify our package. In the package body we write down an explicit representation invariant |invar| and an explicit abstraction function |abstraction|, and we use them to prove the package \`a la Hoare. The package specification refers only to the abstract trait, and our software will automatically perform the translation required to introduce explicit invariants and abstraction functions in the package body. @*Theory of stacks. We use the following theory: %\input stacktrait.pretty @*Specification. We now use the |Stack_theory| trait to specify a package. The phrase |with trait Stack_theory| makes the |Stack_theory| trait visible and the phrase |based on stack| makes the sort |stack| from |Stack_theory| the abstract sort |stack'ABSTRACT|. We also use the |element| sort to represent the private type |element|. % ACK! What proof obligations does this give? In the package {\it specification}, all terms of type |stack| are specified with terms of type |Stacks_theory.stack|. In the package {\it body}, we will specify terms of type |stack| with terms of a sort corresponding to the private type of stack (in this instance, a record type). We will provide an abstraction function from terms of the record trait to terms of the stack trait, and an invariant function from terms of the record trait to |boolean|. @c@0 generic type element is private; length: natural := 100; package stacks is type stack is private; @/ overflow, underflow: exception; @# function empty_stack return stack; @# function pushit(s:stack; e:element) return stack; @# function popit(s:stack) return stack; @# function topit(s:stack) return element; @# function isEmpty(s:stack) return boolean; private subtype stack_range is natural range 0.. length - 1; type stack_array is array(stack_range) of element; type stack is record l: natural; -- length a: stack_array; --stack end record; end stacks; @*Implementation. We break the implementation into pieces so we can discuss them one at a time. Within the hidden part of the package, |stack| will stand for the concrete type (or sort), |stack'CONCRETE|. Outside, in the specification, |stack| will stand for the abstract type (or sort), |stack'ABSTRACT|. We use types in actual Ada code, and sorts in annotations and virtual functions. @c generic type element is private; length: natural := 100; package body stacks is @@; @@; @@; @@; @@; @@; end stacks; @ Here we make explicit the representation invariant |invar| and the abstraction function |abstraction|. These are virtual functions, and templates for them will be generated automatically from the package specification. These functions will appear in the annotations for the operations of the package, which will also have been translated from the package specification. The invariant requires only that the length be within sensible bounds. (I have ignored the problem of definedness throughout the example.) The abstraction function works by peeling off the top element, and pushing it onto the abstraction of what's left. This works all the way down to |s.l=0|, for which the abstraction is |new_stack|. The templates for |invar| and |abstraction| are generated automatically. @= @ To implement |empty_stack| we return a stack of length zero. @= function empty_stack return stack is empty: stack; begin empty.l := 0; return empty; end empty_stack; @*Proof of |empty_stack|. Here and elsewhere we will use the notation |@@return| to denote the return value. Predicate transformation of the postcondition leaves the following verification condition: \VC{|true|}{|invar(empty) and abstraction(empty)=new_stack|} We split and get \VC{|true|}{|invar(empty)|} and, expanding \VC{|true|}{|0<=empty.l and empty.l <= length|} and substituting for |empty.l| (on what basis?) \VC{|true|}{|0<=0 and 0 <= length|} from which \VC{|true|}{|true|} because of the property |0<=n| for any natural number |n|. The second verification condition is \VC{|true|}{|abstraction(empty)=new_stack|} We expand |abstraction|, spawning the new VC \VC{|true|}{|invar(empty)|} for which we can refer to the earlier proof. We are left with \VC{|true|}{|if empty.l=0 then new_stack else push(abstraction(empty[l=>l-1]),empty.a(empty.l)); end if = new_stack;|} Which rewrites to \VC{|true|}{|new_stack=new_stack|} @ Here we have to check for overflow, but there are really no surprises. @= function pushit(s:stack; e:element) return stack is t: stack; begin if s.l=length then raise overflow; end if; t.l := s.l+1; t.a := s.a; t.a(t.l) := e; return t; end empty_stack; @*Predicate transformation of |pushit|. Here we have ignored the possibility of abnormal termination. I've written intermediate conditions in between the statements to which they apply. The predicate transformation rules I've used are completely informal. @c function pushit(s:stack; e:element) return stack is t: stack; begin if s.l=length then raise overflow; end if; t.l := s.l+1; t.a := s.a; t.a(t.l) := e; return t; end empty_stack; @*Proof of |pushit|. We're going to make heavy use of a (nonexistent) theory of records. Record states are as in Anna; informally they behave just as you would think. We first rewrite the term involving |t| into a simpler and more useful form: $$\vbox{\noindent |t[l=>s.l+1;a=>s.a;a(l)=>e]| rewrites to |s[l=>l+1;a(l)=>e]|. }$$ After this simplification we have this verification condition: \VC{| invar(s)|\item |not (size(abstraction(s)) = length)|} {| s.l /= length and invar(s[l=>l+1;a(l)=>e]) and abstraction(s[l=>l+1;a(l)=>e])=push(abstraction(s),e) |} We'll tackle one conjunct at a time. @ {\it First conjunct.} We begin by proving a lemma about |size|: \VC{|invar(s)|} {|size(abstraction(s))|$\simeq$|s.l|} The proof is by type induction on the predefined type |natural|. We omit it here. Once we have the |size| lemma we rewrite the VC for the first conjunct as: \VC{| invar(s)|\item |not (s.l = length)|} {|s.l /= length|} The proof is by the laws (not stated) for |=| and |/=|. @ {\it Second Conjunct.} Again we apply the |size| lemma. \VC{| invar(s)|\item |not (s.l = length)|} {| invar(s[l=>l+1;a(l)=>e])|} Expanding the definition of |invar| in the hypothesis and the conclusion yields \VC{| 0<=s.l and s.l <= length|\item |not (s.l = length)|} {| 0<=s[l=>l+1;a(l)=>e].l and s[l=>l+1;a(l)=>e].l<=length |} and we again exploit the theory of records to rewrite as \VC{| 0<=s.l and s.l <= length|\item |not (s.l = length)|} {| 0<=s.l+1 and s.l+1<=length |} And the proof follows by |and|-splitting hypotheses and conclusion and by applying the laws of arithmetic. @ {\it Third conjunct.} This is the interesting part. We expand the definition of |abstraction| on the left hand side of the conclusion. This spawns a VC showing that the argument to |abstraction| satisfies |invariant|, but we did that earlier. \VC{| invar(s)|\item |not (size(abstraction(s)) = length)|} {| if s[l=>l+1;a(l)=>e].l=0 then new_stack else push(abstraction(s[l=>l+1;a(l)=>e;l=>l-1]), s[l=>l+1;a(l)=>e].a(s[l=>l+1;a(l)=>e].l)) end if =push(abstraction(s),e) |} and, rewriting according to the theory of records (especially |s[l=>l+1;a(l)=>e].a(s[l=>l+1;a(l)=>e].l)| rewrites to |s[l=>l+1;a(s.l+1)=>e].a(s.l+1)| which rewrites to |e|): \VC{| invar(s)|\item |not (size(abstraction(s)) = length)|} {| if s.l+1=0 then new_stack else push(abstraction(s[a(l+1)=>e]),e) end if =push(abstraction(s),e) |} Applying the laws of natural numbers we have \VC{| invar(s)|\item |not (size(abstraction(s)) = length)|} {| push(abstraction(s[a(l+1)=>e]),e) = push(abstraction(s),e) |} And now we show by a lemma that \VC{|invar(s)|\item |s.l/=length|} { |abstraction(s[a(l+1)=>e])|$\simeq$|abstraction(s)| } which is sufficient. The proof of the lemma is by induction over |l| (type induction of the predefined type |natural|). @ @= function popit(s:stack) return stack is t: stack; begin if s.l=0 then raise underflow; end if; t := s; t.l := t.l-1; return t; end popit; @ @= function topit (s:stack) return element is begin if s.l=0 then raise underflow; end if; return s.a(s.l); end topit; @ @= function isEmpty(s:stack) return boolean is begin return s.l=0; end isEmpty; @ Predicate transformation of isEmpty. @c function isEmpty(s:stack) return boolean is begin return s.l=0; end isEmpty; @ Proof of isEmpty. \VC{|invariant(s)|}{|s.l=0 =is_empty(abstraction(s))|} Expanding |abstraction(s)|, \VC{|invariant(s)|}{|s.l=0 =is_empty(if s.l=0 then nil else cons(abstraction(s[l=>l-1]),l.a(s.l-1)))|} Simplifying, \VC{|invariant(s)|}{|(s.l=0)=if s.l=0 then is_empty(nil) else is_empty(cons(abstraction(s[l=>l-1]),l.a(s.l-1)))|} which we can rewrite as \VC{|invariant(s)|}{|(s.l=0)=if s.l=0 then true else is_empty(cons(abstraction(s[l=>l-1]),l.a(s.l-1)))|} Simplifying, \VC{|invariant(s)|}{|true|} @*Index. This is an index of all the identifiers used in the program. The numbers are section numbers, not page numbers.