module LowStar.Lib.AssocList

/// A Low*, stateful associative list that exposes a map-like interface.

module B = LowStar.Buffer
module HS = FStar.HyperStack
module ST = FStar.HyperStack.ST

module M = FStar.Map

open FStar.HyperStack.ST
open LowStar.BufferOps

/// Types, invariants
/// -----------------

val t: eqtype -> Type0 -> Type0

/// Rather than force clients to provide a dummy value for the type (which would
/// allow us to do something like initialize an empty map with ``restrict (const
/// default)``, we use an option.
let map (k: eqtype) v =
  M.t k (option v)

/// Reflecting a stateful, imperative map as a functional one in a given heap.
val v: #t_k:eqtype -> #t_v:Type0 -> h:HS.mem -> ll:t t_k t_v -> GTot (map t_k t_v)

val invariant: #t_k:eqtype -> #t_v:Type0 -> h:HS.mem -> ll:t t_k t_v -> Type0

val region_of: #t_k:eqtype -> #t_v:Type0 -> ll:t t_k t_v -> GTot B.loc

val frame: #t_k:eqtype -> #t_v:Type0 -> ll:t t_k t_v -> l:B.loc -> h0:HS.mem -> h1: HS.mem -> Lemma
  (requires
    invariant h0 ll /\
    B.loc_disjoint l (region_of ll) /\
    B.modifies l h0 h1)
  (ensures
    invariant h1 ll /\
    v h1 ll == v h0 ll)
  [ SMTPatOr [
      [ SMTPat (invariant h1 ll); SMTPat (B.modifies l h0 h1) ];
      [ SMTPat (v h1 ll); SMTPat (B.modifies l h0 h1) ];
    ]]

/// Creating an imperative map
/// --------------------------

/// We completely eliminate the notion of footprint here; every modifies clause
/// from this module will henceforth refer to ``region_of ll`` -- up to the
/// client to maintain suitable freshness, disjointness, etc. invariants for
/// ``loc_all_regions_from false r``.
val create_in: #t_k:eqtype -> #t_v:Type -> r:HS.rid -> ST (t t_k t_v)
  (requires fun h0 ->
    ST.is_eternal_region r)
  (ensures fun h0 ll h1 ->
    invariant h1 ll /\
    B.(modifies loc_none h0 h1) /\
    v h1 ll == M.const None /\
    region_of ll == B.(loc_all_regions_from false r))


/// Find
/// ----

val find (#t_k: eqtype) (#t_v: Type0) (ll: t t_k t_v) (k: t_k):
  Stack (option t_v)
    (requires fun h0 ->
      invariant h0 ll)
    (ensures fun h0 x h1 ->
      let m: map t_k t_v = v h0 ll in
      h0 == h1 /\
      x == M.sel m k)

/// Adding elements
/// ---------------

val add (#t_k: eqtype) (#t_v: Type0) (ll: t t_k t_v) (k: t_k) (x: t_v):
  ST unit
    (requires fun h0 ->
      invariant h0 ll)
    (ensures fun h0 _ h1 ->
      B.modifies (region_of ll) h0 h1 /\
      invariant h1 ll /\
      v h1 ll == M.upd (v h0 ll) k (Some x))

/// Removing elements
/// -----------------

val remove_all (#t_k: eqtype)
  (#t_v: Type0)
  (ll: t t_k t_v)
  (k: t_k):
  ST unit
    (requires fun h0 ->
      invariant h0 ll)
    (ensures fun h0 _ h1 ->
      B.modifies (region_of ll) h0 h1 /\
      invariant h1 ll /\
      v h1 ll == M.upd (v h0 ll) k None)

/// Clearing (resetting)
/// --------------------

val clear (#t_k: eqtype)
  (#t_v: Type0)
  (ll: t t_k t_v):
  ST unit
    (requires fun h0 ->
      invariant h0 ll)
    (ensures fun h0 _ h1 ->
      B.modifies (region_of ll) h0 h1 /\
      invariant h1 ll /\
      v h1 ll == M.const None)

/// Freeing the resource
/// --------------------

val free (#t_k: eqtype)
  (#t_v: Type0)
  (ll: t t_k t_v):
  ST unit
    (requires fun h0 ->
      invariant h0 ll)
    (ensures fun h0 _ h1 ->
      B.modifies (region_of ll) h0 h1)
