
module Bag

  use int.Int

  type bag 'a = 'a -> int

  predicate (==) (b1 b2: bag 'a) =
    forall x: 'a. b1 x = b2 x

  let constant empty : bag 'a =
    fun _ -> 0

  let ghost function add (e: 'a) (b: bag 'a): bag 'a =
    fun x -> if pure {x = e} then b x + 1 else b x

  let ghost function remove (e: 'a) (b: bag 'a): bag 'a =
    fun x -> if pure {x = e} then b x - 1 else b x

end

module BagSpec

  use int.Int
  use Bag

  type t 'a = private {
          mutable size: int;
    ghost mutable contents: bag 'a;
  } invariant { 0 <= size }

  val create () : t 'a
    ensures { result.size = 0 }
    ensures { result.contents == Bag.empty }

  val clear (t: t 'a) : unit
    writes  { t.size, t.contents }
    ensures { t.size = 0 }
    ensures { t.contents == Bag.empty }

  val add (t: t 'a) (x: 'a) : unit
    writes  { t.size, t.contents }
    ensures { t.size = old t.size + 1 }
    ensures { t.contents == Bag.add x (old t.contents) }

end

module ResizableArraySpec

  use int.Int
  use map.Map
  use map.Const

  type rarray 'a = private {
    ghost mutable len: int;
    ghost mutable data: map int 'a;
  } invariant { 0 <= len }

  function ([]) (r: rarray 'a) (i: int) : 'a = Map.get r.data i

  val function ([<-]) (r: rarray 'a) (i: int) (v: 'a) : rarray 'a
    ensures { result.len = r.len }
    ensures { result.data = Map.set r.data i v }

  val length (r: rarray 'a) : int
    ensures { result = r.len }

  val make (len: int) (dummy: 'a) : rarray 'a
    requires { 0 <= len }
    ensures  { result.len = len }
    ensures  { result.data = Const.const dummy }

  val ([]) (r: rarray 'a) (i: int) : 'a
    requires { 0 <= i < r.len }
    ensures  { result = r[i] }

  val ([]<-) (r: rarray 'a) (i: int) (v: 'a) : unit
    requires { 0 <= i < r.len }
    writes   { r.data }
    ensures  { r = (old r)[i <- v] }

  val resize (r: rarray 'a) (len: int) : unit
    requires { 0 <= len }
    writes   { r.len, r.data }
    ensures  { r.len = len }
    ensures  { forall i: int.
               0 <= i < old r.len -> i < len -> r[i] = (old r)[i] }

end

module BagImpl

  use int.Int
  use Bag
  use import ResizableArraySpec as R
  use map.Map as Map
  use int.NumOf as NumOf
  use null.Null as Null

  function numof (r: rarray (Null.t 'a)) (x: 'a) (l u: int) : int =
    NumOf.numof (fun i -> (Map.get r.R.data i).Null.v = Null.Value x) l u

  type t 'a = {
    mutable       size:     int;
                  data:     rarray (Null.t 'a);
    mutable ghost contents: bag 'a;
  }
    invariant { 0 <= size = data.len }
    invariant { forall i: int. 0 <= i < size -> not (Null.is_null data[i]) }
    invariant { forall x: 'a. contents x = numof data x 0 size }
    by { size = 0; data = R.make 0 (Null.create_null ()); contents = empty }

  let create () : t 'a
    ensures { result.size = 0 }
    ensures { result.contents == Bag.empty }
  =
    let null = Null.create_null () : Null.t 'a in
    { size = 0; data = make 0 null; contents = Bag.empty }

  let clear (t: t 'a) : unit
    ensures { t.size = 0 }
    ensures { t.contents == Bag.empty }
  =
    resize t.data 0;
    t.size <- 0;
    t.contents <- Bag.empty

  let add (t: t 'a) (x: 'a) : unit
    ensures { t.size = old t.size + 1 }
    ensures { t.contents == Bag.add x (old t.contents) }
  =
    let n = t.size in
    t.size <- n + 1;
    resize t.data (n + 1);
    assert { forall v: 'a. t.contents v = numof t.data v 0 n by
      forall i: int. 0 <= i < n -> (t.data[i].Null.v = Null.Value v
                          <-> (old t.data)[i].Null.v = Null.Value v) };
    t.data[n] <- Null.create x;
    assert { forall v: 'a. t.contents v = numof t.data v 0 n };
    t.contents <- Bag.add x t.contents

  let get (t: t 'a) (i: int) : 'a
    requires { 0 <= i < t.size }
    ensures  { Null.Value result = t.data[i].Null.v }
  =
    Null.get t.data[i]

  let remove (t: t 'a) (i: int) : unit
    requires { 0 <= i < t.size }
    ensures  { t.size = old t.size - 1 }
    ensures  { forall x: 'a. Null.Value x = old t.data[i].Null.v ->
               t.contents == Bag.remove x (old t.contents) }
  =
    let n = t.size - 1 in
    let ghost x = Null.get t.data[i] in
    t.contents <- Bag.remove x t.contents;
    if i < n then t.data[i] <- t.data[n];
    assert { forall v: 'a. t.contents v = numof t.data v 0 n by
      numof t.data v 0 n = numof (old t.data) v 0 i +
                           numof (old t.data) v (i+1) (n+1) by
        numof t.data v 0 i = numof (old t.data) v 0 i /\
        numof t.data v i n = numof (old t.data) v (i+1) (n+1) by
          numof t.data v (i+1) n = numof (old t.data) v (i+1) n };
    resize t.data n;
    t.size <- n

  clone BagSpec with
    type t = t,
    val create = create,
    val clear = clear,
    val add = add

end

module Harness

  use int.Int
  use Bag
  use BagImpl

  let test1 () =
    let b = create () in
    add b 17;
    add b 42;
    assert { b.contents 42 = 1 };
    add b 55;
    add b 89;
    add b 42;
    assert { b.contents 42 = 2 };
    ()

end
