From iris.heap_lang Require Import lang proofmode notation.
Set Default Goal Selector "!".

Notation NONE := (InjL #()).
Notation NONEV := (InjLV #()).
Notation SOME x := (InjR x).
Notation SOMEV x := (InjRV x).

Section hw6.

Context `{!heapGS Σ}.

Fixpoint isList (l : val) (xs : list val) : iProp Σ :=
  match xs with
  | [] => ⌜l = NONEV⌝  (* NONEV = null pointer *)
  | x :: xs => ∃ (hd : loc) l', ⌜l = SOMEV (#hd)⌝ ∗
                                hd ↦ (x, l') ∗ isList l' xs
  end.

(* exercise: inc_spec (2 stars) *)
Definition inc : val :=
  rec: "inc" "l" :=
    match: "l" with
      NONE => #()
    | SOME "hd" => (* hd ↦ (h, t) *)
        let: "h" := Fst (! "hd") in
        let: "t" := Snd (! "hd") in
        "hd" <- ("h" + #1, "t");;
        "inc" "t"
    end.

Lemma inc_spec (l : val) (xs : list Z) :
  {{{ isList l ((λ x : Z, #x) <$> xs) }}}
    inc l
  {{{ RET #(); isList l ((λ x, #(x + 1)) <$> xs)}}}.
Proof.
  (**
    The proof proceeds by structural induction in [xs]. As [l] changes in each
    iteration, we must universally quantify over it to strengthen the induction
    hypothesis.
  *)
  generalize dependent l.
  induction xs as [|x xs' IH]; intros l.
  - (* Base Case: xs = [] *)
    iIntros "%Φ H Hpost".
    unfold inc.
    wp_rec.
    fold inc.
    simpl.
    iDestruct "H" as "%H".
    rewrite H.
    wp_match.
    iApply "Hpost".
    done.
  - (* Induction step: xs = x :: xs' *)
Admitted.

(* exercise: append_spec (2 stars) *)
Definition append : val :=
  rec: "append" "l1" "l2" :=
    match: "l1" with
      NONE => "l2"
    | SOME "hd" =>
        let: "x" := Fst (!"hd") in
        let: "l1'" := Snd (!"hd") in
        let: "r" := "append" "l1'" "l2" in
        "hd" <- ("x", "r");;
        SOME "hd"
    end.

Lemma append_spec (l1 l2 : val) (xs ys : list val) :
  {{{ isList l1 xs ∗ isList l2 ys }}}
    append l1 l2
  {{{ l, RET l; isList l (xs ++ ys) }}}.
Proof.
  generalize dependent l2. generalize dependent l1. generalize dependent ys.
  induction xs as [| x xs' IH]; simpl.
  (* exercise *)
Admitted.

(* exercise: drop_negative_pure (2 stars)
   Write a function drop_negative_pure that takes a [list Z] (i.e., a list of numbers)
   and removes its negative elements, returning a list that contains only
   the positive elements of the input. *)
Fixpoint drop_negative_pure (l : list Z) : list Z (* := ... *)
. Admitted.

Example drop_negative_pure_ex_1 : drop_negative_pure [] = [].
(* Proof. simpl. reflexivity. Qed. *) Admitted.

Example drop_negative_pure_pure_ex_2 : drop_negative_pure [1; -2; 3]%Z = [1; 3]%Z.
(* Proof. simpl. reflexivity. Qed. *) Admitted.

(* exercise: drop_negative_spec (3 stars)
   Here is a HeapLang function that drops the negative elements of a list: *)
Definition drop_negative : val :=
  rec: "drop_negative" "l" :=
    match: "l" with
      NONE => NONE
    | SOME "hd" =>
        let: "h" := Fst (!"hd") in
        let: "t" := Snd (!"hd") in
        if: "h" < #0 then "drop_negative" "t"
        else "hd" <- ("h", "drop_negative" "t");; SOME "hd"
    end.

(* State and prove a specification for drop_negative saying that drop_negative
   computes the same thing as drop_negative_pure on its argument. *)

(* grad exercise *)
(* Write a HeapLang function that implements "repeat", the pure library function
   that takes a value x and a number n and returns a list with n x's (e.g.,
   repeat 3 5 = [3; 3; 3; 3; 3]). Write and prove a spec showing that your function
   correctly implements repeat.
   Hint: When the number is greater than 0, you will need to use ref to allocate a
   new list cell. *)
(* Definition repeat_list : val := ...*)

(* Lemma repeat_list_spec:
  {{{ ... }}}
  repeat_list x #n
  {{{ ... }}}. *)

End hw6.
