
(** {1 A certified WP calculus} *)

(** {2 A simple imperative language, syntax and semantics} *)

theory Imp

(** terms and formulas *)

type datatype = Tint | Tbool

type operator = Oplus | Ominus | Omult | Ole

type ident = int

type term =
  | Tconst int
  | Tvar ident
  | Tderef ident
  | Tbin term operator term

type fmla =
  | Fterm term
  | Fand fmla fmla
  | Fnot fmla
  | Fimplies fmla fmla
  | Flet ident term fmla
  | Fforall ident datatype fmla

use int.Int
use bool.Bool

type value =
  | Vint int
  | Vbool bool

use map.Map as IdMap
type env = IdMap.map ident value

(** semantics of formulas *)

function eval_bin (x:value) (op:operator) (y:value) : value =
  match x,y with
  | Vint x,Vint y ->
     match op with
     | Oplus -> Vint (x+y)
     | Ominus -> Vint (x-y)
     | Omult -> Vint (x*y)
     | Ole -> Vbool (if x <= y then True else False)
     end
  | _,_ -> Vbool False
  end

function get_env (i:ident) (e:env) : value = IdMap.get e i

function eval_term (sigma:env) (pi:env) (t:term) : value =
  match t with
  | Tconst n -> Vint n
  | Tvar id -> get_env id pi
  | Tderef id -> get_env id sigma
  | Tbin t1 op t2 ->
     eval_bin (eval_term sigma pi t1) op (eval_term sigma pi t2)
  end

predicate eval_fmla (sigma:env) (pi:env) (f:fmla) =
  match f with
  | Fterm t -> eval_term sigma pi t = Vbool True
  | Fand f1 f2 -> eval_fmla sigma pi f1 /\ eval_fmla sigma pi f2
  | Fnot f -> not (eval_fmla sigma pi f)
  | Fimplies f1 f2 -> eval_fmla sigma pi f1 -> eval_fmla sigma pi f2
  | Flet x t f ->
      eval_fmla sigma (IdMap.set pi x (eval_term sigma pi t)) f
  | Fforall x Tint f ->
     forall n:int. eval_fmla sigma (IdMap.set pi x (Vint n)) f
  | Fforall x Tbool f ->
     forall b:bool.
        eval_fmla sigma (IdMap.set pi x (Vbool b)) f
  end

(** substitution of a reference `r` by a logic variable `v`
   warning: proper behavior only guaranted if `v` is fresh *)

let rec function subst_term (e:term) (r:ident) (v:ident) : term =
  match e with
  | Tconst _ -> e
  | Tvar _ -> e
  | Tderef x -> if r=x then Tvar v else e
  | Tbin e1 op e2 -> Tbin (subst_term e1 r v) op (subst_term e2 r v)
  end

predicate fresh_in_term (id:ident) (t:term) =
  match t with
  | Tconst _ -> true
  | Tvar v -> id <> v
  | Tderef _ -> true
  | Tbin t1 _ t2 -> fresh_in_term id t1 /\ fresh_in_term id t2
  end

lemma eval_subst_term:
  forall sigma pi:env, e:term, x:ident, v:ident.
    fresh_in_term v e ->
    eval_term sigma pi (subst_term e x v) =
    eval_term (IdMap.set sigma x (IdMap.get pi v)) pi e

lemma eval_term_change_free :
  forall t:term, sigma pi:env, id:ident, v:value.
    fresh_in_term id t ->
    eval_term sigma (IdMap.set pi id v) t = eval_term sigma pi t

predicate fresh_in_fmla (id:ident) (f:fmla) =
  match f with
  | Fterm e -> fresh_in_term id e
  | Fand f1 f2   | Fimplies f1 f2 ->
      fresh_in_fmla id f1 /\ fresh_in_fmla id f2
  | Fnot f -> fresh_in_fmla id f
  | Flet y t f -> id <> y /\ fresh_in_term id t /\ fresh_in_fmla id f
  | Fforall y _ f -> id <> y /\ fresh_in_fmla id f
  end

let rec function subst (f:fmla) (x:ident) (v:ident) : fmla =
  match f with
  | Fterm e -> Fterm (subst_term e x v)
  | Fand f1 f2 -> Fand (subst f1 x v) (subst f2 x v)
  | Fnot f -> Fnot (subst f x v)
  | Fimplies f1 f2 -> Fimplies (subst f1 x v) (subst f2 x v)
  | Flet y t f -> Flet y (subst_term t x v) (subst f x v)
  | Fforall y ty f -> Fforall y ty (subst f x v)
  end


lemma eval_subst:
  forall f:fmla, sigma pi:env, x:ident, v:ident.
    fresh_in_fmla v f ->
    (eval_fmla sigma pi (subst f x v) <->
     eval_fmla (IdMap.set sigma x (IdMap.get pi v)) pi f)

lemma eval_swap:
  forall f:fmla, sigma pi:env, id1 id2:ident, v1 v2:value.
    id1 <> id2 ->
    (eval_fmla sigma (IdMap.set (IdMap.set pi id1 v1) id2 v2) f <->
    eval_fmla sigma (IdMap.set (IdMap.set pi id2 v2) id1 v1) f)

lemma eval_change_free :
  forall f:fmla, sigma pi:env, id:ident, v:value.
    fresh_in_fmla id f ->
    (eval_fmla sigma (IdMap.set pi id v) f <-> eval_fmla sigma pi f)

(* statements *)

type stmt =
  | Sskip
  | Sassign ident term
  | Sseq stmt stmt
  | Sif term stmt stmt
  | Sassert fmla
  | Swhile term fmla stmt

lemma check_skip:
  forall s:stmt. s=Sskip \/s<>Sskip

(** small-steps semantics for statements *)

inductive one_step env env stmt env env stmt =

  | one_step_assign:
      forall sigma pi:env, x:ident, e:term.
        one_step sigma pi (Sassign x e)
                 (IdMap.set sigma x (eval_term sigma pi e)) pi
                 Sskip

  | one_step_seq:
      forall sigma pi sigma' pi':env, i1 i1' i2:stmt.
        one_step sigma pi i1 sigma' pi' i1' ->
          one_step sigma pi (Sseq i1 i2) sigma' pi' (Sseq i1' i2)

  | one_step_seq_skip:
      forall sigma pi:env, i:stmt.
        one_step sigma pi (Sseq Sskip i) sigma pi i

  | one_step_if_true:
      forall sigma pi:env, e:term, i1 i2:stmt.
        eval_term sigma pi e = (Vbool True) ->
          one_step sigma pi (Sif e i1 i2) sigma pi i1

  | one_step_if_false:
      forall sigma pi:env, e:term, i1 i2:stmt.
        eval_term sigma pi e = (Vbool False) ->
          one_step sigma pi (Sif e i1 i2) sigma pi i2

  | one_step_assert:
      forall sigma pi:env, f:fmla.
        eval_fmla sigma pi f ->
          one_step sigma pi (Sassert f) sigma pi Sskip

  | one_step_while_true:
      forall sigma pi:env, e:term, inv:fmla, i:stmt.
        eval_fmla sigma pi inv ->
        eval_term sigma pi e = (Vbool True) ->
          one_step sigma pi (Swhile e inv i) sigma pi (Sseq i (Swhile e inv i))

  | one_step_while_false:
      forall sigma pi:env, e:term, inv:fmla, i:stmt.
        eval_fmla sigma pi inv ->
        eval_term sigma pi e = (Vbool False) ->
          one_step sigma pi (Swhile e inv i) sigma pi Sskip

(***

  lemma progress:
    forall s:state, i:stmt.
      i <> Sskip ->
      exists s':state, i':stmt. one_step s i s' i'

*)

 (** many steps of execution *)

 inductive many_steps env env stmt env env stmt int =
   | many_steps_refl:
     forall sigma pi:env, i:stmt. many_steps sigma pi i sigma pi i 0
   | many_steps_trans:
     forall sigma1 pi1 sigma2 pi2 sigma3 pi3:env, i1 i2 i3:stmt, n:int.
       one_step sigma1 pi1 i1 sigma2 pi2 i2 ->
       many_steps sigma2 pi2 i2 sigma3 pi3 i3 n ->
       many_steps sigma1 pi1 i1 sigma3 pi3 i3 (n+1)

lemma steps_non_neg:
  forall sigma1 pi1 sigma2 pi2:env, i1 i2:stmt, n:int.
    many_steps sigma1 pi1 i1 sigma2 pi2 i2 n -> n >= 0

lemma many_steps_seq:
  forall sigma1 pi1 sigma3 pi3:env, i1 i2:stmt, n:int.
    many_steps sigma1 pi1 (Sseq i1 i2) sigma3 pi3 Sskip n ->
    exists sigma2 pi2:env, n1 n2:int.
      many_steps sigma1 pi1 i1 sigma2 pi2 Sskip n1 /\
      many_steps sigma2 pi2 i2 sigma3 pi3 Sskip n2 /\
      n = 1 + n1 + n2



predicate valid_fmla (p:fmla) = forall sigma pi:env. eval_fmla sigma pi p

(** {3 Hoare triples} *)

(** partial correctness *)
predicate valid_triple (p:fmla) (i:stmt) (q:fmla) =
    forall sigma pi:env. eval_fmla sigma pi p ->
      forall sigma' pi':env, n:int. many_steps sigma pi i sigma' pi' Sskip n ->
        eval_fmla sigma' pi' q

(*** total correctness *)
(***
predicate total_valid_triple (p:fmla) (i:stmt) (q:fmla) =
    forall s:state. eval_fmla s p ->
      exists s':state, n:int. many_steps s i s' Sskip n /\
        eval_fmla s' q
*)

end


theory TestSemantics

use Imp
use map.Const

function my_sigma : env = Const.const (Vint 0)
function my_pi : env = Const.const (Vint 42)

goal Test13 :
  eval_term my_sigma my_pi (Tconst 13) = Vint 13

goal Test42 :
  eval_term my_sigma my_pi (Tvar 0) = Vint 42

goal Test0 :
  eval_term my_sigma my_pi (Tderef 0) = Vint 0

goal Test55 :
  eval_term my_sigma my_pi (Tbin (Tvar 0) Oplus (Tconst 13)) = Vint 55

goal Ass42 :
    let x = 0 in
    forall sigma' pi':env.
      one_step my_sigma my_pi (Sassign x (Tconst 42)) sigma' pi' Sskip ->
        IdMap.get sigma' x = Vint 42

goal If42 :
    let x = 0 in
    forall sigma1 pi1 sigma2 pi2:env, i:stmt.
      one_step my_sigma my_pi
        (Sif (Tbin (Tderef x) Ole (Tconst 10))
             (Sassign x (Tconst 13))
             (Sassign x (Tconst 42)))
        sigma1 pi1 i ->
      one_step sigma1 pi1 i sigma2 pi2 Sskip ->
        IdMap.get sigma2 x = Vint 13

end

(** {2 Hoare logic} *)

theory HoareLogic

use Imp


(** Hoare logic rules (partial correctness) *)

lemma consequence_rule:
  forall p p' q q':fmla, i:stmt.
  valid_fmla (Fimplies p' p) ->
  valid_triple p i q ->
  valid_fmla (Fimplies q q') ->
  valid_triple p' i q'

lemma skip_rule:
  forall q:fmla. valid_triple q Sskip q

lemma assign_rule:
  forall q:fmla, x id:ident, e:term.
  fresh_in_fmla id q ->
  valid_triple (Flet id e (subst q x id)) (Sassign x e) q

lemma seq_rule:
  forall p q r:fmla, i1 i2:stmt.
  valid_triple p i1 r /\ valid_triple r i2 q ->
  valid_triple p (Sseq i1 i2) q

lemma if_rule:
  forall e:term, p q:fmla, i1 i2:stmt.
  valid_triple (Fand p (Fterm e)) i1 q /\
  valid_triple (Fand p (Fnot (Fterm e))) i2 q ->
  valid_triple p (Sif e i1 i2) q

lemma assert_rule:
  forall f p:fmla. valid_fmla (Fimplies p f) ->
  valid_triple p (Sassert f) p

lemma assert_rule_ext:
  forall f p:fmla.
  valid_triple (Fimplies f p) (Sassert f) p

lemma while_rule:
  forall e:term, inv:fmla, i:stmt.
  valid_triple (Fand (Fterm e) inv) i inv ->
  valid_triple inv (Swhile e inv i) (Fand (Fnot (Fterm e)) inv)

lemma while_rule_ext:
  forall e:term, inv inv':fmla, i:stmt.
  valid_fmla (Fimplies inv' inv) ->
  valid_triple (Fand (Fterm e) inv') i inv' ->
  valid_triple inv' (Swhile e inv i) (Fand (Fnot (Fterm e)) inv')

(*** frame rule ? *)

end

(** {2 WP calculus} *)

module WP

use Imp

use set.Fset as Set
clone set.SetApp as S with type elt = ident, val eq = Int.(=)

predicate assigns (sigma:env) (a:Set.fset ident) (sigma':env) =
  forall i:ident. not (Set.mem i a) ->
    IdMap.get sigma i = IdMap.get sigma' i

lemma assigns_refl:
  forall sigma:env, a:Set.fset ident. assigns sigma a sigma

lemma assigns_trans:
  forall sigma1 sigma2 sigma3:env, a:Set.fset ident.
    assigns sigma1 a sigma2 /\ assigns sigma2 a sigma3 ->
    assigns sigma1 a sigma3

lemma assigns_union_left:
  forall sigma sigma':env, s1 s2:Set.fset ident.
    assigns sigma s1 sigma' -> assigns sigma (Set.union s1 s2) sigma'

lemma assigns_union_right:
  forall sigma sigma':env, s1 s2:Set.fset ident.
    assigns sigma s2 sigma' -> assigns sigma (Set.union s1 s2) sigma'


predicate stmt_writes (i:stmt) (w:Set.fset ident) =
  match i with
  | Sskip | Sassert _ -> true
  | Sassign id _ -> Set.mem id w
  | Sseq s1 s2 | Sif _ s1 s2 -> stmt_writes s1 w /\ stmt_writes s2 w
  | Swhile _ _ s -> stmt_writes s w
  end


  let rec compute_writes (s:stmt) : S.set
   ensures {
     forall sigma pi sigma' pi':env, n:int.
       many_steps sigma pi s sigma' pi' Sskip n ->
       assigns sigma result sigma' }
   variant { s }
  = match s with
    | Sskip -> S.empty ()
    | Sassign i _ -> S.singleton i
    | Sseq s1 s2 -> S.union (compute_writes s1) (compute_writes s2)
    | Sif _ s1 s2 -> S.union (compute_writes s1) (compute_writes s2)
    | Swhile _ _ s -> compute_writes s
    | Sassert _ -> S.empty ()
    end

  val fresh_from_fmla (q:fmla) : ident
    ensures { fresh_in_fmla result q }

  val abstract_effects (i:stmt) (f:fmla) : fmla
    ensures { forall sigma pi:env. eval_fmla sigma pi result ->
        eval_fmla sigma pi f /\
(***
        forall sigma':env, w:Set.set ident.
        stmt_writes i w /\ assigns sigma w sigma' ->
        eval_fmla sigma' pi result
*)
        forall sigma' pi':env, n:int.
           many_steps sigma pi i sigma' pi' Sskip n ->
           eval_fmla sigma' pi' result
     }

  use HoareLogic

  let rec wp (i:stmt) (q:fmla)
    ensures { valid_triple result i q }
    variant { i }
  = match i with
    | Sskip -> q
    | Sseq i1 i2 -> wp i1 (wp i2 q)
    | Sassign x e ->
        let id = fresh_from_fmla q in Flet id e (subst q x id)
    | Sif e i1 i2 ->
        Fand (Fimplies (Fterm e) (wp i1 q))
             (Fimplies (Fnot (Fterm e)) (wp i2 q))
    | Sassert f ->
       Fimplies f q (* liberal wp, no termination required *)
       (* Fand f q *) (* strict wp, termination required *)
    | Swhile e inv i ->
        Fand inv
          (abstract_effects i
            (Fand
                (Fimplies (Fand (Fterm e) inv) (wp i inv))
                (Fimplies (Fand (Fnot (Fterm e)) inv) q)))

    end


end



(***
Local Variables:
compile-command: "why3ide wp2.mlw"
End:
*)
