Require Import primRec.
Require Import cPair.
Require Import Arith.
Require Import code.
Require Import folProp.
Require Import extEqualNat.
Require Import codeList.

Section Well_Formed_Term.

Variable L : Language.
Variable codeF : Functions L -> nat.
Variable codeArityF : nat -> nat.
Hypothesis codeArityFIsPR : isPR 1 codeArityF.
Hypothesis
  codeArityFIsCorrect1 :
    forall f : Functions L, codeArityF (codeF f) = S (arity L (inr _ f)).
Hypothesis
  codeArityFIsCorrect2 :
    forall n : nat, codeArityF n <> 0 -> exists f : Functions L, codeF f = n.

Let Term := Term L.
Let Terms := Terms L.
Let var := var L.
Let apply := apply L.

Definition wellFormedTermTerms : nat -> nat :=
  evalStrongRec 0
    (fun t recs : nat =>
     cPair
       (switchPR (cPairPi1 t)
          (charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
             (S (codeLength (cPairPi2 t))) *
           cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)) 1)
       (switchPR t
          (cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
           cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)) 1)).

Definition wellFormedTerm (t : nat) : nat := cPairPi1 (wellFormedTermTerms t).

Definition wellFormedTerms (ts : nat) : nat :=
  cPairPi2 (wellFormedTermTerms ts).

Lemma lengthTerms :
 forall (n : nat) (ts : Terms n), codeLength (codeTerms L codeF n ts) = n.
Proof.
intros.
induction ts as [| n t ts Hrects].
reflexivity.
replace (codeTerms L codeF (S n) (Tcons L n t ts)) with
 (S (cPair (codeTerm L codeF t) (codeTerms L codeF n ts)));
 [ idtac | reflexivity ].
unfold codeLength in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
set
 (A :=
  fun n0 Hrecs : nat =>
  switchPR n0 (S (codeNth (n0 - S (cPairPi2 (pred n0))) Hrecs)) 0)
 in *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
unfold A at 1 in |- *.
rewrite evalStrongRecHelp1.
simpl in |- *.
rewrite cPairProjections1.
rewrite cPairProjections2.
apply eq_S.
assumption.
generalize (cPair (codeTerm L codeF t) (codeTerms L codeF n ts)).
simpl in |- *.
intros.
apply le_lt_n_Sm.
apply le_trans with (cPair (cPairPi1 n0) (cPairPi2 n0)).
apply cPairLe2.
rewrite cPairProjections.
apply le_n.
Qed.

Lemma wellFormedTermCorrect1 :
 forall t : Term, wellFormedTerm (codeTerm L codeF t) = 1.
Proof.
intros.
set
 (A :=
  fun t recs : nat =>
  cPair
    (switchPR (cPairPi1 t)
       (charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
          (S (codeLength (cPairPi2 t))) *
        cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)) 1)
    (switchPR t
       (cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
        cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)) 1))
 in *.
elim t using
 Term_Terms_ind
  with
    (P0 := fun (n : nat) (ts : fol.Terms L n) =>
           wellFormedTerms (codeTerms L codeF n ts) = 1);
 simpl in |- *; intros.
unfold codeTerm in |- *.
unfold wellFormedTerm in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
unfold A at 1 in |- *.
repeat rewrite cPairProjections1.
simpl in |- *.
reflexivity.
replace (codeTerm L codeF (fol.apply L f t0)) with
 (cPair (S (codeF f)) (codeTerms L codeF _ t0)); [ idtac | reflexivity ].
unfold wellFormedTerm in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
unfold A at 1 in |- *.
repeat rewrite cPairProjections1.
rewrite evalStrongRecHelp1.
simpl in |- *.
rewrite codeArityFIsCorrect1.
rewrite cPairProjections2.
simpl in |- *.
rewrite lengthTerms.
rewrite <- beq_nat_refl.
simpl in |- *.
rewrite plus_comm.
simpl in |- *.
apply H.
rewrite cPairProjections2.
apply cPairLt2.
unfold wellFormedTerms in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
rewrite cPairProjections1.
unfold A in |- *.
rewrite cPairProjections2.
reflexivity.
unfold wellFormedTerms in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
unfold A at 1 in |- *.
rewrite cPairProjections1.
rewrite cPairProjections2.
replace (codeTerms L codeF (S n) (Tcons L n t0 t1)) with
 (S (cPair (codeTerm L codeF t0) (codeTerms L codeF n t1)));
 [ idtac | reflexivity ].
repeat rewrite evalStrongRecHelp1.
simpl in |- *.
rewrite cPairProjections1.
rewrite cPairProjections2.
unfold wellFormedTerm, wellFormedTermTerms in H.
unfold A in |- *; rewrite H.
unfold wellFormedTerms, wellFormedTermTerms in H0.
rewrite H0.
reflexivity.
simpl in |- *.
rewrite cPairProjections2.
apply le_lt_n_Sm.
apply cPairLe2.
simpl in |- *.
rewrite cPairProjections1.
apply le_lt_n_Sm.
apply cPairLe1.
Qed.

Lemma wellFormedTermsCorrect1 :
 forall (n : nat) (ts : Terms n),
 wellFormedTerms (codeTerms L codeF n ts) = 1.
Proof.
intros.
set
 (A :=
  fun t recs : nat =>
  cPair
    (switchPR (cPairPi1 t)
       (charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
          (S (codeLength (cPairPi2 t))) *
        cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)) 1)
    (switchPR t
       (cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
        cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)) 1))
 in *.
induction ts as [| n t ts Hrects].
unfold wellFormedTerms in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
rewrite cPairProjections1.
unfold A in |- *.
rewrite cPairProjections2.
reflexivity.
unfold wellFormedTerms in |- *.
unfold wellFormedTermTerms in |- *.
fold A in |- *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
unfold A at 1 in |- *.
rewrite cPairProjections1.
rewrite cPairProjections2.
replace (codeTerms L codeF (S n) (Tcons L n t ts)) with
 (S (cPair (codeTerm L codeF t) (codeTerms L codeF n ts)));
 [ idtac | reflexivity ].
repeat rewrite evalStrongRecHelp1.
simpl in |- *.
rewrite cPairProjections1.
rewrite cPairProjections2.
replace (cPairPi1 (evalStrongRec 0 A (codeTerm L codeF t))) with
 (wellFormedTerm (codeTerm L codeF t)).
rewrite (wellFormedTermCorrect1 t).
unfold wellFormedTerms, wellFormedTermTerms in Hrects.
unfold A in |- *; rewrite Hrects.
reflexivity.
reflexivity.
simpl in |- *.
rewrite cPairProjections2.
apply le_lt_n_Sm.
apply cPairLe2.
simpl in |- *.
rewrite cPairProjections1.
apply le_lt_n_Sm.
apply cPairLe1.
Qed.

Remark wellFormedTermTermsCorrect2 :
 forall n : nat,
 (wellFormedTerm n <> 0 -> exists t : Term, codeTerm L codeF t = n) /\
 (wellFormedTerms n <> 0 ->
  exists m : nat, (exists ts : Terms m, codeTerms L codeF m ts = n)).
Proof.
assert (multLemma1 : forall a b : nat, a * b <> 0 -> a <> 0).
unfold not in |- *; intros.
apply H.
rewrite H0.
simpl in |- *.
reflexivity.
assert (multLemma2 : forall a b : nat, a * b <> 0 -> b <> 0).
intros.
rewrite mult_comm in H.
eapply multLemma1.
apply H.
assert
 (forall m n : nat,
  n < m ->
  (wellFormedTerm n <> 0 -> exists t : Term, codeTerm L codeF t = n) /\
  (wellFormedTerms n <> 0 ->
   exists m : nat, (exists ts : Terms m, codeTerms L codeF m ts = n))).
intro.
induction m as [| m Hrecm].
intros.
elim (lt_not_le _ _ H).
apply le_O_n.
intros.
induction (le_lt_or_eq _ _ (lt_n_Sm_le _ _ H)).
apply Hrecm; auto.
unfold wellFormedTerm in |- *.
unfold wellFormedTerms in |- *.
unfold wellFormedTermTerms in |- *.
set
 (A :=
  fun t recs : nat =>
  cPair
    (switchPR (cPairPi1 t)
       (charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
          (S (codeLength (cPairPi2 t))) *
        cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)) 1)
    (switchPR t
       (cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
        cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)) 1))
 in *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
rewrite cPairProjections1.
split.
unfold A at 1 in |- *.
rewrite cPairProjections1.
assert (cPair (cPairPi1 n) (cPairPi2 n) = n).
apply cPairProjections.
destruct (cPairPi1 n).
simpl in |- *.
intros.
exists (var (cPairPi2 n)).
transitivity (cPair 0 (cPairPi2 n)).
reflexivity.
assumption.
rewrite evalStrongRecHelp1.
simpl in |- *.
intros.
induction (eq_nat_dec (codeArityF n0) (S (codeLength (cPairPi2 n)))).
assert (wellFormedTerms (cPairPi2 n) <> 0).
eapply multLemma2.
apply H2.
assert (cPairPi2 n < m).
apply lt_le_trans with (cPair (S n0) (cPairPi2 n)).
apply cPairLt2.
rewrite H1.
rewrite H0.
apply le_n.
induction (Hrecm _ H4).
clear H5.
induction (H6 H3).
induction H5 as (x0, H5).
assert (codeArityF n0 <> 0).
unfold not in |- *; intros.
rewrite H7 in a.
discriminate a.
induction (codeArityFIsCorrect2 _ H7).
rewrite <- H8 in a.
rewrite codeArityFIsCorrect1 in a.
injection a.
clear a.
intro.
rewrite <- H5 in H9.
rewrite lengthTerms in H9.
cut (codeTerms L codeF x x0 = cPairPi2 n).
clear H5.
generalize x0.
clear x0.
rewrite <- H9.
intros.
exists (apply x1 x0).
transitivity (cPair (S n0) (cPairPi2 n)).
rewrite <- H8.
rewrite <- H5.
reflexivity.
assumption.
assumption.
rewrite beq_nat_not_refl in H2.
elim H2.
reflexivity.
assumption.
apply lt_le_trans with (cPair (S n0) (cPairPi2 n)).
apply cPairLt2.
rewrite H1.
apply le_n.
unfold A at 1 in |- *.
rewrite cPairProjections2.
destruct n.
simpl in |- *.
intros.
exists 0.
exists (Tnil L).
reflexivity.
repeat rewrite evalStrongRecHelp1.
simpl in |- *.
intros.
assert (cPairPi1 n < m).
rewrite <- H0.
apply le_lt_n_Sm.
apply le_trans with (cPair (cPairPi1 n) (cPairPi2 n)).
apply cPairLe1.
rewrite cPairProjections.
apply le_n.
assert (cPairPi2 n < m).
rewrite <- H0.
apply le_lt_n_Sm.
apply le_trans with (cPair (cPairPi1 n) (cPairPi2 n)).
apply cPairLe2.
rewrite cPairProjections.
apply le_n.
induction (Hrecm _ H2).
clear H5.
induction (Hrecm _ H3).
clear H5.
assert (wellFormedTerm (cPairPi1 n) <> 0).
eapply multLemma1.
apply H1.
assert (wellFormedTerms (cPairPi2 n) <> 0).
eapply multLemma2.
apply H1.
induction (H4 H5).
induction (H6 H7).
induction H9 as (x1, H9).
exists (S x0).
exists (Tcons L x0 x x1).
rewrite <- (cPairProjections n).
rewrite <- H8.
rewrite <- H9.
reflexivity.
simpl in |- *.
apply le_lt_n_Sm.
apply le_trans with (cPair (cPairPi1 n) (cPairPi2 n)).
apply cPairLe2.
rewrite cPairProjections.
apply le_n.
apply le_lt_n_Sm.
apply le_trans with (cPair (cPairPi1 n) (cPairPi2 n)).
apply cPairLe1.
rewrite cPairProjections.
apply le_n.
intros.
eapply H.
apply lt_n_Sn.
Qed.

Lemma wellFormedTermCorrect2 :
 forall n : nat,
 wellFormedTerm n <> 0 -> exists t : Term, codeTerm L codeF t = n.
Proof.
intro.
eapply proj1.
apply wellFormedTermTermsCorrect2.
Qed.

Lemma wellFormedTermsCorrect2 :
 forall n : nat,
 wellFormedTerms n <> 0 ->
 exists m : nat, (exists ts : Terms m, codeTerms L codeF m ts = n).
Proof.
intro.
eapply proj2.
apply wellFormedTermTermsCorrect2.
Qed.

Remark wellFormedTermTermsIsPR : isPR 1 wellFormedTermTerms.
Proof.
unfold wellFormedTermTerms in |- *.
apply evalStrongRecIsPR.
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat =>
          switchPR (cPairPi1 t)
            (charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
               (S (codeLength (cPairPi2 t))) *
             cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)) 1)
    (g := fun t recs : nat =>
          switchPR t
            (cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
             cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)) 1).
apply
 compose2_3IsPR
  with
    (f1 := fun t recs : nat => cPairPi1 t)
    (f2 := fun t recs : nat =>
           charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
             (S (codeLength (cPairPi2 t))) *
           cPairPi2 (codeNth (t - S (cPairPi2 t)) recs))
    (f3 := fun t recs : nat => 1).
apply filter10IsPR.
apply cPairPi1IsPR.
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat =>
          charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
            (S (codeLength (cPairPi2 t))))
    (g := fun t recs : nat => cPairPi2 (codeNth (t - S (cPairPi2 t)) recs)).
apply
 filter10IsPR
  with
    (g := fun t : nat =>
          charFunction 2 beq_nat (codeArityF (pred (cPairPi1 t)))
            (S (codeLength (cPairPi2 t)))).
apply
 compose1_2IsPR
  with
    (f := fun t : nat => codeArityF (pred (cPairPi1 t)))
    (f' := fun t : nat => S (codeLength (cPairPi2 t))).
apply compose1_1IsPR with (f := fun t : nat => pred (cPairPi1 t)).
apply compose1_1IsPR.
apply cPairPi1IsPR.
apply predIsPR.
apply codeArityFIsPR.
apply compose1_1IsPR with (f := fun t : nat => codeLength (cPairPi2 t)).
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply codeLengthIsPR.
apply succIsPR.
apply eqIsPR.
apply
 compose2_1IsPR
  with (f := fun t recs : nat => codeNth (t - S (cPairPi2 t)) recs).
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat => t - S (cPairPi2 t))
    (g := fun t recs : nat => recs).
apply filter10IsPR with (g := fun t : nat => t - S (cPairPi2 t)).
apply
 compose1_2IsPR
  with (f := fun t : nat => t) (f' := fun t : nat => S (cPairPi2 t)).
apply idIsPR.
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply succIsPR.
apply minusIsPR.
apply pi2_2IsPR.
apply codeNthIsPR.
apply cPairPi2IsPR.
apply multIsPR.
apply filter10IsPR with (g := fun _ : nat => 1).
apply const1_NIsPR.
apply switchIsPR.
apply
 compose2_3IsPR
  with
    (f1 := fun t recs : nat => t)
    (f2 := fun t recs : nat =>
           cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs) *
           cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs))
    (f3 := fun t recs : nat => 1).
apply pi1_2IsPR.
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat =>
          cPairPi1 (codeNth (t - S (cPairPi1 (pred t))) recs))
    (g := fun t recs : nat =>
          cPairPi2 (codeNth (t - S (cPairPi2 (pred t))) recs)).
apply
 compose2_1IsPR
  with (f := fun t recs : nat => codeNth (t - S (cPairPi1 (pred t))) recs).
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat => t - S (cPairPi1 (pred t)))
    (g := fun t recs : nat => recs).
apply filter10IsPR with (g := fun t : nat => t - S (cPairPi1 (pred t))).
apply
 compose1_2IsPR
  with (f := fun t : nat => t) (f' := fun t : nat => S (cPairPi1 (pred t))).
apply idIsPR.
apply compose1_1IsPR with (f := fun t : nat => cPairPi1 (pred t)).
apply compose1_1IsPR.
apply predIsPR.
apply cPairPi1IsPR.
apply succIsPR.
apply minusIsPR.
apply pi2_2IsPR.
apply codeNthIsPR.
apply cPairPi1IsPR.
apply
 compose2_1IsPR
  with (f := fun t recs : nat => codeNth (t - S (cPairPi2 (pred t))) recs).
apply
 compose2_2IsPR
  with
    (f := fun t recs : nat => t - S (cPairPi2 (pred t)))
    (g := fun t recs : nat => recs).
apply filter10IsPR with (g := fun t : nat => t - S (cPairPi2 (pred t))).
apply
 compose1_2IsPR
  with (f := fun t : nat => t) (f' := fun t : nat => S (cPairPi2 (pred t))).
apply idIsPR.
apply compose1_1IsPR with (f := fun t : nat => cPairPi2 (pred t)).
apply compose1_1IsPR.
apply predIsPR.
apply cPairPi2IsPR.
apply succIsPR.
apply minusIsPR.
apply pi2_2IsPR.
apply codeNthIsPR.
apply cPairPi2IsPR.
apply multIsPR.
apply filter10IsPR with (g := fun _ : nat => 1).
apply const1_NIsPR.
apply switchIsPR.
apply cPairIsPR.
Qed.

Lemma wellFormedTermIsPR : isPR 1 wellFormedTerm.
Proof.
unfold wellFormedTerm in |- *.
apply compose1_1IsPR.
apply wellFormedTermTermsIsPR.
apply cPairPi1IsPR.
Qed.

Lemma wellFormedTermsIsPR : isPR 1 wellFormedTerms.
Proof.
unfold wellFormedTerms in |- *.
apply compose1_1IsPR.
apply wellFormedTermTermsIsPR.
apply cPairPi2IsPR.
Qed.

Section Well_Formed_Formula.

Variable codeR : Relations L -> nat.
Variable codeArityR : nat -> nat.
Hypothesis codeArityRIsPR : isPR 1 codeArityR.
Hypothesis
  codeArityRIsCorrect1 :
    forall r : Relations L, codeArityR (codeR r) = S (arity L (inl _ r)).
Hypothesis
  codeArityRIsCorrect2 :
    forall n : nat, codeArityR n <> 0 -> exists r : Relations L, codeR r = n.

Let Formula := Formula L.
Let equal := equal L.
Let atomic := atomic L.
Let impH := impH L.
Let notH := notH L.
Let forallH := forallH L.

Definition wellFormedFormula : nat -> nat :=
  evalStrongRec 0
    (fun f recs : nat =>
     switchPR (cPairPi1 f)
       (switchPR (pred (cPairPi1 f))
          (switchPR (pred (pred (cPairPi1 f)))
             (switchPR (pred (pred (pred (cPairPi1 f))))
                (charFunction 2 beq_nat
                   (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                   (S (codeLength (cPairPi2 f))) *
                 wellFormedTerms (cPairPi2 f))
                (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
             (codeNth (f - S (cPairPi2 f)) recs))
          (codeNth (f - S (cPairPi1 (cPairPi2 f))) recs *
           codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
       (wellFormedTerm (cPairPi1 (cPairPi2 f)) *
        wellFormedTerm (cPairPi2 (cPairPi2 f)))).

Lemma wellFormedFormulaCorrect1 :
 forall f : Formula, wellFormedFormula (codeFormula L codeF codeR f) = 1.
Proof.
intros.
set
 (A :=
  fun f recs : nat =>
  switchPR (cPairPi1 f)
    (switchPR (pred (cPairPi1 f))
       (switchPR (pred (pred (cPairPi1 f)))
          (switchPR (pred (pred (pred (cPairPi1 f))))
             (charFunction 2 beq_nat
                (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                (S (codeLength (cPairPi2 f))) * wellFormedTerms (cPairPi2 f))
             (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
          (codeNth (f - S (cPairPi2 f)) recs))
       (codeNth (f - S (cPairPi1 (cPairPi2 f))) recs *
        codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
    (wellFormedTerm (cPairPi1 (cPairPi2 f)) *
     wellFormedTerm (cPairPi2 (cPairPi2 f)))) in *.
unfold wellFormedFormula in |- *.
fold A in |- *.
induction f as [t t0| r t| f1 Hrecf1 f0 Hrecf0| f Hrecf| n f Hrecf]; intros;
 unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *;
 rewrite computeEvalStrongRecHelp;
 unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *;
 simpl in |- *; unfold A at 1 in |- *;
 repeat first [ rewrite cPairProjections1 | rewrite cPairProjections2 ].
simpl in |- *.
repeat rewrite wellFormedTermCorrect1.
reflexivity.
simpl in |- *.
rewrite wellFormedTermsCorrect1.
rewrite codeArityRIsCorrect1.
rewrite lengthTerms.
rewrite <- beq_nat_refl.
reflexivity.
rewrite evalStrongRecHelp1 with (m := codeFormula L codeF codeR f0).
rewrite evalStrongRecHelp1 with (m := codeFormula L codeF codeR f1).
simpl in |- *.
rewrite Hrecf1.
rewrite Hrecf0.
reflexivity.
eapply le_lt_trans; [ idtac | apply cPairLt2 ].
apply cPairLe1.
eapply le_lt_trans; [ idtac | apply cPairLt2 ].
apply cPairLe2.
rewrite evalStrongRecHelp1 with (m := codeFormula L codeF codeR f).
simpl in |- *.
assumption.
apply cPairLt2.
rewrite evalStrongRecHelp1 with (m := codeFormula L codeF codeR f).
simpl in |- *.
assumption.
eapply le_lt_trans; [ idtac | apply cPairLt2 ].
apply cPairLe2.
Qed.

Lemma wellFormedFormulaCorrect2 :
 forall n : nat,
 wellFormedFormula n <> 0 ->
 exists f : Formula, codeFormula L codeF codeR f = n.
Proof.
assert (multLemma1 : forall a b : nat, a * b <> 0 -> a <> 0).
unfold not in |- *; intros.
apply H.
rewrite H0.
simpl in |- *.
reflexivity.
assert (multLemma2 : forall a b : nat, a * b <> 0 -> b <> 0).
intros.
rewrite mult_comm in H.
eapply multLemma1.
apply H.
assert
 (forall m n : nat,
  n < m ->
  wellFormedFormula n <> 0 ->
  exists f : Formula, codeFormula L codeF codeR f = n).
intro.
induction m as [| m Hrecm].
intros n H.
elim (lt_not_le _ _ H).
apply le_O_n.
intros n H.
induction (le_lt_or_eq _ _ (lt_n_Sm_le _ _ H)).
apply Hrecm; auto.
unfold wellFormedFormula in |- *.
set
 (A :=
  fun f recs : nat =>
  switchPR (cPairPi1 f)
    (switchPR (pred (cPairPi1 f))
       (switchPR (pred (pred (cPairPi1 f)))
          (switchPR (pred (pred (pred (cPairPi1 f))))
             (charFunction 2 beq_nat
                (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                (S (codeLength (cPairPi2 f))) * wellFormedTerms (cPairPi2 f))
             (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
          (codeNth (f - S (cPairPi2 f)) recs))
       (codeNth (f - S (cPairPi1 (cPairPi2 f))) recs *
        codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
    (wellFormedTerm (cPairPi1 (cPairPi2 f)) *
     wellFormedTerm (cPairPi2 (cPairPi2 f)))) in *.
unfold evalStrongRec, evalComposeFunc, evalOneParamList, evalList in |- *.
rewrite computeEvalStrongRecHelp.
unfold compose2, evalComposeFunc, evalOneParamList, evalList in |- *.
simpl in |- *.
rewrite cPairProjections1.
unfold A at 1 in |- *.
assert (cPair (cPairPi1 n) (cPairPi2 n) = n).
apply cPairProjections.
destruct (cPairPi1 n).
simpl in |- *.
intros.
assert (wellFormedTerm (cPairPi1 (cPairPi2 n)) <> 0).
eapply multLemma1.
apply H2.
assert (wellFormedTerm (cPairPi2 (cPairPi2 n)) <> 0).
eapply multLemma2.
apply H2.
induction (wellFormedTermCorrect2 _ H3).
induction (wellFormedTermCorrect2 _ H4).
exists (equal x x0).
simpl in |- *.
rewrite H5.
rewrite H6.
rewrite cPairProjections.
assumption.
destruct n0.
simpl in |- *.
assert (cPairPi2 n < m).
apply lt_le_trans with (cPair 1 (cPairPi2 n)).
apply cPairLt2.
rewrite H1.
rewrite H0.
apply le_n.
assert (cPairPi1 (cPairPi2 n) < m).
apply
 le_lt_trans with (cPair (cPairPi1 (cPairPi2 n)) (cPairPi2 (cPairPi2 n))).
apply cPairLe1.
rewrite cPairProjections.
assumption.
assert (cPairPi2 (cPairPi2 n) < m).
apply
 le_lt_trans with (cPair (cPairPi1 (cPairPi2 n)) (cPairPi2 (cPairPi2 n))).
apply cPairLe2.
rewrite cPairProjections.
assumption.
repeat rewrite evalStrongRecHelp1.
intros.
assert (evalStrongRec 0 A (cPairPi1 (cPairPi2 n)) <> 0).
eapply multLemma1.
apply H5.
assert (evalStrongRec 0 A (cPairPi2 (cPairPi2 n)) <> 0).
eapply multLemma2.
apply H5.
induction (Hrecm _ H3 H6).
induction (Hrecm _ H4 H7).
exists (impH x x0).
simpl in |- *.
rewrite H8.
rewrite H9.
rewrite cPairProjections.
assumption.
eapply lt_le_trans.
apply H4.
rewrite H0.
apply le_n.
eapply lt_le_trans.
apply H3.
rewrite H0.
apply le_n.
destruct n0.
simpl in |- *.
assert (cPairPi2 n < m).
apply lt_le_trans with (cPair 2 (cPairPi2 n)).
apply cPairLt2.
rewrite H1.
rewrite H0.
apply le_n.
repeat rewrite evalStrongRecHelp1.
intros.
induction (Hrecm _ H2 H3).
exists (notH x).
simpl in |- *.
rewrite H4.
assumption.
eapply lt_le_trans.
apply H2.
rewrite H0.
apply le_n.
destruct n0.
simpl in |- *.
assert (cPairPi2 n < m).
apply lt_le_trans with (cPair 3 (cPairPi2 n)).
apply cPairLt2.
rewrite H1.
rewrite H0.
apply le_n.
assert (cPairPi2 (cPairPi2 n) < m).
apply
 le_lt_trans with (cPair (cPairPi1 (cPairPi2 n)) (cPairPi2 (cPairPi2 n))).
apply cPairLe2.
rewrite cPairProjections.
assumption.
repeat rewrite evalStrongRecHelp1.
intros.
induction (Hrecm _ H3 H4).
exists (forallH (cPairPi1 (cPairPi2 n)) x).
simpl in |- *.
rewrite H5.
rewrite cPairProjections.
assumption.
eapply lt_le_trans.
apply H3.
rewrite H0.
apply le_n.
simpl in |- *.
induction (eq_nat_dec (codeArityR n0) (S (codeLength (cPairPi2 n)))).
assert (codeArityR n0 <> 0).
unfold not in |- *; intros.
rewrite H2 in a.
discriminate a.
induction (codeArityRIsCorrect2 _ H2).
intros.
assert (wellFormedTerms (cPairPi2 n) <> 0).
eapply multLemma2.
apply H4.
rewrite <- H3 in a.
rewrite codeArityRIsCorrect1 in a.
injection a.
clear a.
intros.
induction (wellFormedTermsCorrect2 _ H5).
induction H7 as (x1, H7).
rewrite <- H7 in H6.
rewrite lengthTerms in H6.
cut (codeTerms L codeF x0 x1 = cPairPi2 n).
generalize x1.
clear H7 x1.
rewrite <- H6.
intros.
exists (atomic x x1).
simpl in |- *.
rewrite H7.
rewrite H3.
assumption.
assumption.
rewrite beq_nat_not_refl.
intros.
simpl in H2.
elim H2.
reflexivity.
assumption.
intros.
eapply H.
apply lt_n_Sn.
assumption.
Qed.

Lemma wellFormedFormulaIsPR : isPR 1 wellFormedFormula.
Proof.
unfold wellFormedFormula in |- *.
apply evalStrongRecIsPR.
assert (isPR 2 (fun f recs : nat => cPairPi1 f)).
apply filter10IsPR.
apply cPairPi1IsPR.
assert (isPR 2 (fun f recs : nat => pred (cPairPi1 f))).
apply compose2_1IsPR with (f := fun f recs : nat => cPairPi1 f).
assumption.
apply predIsPR.
assert (isPR 2 (fun f recs : nat => pred (pred (cPairPi1 f)))).
apply compose2_1IsPR with (f := fun f recs : nat => pred (cPairPi1 f)).
assumption.
apply predIsPR.
assert (isPR 2 (fun f recs : nat => pred (pred (pred (cPairPi1 f))))).
apply compose2_1IsPR with (f := fun f recs : nat => pred (pred (cPairPi1 f))).
assumption.
apply predIsPR.
assert
 (forall g : nat -> nat,
  isPR 1 g -> isPR 2 (fun f recs : nat => codeNth (f - S (g f)) recs)).
intros.
apply
 compose2_2IsPR
  with (f := fun f recs : nat => f - S (g f)) (g := fun f recs : nat => recs).
apply filter10IsPR with (g := fun f : nat => f - S (g f)).
apply
 compose1_2IsPR with (f := fun f : nat => f) (f' := fun f : nat => S (g f)).
apply idIsPR.
apply compose1_1IsPR.
assumption.
apply succIsPR.
apply minusIsPR.
apply pi2_2IsPR.
apply codeNthIsPR.
apply
 compose2_3IsPR
  with
    (f1 := fun f recs : nat => cPairPi1 f)
    (f2 := fun f recs : nat =>
           switchPR (pred (cPairPi1 f))
             (switchPR (pred (pred (cPairPi1 f)))
                (switchPR (pred (pred (pred (cPairPi1 f))))
                   (charFunction 2 beq_nat
                      (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                      (S (codeLength (cPairPi2 f))) *
                    wellFormedTerms (cPairPi2 f))
                   (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
                (codeNth (f - S (cPairPi2 f)) recs))
             (codeNth (f - S (cPairPi1 (cPairPi2 f))) recs *
              codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
    (f3 := fun f recs : nat =>
           wellFormedTerm (cPairPi1 (cPairPi2 f)) *
           wellFormedTerm (cPairPi2 (cPairPi2 f))).
assumption.
apply
 compose2_3IsPR
  with
    (f1 := fun f recs : nat => pred (cPairPi1 f))
    (f2 := fun f recs : nat =>
           switchPR (pred (pred (cPairPi1 f)))
             (switchPR (pred (pred (pred (cPairPi1 f))))
                (charFunction 2 beq_nat
                   (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                   (S (codeLength (cPairPi2 f))) *
                 wellFormedTerms (cPairPi2 f))
                (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
             (codeNth (f - S (cPairPi2 f)) recs))
    (f3 := fun f recs : nat =>
           codeNth (f - S (cPairPi1 (cPairPi2 f))) recs *
           codeNth (f - S (cPairPi2 (cPairPi2 f))) recs).
assumption.
apply
 compose2_3IsPR
  with
    (f1 := fun f recs : nat => pred (pred (cPairPi1 f)))
    (f2 := fun f recs : nat =>
           switchPR (pred (pred (pred (cPairPi1 f))))
             (charFunction 2 beq_nat
                (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
                (S (codeLength (cPairPi2 f))) * wellFormedTerms (cPairPi2 f))
             (codeNth (f - S (cPairPi2 (cPairPi2 f))) recs))
    (f3 := fun f recs : nat => codeNth (f - S (cPairPi2 f)) recs).
assumption.
apply
 compose2_3IsPR
  with
    (f1 := fun f recs : nat => pred (pred (pred (cPairPi1 f))))
    (f2 := fun f recs : nat =>
           charFunction 2 beq_nat
             (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
             (S (codeLength (cPairPi2 f))) * wellFormedTerms (cPairPi2 f))
    (f3 := fun f recs : nat => codeNth (f - S (cPairPi2 (cPairPi2 f))) recs).
assumption.
apply
 filter10IsPR
  with
    (g := fun f : nat =>
          charFunction 2 beq_nat
            (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
            (S (codeLength (cPairPi2 f))) * wellFormedTerms (cPairPi2 f)).
apply
 compose1_2IsPR
  with
    (f := fun f : nat =>
          charFunction 2 beq_nat
            (codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
            (S (codeLength (cPairPi2 f))))
    (f' := fun f : nat => wellFormedTerms (cPairPi2 f)).
apply
 compose1_2IsPR
  with
    (f := fun f : nat => codeArityR (pred (pred (pred (pred (cPairPi1 f))))))
    (f' := fun f : nat => S (codeLength (cPairPi2 f))).
apply
 compose1_1IsPR
  with (f := fun f : nat => pred (pred (pred (pred (cPairPi1 f))))).
apply
 compose1_1IsPR with (f := fun f : nat => pred (pred (pred (cPairPi1 f))));
 try apply predIsPR.
apply compose1_1IsPR with (f := fun f : nat => pred (pred (cPairPi1 f)));
 try apply predIsPR.
apply compose1_1IsPR with (f := fun f : nat => pred (cPairPi1 f));
 try apply predIsPR.
apply compose1_1IsPR.
apply cPairPi1IsPR.
apply predIsPR.
apply codeArityRIsPR.
apply compose1_1IsPR with (f := fun f : nat => codeLength (cPairPi2 f)).
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply codeLengthIsPR.
apply succIsPR.
apply eqIsPR.
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply wellFormedTermsIsPR.
apply multIsPR.
apply H3 with (g := fun f : nat => cPairPi2 (cPairPi2 f)).
apply compose1_1IsPR; apply cPairPi2IsPR.
apply switchIsPR.
apply H3.
apply cPairPi2IsPR.
apply switchIsPR.
apply
 compose2_2IsPR
  with
    (f := fun f recs : nat => codeNth (f - S (cPairPi1 (cPairPi2 f))) recs)
    (g := fun f recs : nat => codeNth (f - S (cPairPi2 (cPairPi2 f))) recs).
apply H3 with (g := fun f : nat => cPairPi1 (cPairPi2 f)).
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply cPairPi1IsPR.
apply H3 with (g := fun f : nat => cPairPi2 (cPairPi2 f)).
apply compose1_1IsPR; apply cPairPi2IsPR.
apply multIsPR.
apply switchIsPR.
apply
 filter10IsPR
  with
    (g := fun f : nat =>
          wellFormedTerm (cPairPi1 (cPairPi2 f)) *
          wellFormedTerm (cPairPi2 (cPairPi2 f))).
apply
 compose1_2IsPR
  with
    (f := fun f : nat => wellFormedTerm (cPairPi1 (cPairPi2 f)))
    (f' := fun f : nat => wellFormedTerm (cPairPi2 (cPairPi2 f))).
apply compose1_1IsPR with (f := fun f : nat => cPairPi1 (cPairPi2 f)).
apply compose1_1IsPR.
apply cPairPi2IsPR.
apply cPairPi1IsPR.
apply wellFormedTermIsPR.
apply compose1_1IsPR with (f := fun f : nat => cPairPi2 (cPairPi2 f)).
apply compose1_1IsPR; apply cPairPi2IsPR.
apply wellFormedTermIsPR.
apply multIsPR.
apply switchIsPR.
Qed.

End Well_Formed_Formula.

End Well_Formed_Term.


Set Implicit Arguments.
Require Import List Omega.
Require Export syntax semantics equivalence typing.

 (** * Order Typing *)
Section OrderTyping.

  Context {X: Const}.

  Definition ctx := list type.

  Implicit Types (s t: exp X) (n m: nat) (Gamma Delta Sigma: ctx)
           (x y: fin) (c d: X) (A B: type)
           (sigma tau: fin -> exp X) (delta xi: fin -> fin).

    (** ** Order of Types *)
    Section Order.

    Fixpoint ord A :=
      match A with
      | typevar beta => 1
      | A → B => max (S (ord A)) (ord B)
      end.

    Lemma ord_1 A: 1 <= ord A.
    Proof.
      induction A; cbn [ord]; eauto.
      transitivity (S (ord A1)); eauto using Nat.max_lub_l.
    Qed.

    Lemma ord_arr A B: ord (A → B) = max (S (ord A)) (ord B).
    Proof. reflexivity. Qed.

    Lemma ord_arr_one A B: ~ ord (A → B) <= 1.
    Proof.
      intros H; rewrite ord_arr in H; eapply Nat.max_lub_l in H.
      rewrite ord_1 with (A := A) in H; omega.
    Qed.

    Fixpoint ord' Gamma :=
      match Gamma with
      | nil => 0
      | A :: Gamma => max (ord A) (ord' Gamma)
      end.

    Lemma ord'_app Gamma Gamma': ord' (Gamma ++ Gamma') = max (ord' Gamma) (ord' Gamma').
    Proof.
      induction Gamma; cbn; eauto.
      now rewrite IHGamma,  Nat.max_assoc.
    Qed.

    Lemma ord'_rev L:
      ord' (rev L) = ord' L.
    Proof.
      induction L; cbn; rewrite ?ord'_app; eauto.
      simplify; cbn; eauto.
      rewrite IHL.
      simplify. eapply Nat.max_comm.
    Qed.

    Lemma ord'_in A Gamma: A ∈ Gamma -> ord A <= ord' Gamma.
    Proof.
      induction Gamma; cbn in *; intuition; subst; eauto.
      eauto using Max.max_lub_l.
      transitivity (ord' Gamma); eauto using Max.max_lub_r.
    Qed.

    Lemma ord'_elements n Gamma: (forall A, A ∈ Gamma -> ord A <= n) <-> ord' Gamma <= n.
    Proof.
      induction Gamma; cbn; intuition; subst; eauto.
      all: simplify in *; intuition.
    Qed.

    Lemma ord'_cons n Gamma A:
      ord A < n -> ord' Gamma <= n -> ord' (A :: Gamma) <= n.
    Proof.
      intros; cbn; eapply Max.max_lub; omega.
    Qed.

    Lemma order_head Gamma s A B:
      Gamma ⊢ s : A -> Gamma ⊢ (head s) : B -> isAtom (head s) -> ord A <= ord B.
    Proof.
      induction 1; eauto; cbn.
      - intros H1 _; inv H1. rewrite H in H2; injection H2 as ->; eauto.
      - intros H1; inv H1; eauto.
      - cbn in *; intuition.
      - intros; rewrite <-IHtyping1; eauto.
        rewrite ord_arr; eauto.
    Qed.

    Lemma ord_target A: ord (target A) <= 1.
    Proof. induction A; eauto. Qed.

    Lemma ord_target' Gamma: ord' (target' Gamma) <= 1.
    Proof.
      eapply ord'_elements; unfold target'; intros; mapinj; eauto using ord_target.
    Qed.

  End Order.
  Hint Resolve ord'_cons.

  (** ** Order Typing *)
  Reserved Notation "Gamma '⊢(' n ')' s ':' A" (at level 80, s at level 99).

  Inductive ordertyping n Gamma: exp X -> type -> Prop :=
  | ordertypingVar x A:
      ord A <= n -> nth Gamma x = Some A -> Gamma ⊢(n) (var x) : A
  | ordertypingConst c:
      ord (ctype X c) <= S n  -> Gamma ⊢(n) (const c) : ctype X c
  | ordertypingLam s A B:
      A :: Gamma ⊢(n) s : B -> Gamma ⊢(n) lambda s : A → B
  | ordertypingApp s t A B:
      Gamma ⊢(n) s : A → B -> Gamma ⊢(n) t : A -> Gamma ⊢(n) s t : B
  where "Gamma '⊢(' n ')' s ':' A" := (ordertyping n Gamma s A).

  Hint Constructors ordertyping.

  Lemma ordertyping_step n Gamma s A:
    Gamma ⊢(n) s : A -> Gamma ⊢(S n) s: A.
  Proof.
    induction 1; eauto.
  Qed.

  Lemma ordertyping_monotone m n Gamma s A:
    m <= n -> Gamma ⊢(m) s : A -> Gamma ⊢(n) s: A.
  Proof.
    induction 1; eauto using ordertyping_step.
  Qed.

  Hint Resolve ordertyping_monotone.

  Lemma ordertyping_soundness n Gamma s A:
    Gamma ⊢(n) s : A -> Gamma ⊢ s : A.
  Proof.
    induction 1; eauto.
  Qed.

  Lemma ordertyping_completeness Gamma s A:
    Gamma ⊢ s : A -> exists n, Gamma ⊢(n) s : A.
  Proof.
    induction 1.
    - exists (ord A); eauto.
    - exists (ord (ctype X c)); eauto.
    - destruct IHtyping as [n]; exists (max (S (ord A)) n).
      econstructor; eauto.
    - destruct IHtyping1 as [n], IHtyping2 as [m].
      exists (max n m); econstructor; eauto.
  Qed.

  Lemma vars_ordertyping n x s A Gamma:
    x ∈ vars s -> Gamma ⊢(n) s : A -> exists B, nth Gamma x = Some B /\ ord B <= n.
  Proof.
    intros H % vars_varof; induction 1 in x, H |-*; inv H; eauto.
    eapply IHordertyping in H2 as []; eexists; eauto.
  Qed.

  Lemma vars_ordertyping_nth n x s A B Gamma:
    x ∈ vars s -> Gamma ⊢(n) s : A -> nth Gamma x = Some B -> ord B <= n.
  Proof.
    intros; edestruct vars_ordertyping; eauto.
    intuition; congruence.
  Qed.

  Definition ordertypingSubst n Delta sigma Gamma :=
    forall i A, nth Gamma i = Some A -> Delta ⊢(n) sigma i : A.

  Notation "Delta ⊩( n ) sigma : Gamma" := (ordertypingSubst n Delta sigma Gamma)
                                (at level 80, sigma at level 99).

  Lemma ordertypingSubst_monotone n m sigma Delta Gamma:
    n <= m -> Delta ⊩(n) sigma : Gamma -> Delta ⊩(m) sigma : Gamma.
  Proof.
    intros H H' i A H1; eapply ordertyping_monotone; eauto.
  Qed.

  Lemma ordertypingSubst_soundness n Delta (sigma: fin -> exp X) Gamma:
    Delta ⊩(n) sigma : Gamma -> Delta ⊩ sigma : Gamma.
  Proof.
    intros ????; eapply ordertyping_soundness; eauto.
  Qed.

  Lemma ordertypingSubst_complete Delta sigma Gamma:
    Delta ⊩ sigma : Gamma -> exists n, Delta ⊩(n) sigma : Gamma.
  Proof.
    induction Gamma as [| A Gamma IH] in sigma |-* .
    - intros _. exists 0. intros []?; cbn; discriminate.
    - intros ?. specialize (IH (shift >> sigma)).
      mp IH; [intros ???; eauto|].
      specialize (H 0 A); mp H; eauto.
      eapply ordertyping_completeness in H.
      destruct IH as [n], H as [m].
      exists (max n m); intros [|x]B; cbn; [injection 1 as ->|intros].
      all: eapply ordertyping_monotone.
      2, 4: eauto. all: eauto.
  Qed.

  (** ** Preservation Order Typing *)
  Section PreservationOrdertyping.

    Lemma ordertyping_weak_preservation_under_renaming Gamma n s A delta Delta:
      Gamma ⊢(n) s : A ->
      (forall x A, nth Gamma x = Some A -> x ∈ vars s -> nth Delta (delta x) = Some A) ->
      Delta ⊢( n) ren delta s : A.
    Proof.
      induction s in delta, Gamma, Delta, A  |-*; cbn -[vars]; intros; inv H; eauto.
      - econstructor. eapply IHs; eauto.
        intros [] ? ?; cbn -[vars] in *; intuition.
      - econstructor; [eapply IHs1|eapply IHs2]; eauto.
    Qed.

    Lemma ordertyping_preservation_under_renaming n delta Gamma Delta s A:
      Gamma ⊢(n) s : A -> Delta ⊫ delta : Gamma -> Delta ⊢(n) ren_exp delta s : A.
    Proof.
      intros; eapply ordertyping_weak_preservation_under_renaming; eauto.
    Qed.

    Lemma ordertyping_weak_preservation_under_substitution n sigma Gamma Delta s A:
      Gamma ⊢(n) s : A ->
      (forall i A, i ∈ vars s -> nth Gamma i = Some A -> Delta ⊢(n) sigma i : A) ->
      Delta ⊢(n) sigma • s : A.
    Proof.
      induction 1 in sigma, Delta |-*; intros H'; cbn [subst_exp]; subst; eauto.
      - econstructor; eauto; eapply IHordertyping.
        intros [|m]; cbn; eauto.
        + intros ? H1; injection 1 as <-; econstructor; cbn; eauto.
          eapply vars_ordertyping in H1 as []; eauto.  intuition.
          now injection H1 as ->.
        + intros D H1 H2; unfold funcomp.
          eapply ordertyping_preservation_under_renaming; eauto.
          now intros i.
      - econstructor; [eapply IHordertyping1 | eapply IHordertyping2].
        all: intros; eapply H'; eauto.
    Qed.

    Lemma ordertyping_preservation_under_substitution n sigma Gamma Delta s A:
      Gamma ⊢(n) s : A ->
                 Delta ⊩(n) sigma : Gamma -> Delta ⊢(n) sigma • s : A.
    Proof.
      intros;
        eapply ordertyping_weak_preservation_under_substitution; eauto.
    Qed.

    Lemma ordertyping_preservation_under_reduction n s s' Gamma A:
      s > s' -> Gamma ⊢(n) s : A -> Gamma ⊢(n) s' : A.
    Proof.
      induction 1 in n, Gamma, A |-*; intros H1; invp @ordertyping; eauto.
      inv H3. eapply ordertyping_weak_preservation_under_substitution; eauto.
      intros []; cbn; eauto.
      intros ? _; now injection 1 as ->.
      intros D H6 H7. eapply vars_ordertyping in H6 as [B]; eauto.
      intuition. econstructor; intuition.
      cbn in H0; rewrite H0 in H7; now injection H7 as ->.
    Qed.

    Lemma ordertyping_preservation_under_steps n (s s': exp X) (Gamma: ctx) A:
      s >* s' -> Gamma ⊢(n) s : A -> Gamma ⊢(n) s' : A.
    Proof.
      induction 1 in Gamma, A |-*;
        eauto using ordertyping_preservation_under_reduction.
    Qed.

  End PreservationOrdertyping.

  Lemma weakening_ordertyping_app n Gamma Delta s A:
    Gamma ⊢(n) s : A -> Gamma ++ Delta ⊢(n) s : A.
  Proof.
    intros H; replace s with (ren id s) by now asimpl.
    eapply ordertyping_preservation_under_renaming; eauto.
    intros i B H'; unfold id.
    rewrite nth_error_app1; eauto.
    eapply nth_error_Some_lt; eauto.
  Qed.

End OrderTyping.

Notation "Gamma '⊢(' n ')' s ':' A" :=
  (ordertyping n Gamma s A) (at level 80, s at level 99).
Notation "Delta ⊩( n ) sigma : Gamma" := (ordertypingSubst n Delta sigma Gamma) (at level 80, sigma at level 99).

Hint Constructors ordertyping.
Hint Rewrite ord_arr ord'_app ord'_rev : simplify.
Hint Resolve ord'_cons ord'_in .
Hint Resolve vars_ordertyping_nth ordertyping_monotone ordertyping_step ordertyping_soundness.

Hint Resolve
     ordertyping_preservation_under_renaming
     ordertyping_preservation_under_substitution.

Hint Resolve ord_target ord_target'.


Require Import compcert.cfrontend.Clight.
Require Import Coq.micromega.Psatz.
Require Import compcert.cfrontend.Cop.
Require Import compcert.cfrontend.Ctypes.
Require Import compcert.lib.Integers.
Require Import Coq.Reals.Rdefinitions.
Require Import List.
Import ListNotations.
Require Import Logic.Syntax.
Require Import Logic.Semantics.
Require Import Logic.Lib.
Require Import compcert.flocq.Core.Fcore_defs.
Require Import compcert.flocq.Appli.Fappli_IEEE.
Require Import compcert.flocq.Appli.Fappli_IEEE_bits.

Require Import ExtLib.Programming.Extras.
Import FunNotation.

Local Close Scope HP_scope.
Delimit Scope SrcLang_scope with SL.
Local Open Scope SrcLang_scope.

(* TODO fill these in *)
(** [prec] is the number of bits of the mantissa including the implicit one.
    [emax] is the exponent of the infinities.
    Typically p=24 and emax = 128 in single precision. *)

(*
Variable prec:Z.
Variable emax:Z.
Variable emin:Z.
Hypothesis emaxGtEmin : (emax > emin)%Z.
Hypothesis precGe1: (prec >= 1)%Z.
Hypothesis precLtEmax : (prec < emax)%Z.
Hypothesis eminMinValue : (emin >= 3 - emax - prec)%Z.
Hypothesis nan : binary_float prec emax -> binary_float prec emax -> bool * nan_pl prec.
Locate FLT_exp.
Hypothesis precThm : forall k : ,Z
    (emin < k)%Z ->
    (prec <=
     k - Fcore_FLT.FLT_exp (3 - emax - prec) prec k)%Z.*)

Definition prec:Z := 24%Z.
(* Definition emax:Z := 128%Z.
   Definition emax:Z := 1023%Z. *)
Definition emax := 128%Z.

(*Definition emin:Z := (3 - emax - prec)%Z.*)
Definition emin:Z := (-126)%Z.
Definition emaxGtEmin : (emax > emin)%Z.
Proof. compute. reflexivity.
Defined.

Definition precGe1: (prec >= 1)%Z.
Proof. compute. inversion 1.
Defined.

Lemma eminMinValue : (emin >= 3 - emax - prec)%Z.
Proof. compute. inversion 1. Qed.

Definition precLtEmax : (prec < emax)%Z.
Proof. compute. reflexivity.
Defined.

(* TODO - figure out where eminMinValue and precThm are actually getting used *)

(* Lemma nan : binary_float prec emax -> binary_float prec emax -> bool * nan_pl prec. *)
Lemma precThm : forall k : Z,
    (emin < k)%Z ->
    (prec <=
     k - Fcore_FLT.FLT_exp (3 - emax - prec) prec k)%Z.
Proof.
  intros.
  unfold Fcore_FLT.FLT_exp.
  unfold Z.max.
  destruct (k -prec ?= 3 - emax - prec)%Z eqn:Hk; try lia.
  - rewrite Z.compare_lt_iff in Hk.
    unfold emin, emax, prec in *.
    simpl in *.
    psatz Z.
Qed.

Definition custom_prec := prec.
Definition custom_emax:= emax.
Definition custom_float_zero := B754_zero custom_prec custom_emax false.
Definition custom_emin := emin.
Definition float := binary_float custom_prec custom_emax.
Lemma customEminMinValue : (custom_emin >= 3 - custom_emax - custom_prec)%Z.
unfold custom_emin, custom_emax, custom_prec.
apply eminMinValue.
Qed.
Lemma custom_precGe1: (custom_prec >= 1)%Z.
unfold custom_prec.
apply precGe1.
Qed.
Lemma custom_emaxGtCustom_emin : (custom_emax > custom_emin)%Z.
Proof.
unfold custom_emax,custom_emin.
apply emaxGtEmin.
Qed.

(* Term, sans the next operator *)
Inductive NowTerm : Type :=
| VarNowN : Var -> NowTerm
| NatN : nat -> NowTerm
(*| RealN : Rdefinitions.R -> NowTerm*)
| FloatN : float -> NowTerm
| PlusN : NowTerm -> NowTerm -> NowTerm
| MinusN : NowTerm -> NowTerm -> NowTerm
| MultN : NowTerm -> NowTerm -> NowTerm.
(*| InvN : NowTerm -> NowTerm.*)

(* peeled from Syntax.v *)
Infix "+" := (PlusN) : SrcLang_scope.
Infix "-" := (MinusN) : SrcLang_scope.
Notation "-- x" := (MinusN (FloatN 0) x)
(at level 0) : SrcLang_scope.
(*Notation "/ x" := (InvN x) : SrcLang_scope.
Notation "x / y" := (MultN x (InvN y)) : SrcLang_scope.*)
Infix "*" := (MultN) : SrcLang_scope.

(*Definition NatC (n:nat) : NowTerm :=
NatN n.
Coercion NatC : nat >-> NowTerm.*)
Definition FloatC (f:float) : NowTerm :=
FloatN f.
Definition VarC (x:String.string) : NowTerm :=
VarNowN x.
Coercion VarC : String.string >-> NowTerm.
(* convenient coercions between number formats *)
Definition nat_to_int (n : nat) : int :=
Int.repr $ Z.of_nat n.

Lemma custom_precGt0 : Fcore_FLX.Prec_gt_0 custom_prec.
unfold Fcore_FLX.Prec_gt_0.
unfold custom_prec.
pose proof precGe1.
lia.
Qed.

Lemma custom_precLtEmax : (custom_prec < custom_emax)%Z.
unfold custom_emax, custom_prec.
apply precLtEmax.
Qed.

Definition custom_nan:float -> float -> bool * nan_pl custom_prec := Floats.Float32.binop_pl.

Definition nat_to_float (n : nat) : float :=
Fappli_IEEE_extra.BofZ custom_prec custom_emax custom_precGt0 custom_precLtEmax (Z.of_nat n).

Definition FloatToR : (float) -> R := B2R custom_prec custom_emax.
Coercion nat_to_int : nat >-> int.

Coercion Pos.of_nat : nat >-> positive.
(*Coercion FloatToR : Floats.float >-> R.*)

(* inject  *)
Fixpoint denowify (nt : NowTerm) : Term :=
  match nt with
    | VarNowN v => VarNowT v
    | NatN n => NatT n
    | FloatN f => RealT $ FloatToR f
    (*| InvN t => InvT (denowify t)*)
    | PlusN t1 t2 => PlusT (denowify t1) (denowify t2)
    | MinusN t1 t2 => MinusT (denowify t1) (denowify t2)
    | MultN t1 t2 => MultT (denowify t1) (denowify t2)
end.

(* Formula, sans the things we don't need to worry about
compiling, and without and and or *)
Inductive FlatFormula :=
| FTRUE : FlatFormula
| FFALSE : FlatFormula
| FComp : NowTerm -> NowTerm -> CompOp -> FlatFormula
| FAnd : FlatFormula -> FlatFormula -> FlatFormula
| FOr : FlatFormula -> FlatFormula -> FlatFormula.

(* Comparisons *)
Notation "t1 > t2" := (FComp t1 t2 Gt) : SrcLang_scope.
Notation "t1 >= t2" := (FComp t1 t2 Ge) : SrcLang_scope.
Notation "t1 < t2" := (FComp t1 t2 Lt) : SrcLang_scope.
Notation "t1 <= t2" := (FComp t1 t2 Le) : SrcLang_scope.
Notation "t1 = t2" := (FComp t1 t2 Eq) : SrcLang_scope.
Notation "x <= y <= z" :=
(And (FComp x y Le) (FComp y z Le)) : SrcLang_scope.

(* Propositional Logic *)
Infix "/\" := (FAnd) : SrcLang_scope.
Infix "\/" := (FOr) : SrcLang_scope.

(* Convert a formula to a flat one *)
(*Definition flatten_formula (tla : Formula) : option FlatFormula :=
match tla with
| TRUE => Some FTRUE
| FALSE => Some FFALSE
| Comp a b c => Some (FComp a b c)
| _ => None
end.*)

(* inject FlatFormula into Formula *)
Fixpoint deflatten_formula (ff : FlatFormula) : Formula :=
  match ff with
    | FTRUE => TRUE
    | FFALSE => FALSE
    | FComp a b c => Comp (denowify a) (denowify b) c
    | FAnd ff1 ff2 => And (deflatten_formula ff1)
                          (deflatten_formula ff2)
    | FOr ff1 ff2 => Or (deflatten_formula ff1)
                        (deflatten_formula ff2)
end.

(* Captures the structure we want each term in our IR to have
Basically, a list of simultaneous assignments, and a
list of conditions guarding (all of) those assigments.
Our program will be a list of these statements. *)

Inductive SrcProg : Set :=
| SAssn : Var -> NowTerm -> SrcProg
| SSkip : SrcProg
| SSeq  : SrcProg -> SrcProg -> SrcProg
| SITE  : FlatFormula -> SrcProg -> SrcProg -> SrcProg
.

(* These are the old implementation of the source language.
   They have since been replaced *)
(*
Record progr_assn : Set :=
  mk_progr_assn {
      pa_dest : Var;
      pa_source : NowTerm
    }.

(* for now we omit "unknown" case for simplicity *)
Record progr_stmt : Set :=
  mk_progr_stmt {
      ps_conds : FlatFormula;
      ps_assns : list progr_assn
}.
*)

Notation "a !!= b" := (SAssn a b) (at level 59) : SrcLang_scope.
Notation "'SIF' c 'STHEN' s1 'SELSE' s2" :=
(SITE c s1 s2) (at level 60) : SrcLang_scope.
Notation "'SIF' c 'STHEN' s" :=
(SITE c s SSkip) (at level 61) : SrcLang_scope.

Notation "s1 ;; s2" :=
(SSeq s1 s2) (at level 58) : SrcLang_scope.

(*
Check (SIF FTRUE STHEN SSkip SELSE SSkip ;; SSkip).
*)

(*
Notation "'PIF' cs 'PTHEN' yas 'PUNKNOWN' unas" :=
(mk_progr_stmt cs yas unas) (at level 59).
*)
Local Open Scope string_scope.
Definition testProg :=
  ("a" !!= "b" + (NatN 3))%SL.

Definition testProg2 :=
  (SIF FTRUE STHEN "a" !!= "b").

(* Fold a list with its first element as starting accumulator
Takes function and list, as well as default element to return if list is nil *)
Definition self_foldr {A : Type} (f : A -> A -> A) (l : list A) (dflt : A) : A :=
  match l with
    | List.nil => dflt
    | h :: t =>
      List.fold_right f h t
  end.

Require Import ExtLib.Core.RelDec.
Require Import ExtLib.Data.String.
Require Import ExtLib.Data.Option.

(* Semantics *)
Definition fstate := list (Var * (float)).

Fixpoint fstate_lookup (f : fstate) (v : Var) : option (float) :=
  match f with
  | List.nil          => None
  | (v',b) :: fs =>
    if v ?[ eq ] v' then
      Some b
    else fstate_lookup fs v
  end.

Fixpoint fstate_set (f : fstate) (v : Var) (val : float) : fstate :=
  match f with
    | List.nil           => (v, val) :: List.nil
    | (v', b) :: fs =>
      if v ?[ eq ] v' then
        (v, val) :: fs
      else
        (v', b) :: fstate_set fs v val
  end.

Definition lift2 {T U V : Type} (f : T -> U -> V) (a : option T) (b : option U) : option V :=
  match a , b with
  | Some a , Some b => Some (f a b)
  | _ , _ => None
  end.

(* Useful floating-point operations for our format *)
Definition float_plus (a b : float) : float :=
  Bplus custom_prec custom_emax custom_precGt0 custom_precLtEmax custom_nan mode_NE a b.

Definition float_minus (a b : float) : float :=
  Bminus custom_prec custom_emax custom_precGt0 custom_precLtEmax custom_nan mode_NE a b.

Definition float_mult (a b : float) : float :=
  Bmult custom_prec custom_emax custom_precGt0 custom_precLtEmax custom_nan mode_NE a b.

(* This replaces a validity proof in the floating-point representation and replaces it with
   eq_refl (this is possible since boolean equality is decidable). Doing this optimization
   allows us to compute floating-point operations in Coq, (including constructing
   floating-point numbers) though we must do so using lazy. *)
Definition concretize_float (f : float) :=
  match f with
  | B754_finite sig m e pf =>
    @B754_finite _ _ sig m e
                (match bounded prec emax m e as X return (X = true -> X = true) with
                 | true => fun p => eq_refl
                 | false => fun p => p
                 end pf)
  | _ => f
  end.

Lemma concretize_float_correct :
  forall (f : float), concretize_float f = f.
Proof.
  intros.
  unfold concretize_float.
  destruct f; try reflexivity.
  assert (((if bounded prec emax m e as X return (X = true -> X = true)
       then fun _ : true = true => eq_refl
            else fun p : false = true => p) e0) = e0).
  { unfold custom_prec, custom_emax in e0.
    rewrite e0. reflexivity. }
  rewrite H. reflexivity.
Qed.

(* here is how we define new floating-point constants. *)
Definition new_float_one := Eval lazy in (concretize_float (nat_to_float 1)).

(* Nice tactics for reducing floating-point expressions. Automatically apply concretization *)
Ltac fcompute_in X :=
  rewrite <- concretize_float_correct in X; lazy in X.

Ltac fcompute :=
  rewrite <- concretize_float_correct; lazy.

(* raw representaion of float 1 as bits (obtained from simple C program) *)
Lemma test : new_float_one = concretize_float (b32_of_bits 1065353216).
Proof.
  fcompute.
  reflexivity.
Qed.

(* and here is how we add them *)
Definition new_float_one' := Eval lazy in (concretize_float (float_plus custom_float_zero (nat_to_float 1))).

(* These examples illustrate how "concretize_float" should be used: you should wrap an entire float-typed expression in it
   and then run Eval lazy. "Concretize_float" is an aid to evaluation, essentially. *)

(* Floating-point comparisons *)
(* NB: sign bit is true if negative *)
Definition float_lt (a b : float) : bool :=
  match float_minus a b with
  | B754_zero _ => false
  | B754_infinity is_neg => is_neg
  | B754_nan is_neg _ => is_neg (* should never happen for non-exceptional operands... *)
  | B754_finite is_neg _ _ _ => is_neg
  end.

Definition float_le (a b : float) : bool :=
  match float_minus a b with
  | B754_zero _ => true
  | B754_infinity is_neg => is_neg
  | B754_nan is_neg _ => is_neg (* should never happen for non-exceptional operands... *)
  | B754_finite is_neg _ _ _ => is_neg
  end.

Section eval_expr.
  Variable st : fstate.

  (* denotation of NowTerm *)

  (* TODO - does this actually get used anywhere? --M *)
Definition try := (fun pl : positive =>
   ((PreOmega.Z_of_nat' (S (Fcore_digits.digits2_Pnat pl)) <? 53)%Z = true)%type).

Fixpoint eval_NowTerm (t:NowTerm) :=
  match t with
  | VarNowN var => fstate_lookup st var
  | NatN n => Some (nat_to_float n)
  | FloatN f => Some f
  | PlusN t1 t2 =>
    lift2 float_plus
          (eval_NowTerm t1) (eval_NowTerm t2)
  | MinusN t1 t2 =>
    lift2 float_minus
          (eval_NowTerm t1) (eval_NowTerm t2)
  | MultN t1 t2 =>
    lift2 float_mult
          (eval_NowTerm t1) (eval_NowTerm t2)
  end.

  (* denotation of comparison functions *)

Definition custom_cmp (c : comparison) (f1 : float) (f2 : float) := Floats.cmp_of_comparison c (Fappli_IEEE_extra.Bcompare custom_prec custom_emax f1 f2).

  Definition eval_comp (op : CompOp) (lhs rhs : NowTerm) : option bool :=
    let elhs := eval_NowTerm lhs in
    let erhs := eval_NowTerm rhs in
    let cmp := match op with
               | Gt => Cgt
               | Ge => Cge
               | Lt => Clt
               | Le => Cle
               | Eq => Ceq
               end in
    lift2 (custom_cmp cmp) elhs erhs.

  (* denotation of conditionals *)
  Fixpoint progr_cond_holds (conds : FlatFormula) : option bool :=
    match conds with
    | FTRUE => Some true
    | FFALSE => Some false
    | FComp lhs rhs op => eval_comp op lhs rhs
    | FAnd p1 p2 => lift2 andb (progr_cond_holds p1) (progr_cond_holds p2)
    | FOr p1 p2 => lift2 orb (progr_cond_holds p1) (progr_cond_holds p2)
    end.
End eval_expr.

(* String.string_dec *)
(* update state as per an assignment *)
(* TODO rename these to eval_something *)
Definition assn_update_state (v : Var) (nt : NowTerm) (st : fstate) : option fstate :=
  match eval_NowTerm st nt with
  | Some val => Some (fstate_set st v val)
  | None => None
  end.

(*
Fixpoint assns_update_state (assns: list progr_assn) (acc : fstate) : option fstate :=
  match assns with
  | List.nil => Some acc
  | h :: t =>
    match assn_update_state h acc with
    | Some news => assns_update_state t news
    | _ => None
    end
  end.
*)

Fixpoint eval_SrcProg (sp : SrcProg) (init : fstate) : option fstate :=
  match sp with
    | SAssn v nt   => assn_update_state v nt init
    | SSkip        => Some init
    | SSeq p1 p2   =>
      match eval_SrcProg p1 init with
        | Some nxt => eval_SrcProg p2 nxt
        | None     => None
      end
    | SITE c p1 p2 =>
      match progr_cond_holds init c with
        | Some true  => eval_SrcProg p1 init
        | Some false => eval_SrcProg p2 init
        | None       => None
      end
  end.

(* failed effort to get nat_to_float to compute *)
(*
Lemma derp : forall x, nat_to_float 3 = x.
Proof.
  intros.
  unfold nat_to_float.
  unfold Fappli_IEEE_extra.BofZ.
  unfold binary_normalize.
  unfold binary_round_correct.
  simpl.
  unfold shl_align_fexp_correct.
  simpl.
  compute.
  unfold shl_align_correct.
  unfold binary_round_aux_correct.
  simpl.
  unfold Fcalc_round.truncate_correct_partial.
  unfold Fcalc_round.truncate_correct.
  simpl.
  unfold fexp_correct.
  unfold Fcalc_round.round_trunc_sign_any_correct.
  simpl.
  unfold Fcalc_round.truncate_correct_format.
  unfold Fcalc_round.truncate_correct.
  simpl.
  unfold Fcalc_round.round_sign_any_correct.
  simpl.
  unfold F2R.
  unfold Fnum.
  unfold Fcore_Raux.Z2R.
  simpl.
  Locate Zpower.shift_pos_correct.
  lazy.
  unfold FF2B.
  simpl.
  Check B754_finite.
  unfold Fcore_FLT.FLT_exp.
  simpl.
  unfold binary_round_correct.
  simpl.
*)
(* Previous denotation functions for source language *)

(*
(* denotation of a single program statement *)
Fixpoint eval_progr_stmt (ps : progr_stmt) (init : fstate) : option fstate :=
  match ps with
  | mk_progr_stmt conds assns =>
    match progr_cond_holds init conds with
    | Some true => assns_update_state assns init
    | Some false => Some init
    | None => None
    end
  end.

(* denotation of a source language program *)
Fixpoint eval_progr (p : progr) (init : fstate) : option fstate :=
  match p with
  | List.nil => Some init
  | h :: t =>
    match eval_progr_stmt h init with
    | Some news => eval_progr t news
    | None => None
    end
  end.
*)


(** * HoareList: Hoare Logic with Lists *)

Require Export SfLib.

(* ####################################################### *)
(** * Imp Programs with Lists *)

(** There are only so many numeric functions with interesting
    properties that have simple proofs.  (Of course, there are lots of
    interesting functions on numbers and they have many interesting
    properties -- this is the whole field of number theory! -- but
    proving these properties often requires developing a lot of
    supporting lemmas.)  In order to able to write a few more programs
    to reason about, we introduce here an extended version of Imp
    where variables can range over both numbers and lists of numbers.
    The basic operations are extended to also include taking the head
    and tail of lists, and testing lists for nonemptyness.

    To do this, we only need to change the definitions of [state],
    [aexp], [aeval], [bexp], and [beval]. The definitions of [com] and
    [ceval] can be reused verbatim, although we need to copy-and-paste
    them in the context of the new definitions.

    We start by repeating some material from chapter [Imp]. *)

(** ** Repeated Definitions *)

Inductive id : Type :=
  Id : nat -> id.

Theorem eq_id_dec : forall id1 id2 : id, {id1 = id2} + {id1 <> id2}.
Proof.
   intros id1 id2.
   destruct id1 as [n1]. destruct id2 as [n2].
   destruct (eq_nat_dec n1 n2) as [Heq | Hneq].
   Case "n1 = n2".
     left. rewrite Heq. reflexivity.
   Case "n1 <> n2".
     right. intros contra. inversion contra. apply Hneq. apply H0.
Defined.

Lemma eq_id : forall (T:Type) x (p q:T),
              (if eq_id_dec x x then p else q) = p.
Proof.
  intros.
  destruct (eq_id_dec x x).
  Case "x = x".
    reflexivity.
  Case "x <> x (impossible)".
    apply ex_falso_quodlibet; apply n; reflexivity. Qed.

Lemma neq_id : forall (T:Type) x y (p q:T), x <> y ->
               (if eq_id_dec x y then p else q) = q.
Proof.
  intros.
  destruct (eq_id_dec x y).
  Case "x = y (impossible)".
    subst. apply ex_falso_quodlibet; apply H; reflexivity.
  Case "x <> y".
    reflexivity. Qed.

Definition X : id := Id 0.
Definition Y : id := Id 1.
Definition Z : id := Id 2.

(** ** Extensions *)

(** Now we come to the key changes.

    Rather than evaluating to a [nat], an [aexp] in our new language
    will evaluate to a _value_ -- an element of type [val] -- which
    can be either a [nat] or a list of [nat]s.

    Similarly, [state]s will now map identifiers to [val]s rather than
    [nat]s, so that we can store lists in mutable variables. *)

Inductive val : Type :=
| VNat : nat -> val
| VList : list nat -> val.

Definition state := id -> val.

Definition empty_state : state := fun _ => VNat 0.
Definition update (st : state) (X:id) (v : val) : state :=
  fun X' => if eq_id_dec X X' then v else st X'.

(** Imp does not have a static type system, so nothing prevents the
    programmer from e.g. adding two lists or taking the head of a
    number. We have to decide what to do in such nonsensical
    situations.

    We adopt a simple solution: if an arithmetic function is given a
    list as an argument we treat the list as if it was the number
    [0]. Similarly, if a list function is given a number as an
    argument we treat the number as if it was [nil].  (Cf. Javascript,
    where adding [3] to the empty list evaluates to [3]...)

    The two functions [asnat] and [aslist] interpret [val]s in a
    numeric or a list context; [aeval] calls these whenever it
    evaluates an arithmetic or a list operation.*)

Definition asnat (v : val) : nat :=
  match v with
  | VNat n => n
  | VList _ => 0
  end.

Definition aslist (v : val) : list nat :=
  match v with
  | VNat n => []
  | VList xs => xs
  end.

(** Now we fill in the definitions of abstract syntax and
    evaluation functions for arithmetic and boolean expressions. *)

Inductive aexp : Type :=
  | ANum : nat -> aexp
  | AId : id -> aexp
  | APlus : aexp -> aexp -> aexp
  | AMinus : aexp -> aexp -> aexp
  | AMult : aexp -> aexp -> aexp
  (* Four new cases: *)
  | AHead : aexp -> aexp
  | ATail : aexp -> aexp
  | ACons : aexp -> aexp -> aexp
  | ANil  : aexp.

Tactic Notation "aexp_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "ANum" | Case_aux c "AId" | Case_aux c "APlus"
  | Case_aux c "AMinus" | Case_aux c "AMult"
  | Case_aux c "AHead" | Case_aux c "ATail"
  | Case_aux c "ACons" | Case_aux c "ANil" ].

Definition tail (l : list nat) :=
  match l with
  | x::xs => xs
  | [] => []
  end.

Definition head (l : list nat) :=
  match l with
  | x::xs => x
  | [] => 0
  end.

Fixpoint aeval (st : state) (e : aexp) : val :=
  match e with
  | ANum n => VNat n
  | AId i => st i
  | APlus a1 a2   => VNat (asnat (aeval st a1) + asnat (aeval st a2))
  | AMinus a1 a2  => VNat (asnat (aeval st a1) - asnat (aeval st a2))
  | AMult a1 a2   => VNat (asnat (aeval st a1) * asnat (aeval st a2))
  (* Four new cases: *)
  | ATail a => VList (tail (aslist (aeval st a)))
  | AHead a => VNat (head (aslist (aeval st a)))
  | ACons a1 a2 => VList (asnat (aeval st a1) :: aslist (aeval st a2))
  | ANil => VList []
  end.

(** We extend [bexp]s with an operation to test if a list is nonempty
    and adapt [beval] acordingly. *)

Inductive bexp : Type :=
  | BTrue : bexp
  | BFalse : bexp
  | BEq : aexp -> aexp -> bexp
  | BLe : aexp -> aexp -> bexp
  | BNot : bexp -> bexp
  | BAnd : bexp -> bexp -> bexp
  (* New case: *)
  | BIsCons : aexp -> bexp.

Tactic Notation "bexp_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "BTrue" | Case_aux c "BFalse" | Case_aux c "BEq"
  | Case_aux c "BLe" | Case_aux c "BNot" | Case_aux c "BAnd"
  | Case_aux c "BIsCons" ].

Fixpoint beval (st : state) (e : bexp) : bool :=
  match e with
  | BTrue       => true
  | BFalse      => false
  | BEq a1 a2   => beq_nat (asnat (aeval st a1)) (asnat (aeval st a2))
  | BLe a1 a2   => ble_nat (asnat (aeval st a1)) (asnat (aeval st a2))
  | BNot b1     => negb (beval st b1)
  | BAnd b1 b2  => andb (beval st b1) (beval st b2)
  (* New case: *)
  | BIsCons a    => match aslist (aeval st a) with
                     | _::_ => true
                     | [] => false
                   end
  end.

(** ** Repeated Definitions *)

(** Now we need to repeat a little bit of low-level work from Imp.v,
    plus the definitions of [com] and [ceval].  There are no
    interesting changes -- it's just a matter of repeating the same
    definitions, lemmas, and proofs in the context of the new
    definitions of arithmetic and boolean expressions.

    (Is all this cutting and pasting really necessary?  No: Coq
    includes a powerful module system that we could use to abstract
    the repeated definitions with respect to the varying parts.  But
    explaining how it works would distract us from the topic at hand.)
 *)

Theorem update_eq : forall n V st,
  (update st V n) V = n.
Proof.
  intros n V st.
  unfold update.
  rewrite eq_id.
  reflexivity.
Qed.

Theorem update_neq : forall V2 V1 n st,
  V2 <> V1 ->
  (update st V2 n) V1 = (st V1).
Proof.
  intros V2 V1 n st Hneq.
  unfold update.
  rewrite neq_id.
  reflexivity.
  assumption. Qed.

Theorem update_shadow : forall x1 x2 k1 k2 (f : state),
   (update  (update f k2 x1) k2 x2) k1 = (update f k2 x2) k1.
Proof.
  intros x1 x2 k1 k2 f.
  unfold update.
  destruct (eq_id_dec k2 k1); reflexivity.  Qed.

Theorem update_same : forall x1 k1 k2 (f : state),
  f k1 = x1 ->
  (update f k1 x1) k2 = f k2.
Proof.
  intros x1 k1 k2 f Heq.
  unfold update. subst.
  destruct (eq_id_dec k1 k2).
  Case "k1 = k2".
    subst. reflexivity.
  Case "k1 <> k2".
    reflexivity.  Qed.

Theorem update_permute : forall x1 x2 k1 k2 k3 f,
  k2 <> k1 ->
  (update (update f k2 x1) k1 x2) k3 = (update (update f k1 x2) k2 x1) k3.
Proof.
  intros x1 x2 k1 k2 k3 f H.
  unfold update.
  destruct (eq_id_dec k1 k3); try reflexivity.
  Case "k1 = k3".
      subst. rewrite neq_id. reflexivity. assumption. Qed.

(** We can keep exactly the same old definitions of [com] and
    [ceval]. *)

Inductive com : Type :=
  | CSkip : com
  | CAss : id -> aexp -> com
  | CSeq : com -> com -> com
  | CIf : bexp -> com -> com -> com
  | CWhile : bexp -> com -> com.

Tactic Notation "com_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "SKIP" | Case_aux c "::=" | Case_aux c ";"
  | Case_aux c "IFB" | Case_aux c "WHILE" ].

Notation "'SKIP'" :=
  CSkip.
Notation "X '::=' a" :=
  (CAss X a) (at level 60).
Notation "c1 ; c2" :=
  (CSeq c1 c2) (at level 80, right associativity).
Notation "'WHILE' b 'DO' c 'END'" :=
  (CWhile b c) (at level 80, right associativity).
Notation "'IFB' e1 'THEN' e2 'ELSE' e3 'FI'" :=
  (CIf e1 e2 e3) (at level 80, right associativity).

Reserved Notation "c1 '/' st '||' st'" (at level 40, st at level 39).

Inductive ceval : state -> com -> state -> Prop :=
  | E_Skip : forall st,
      SKIP / st || st
  | E_Asgn  : forall st a1 n X,
      aeval st a1 = n ->
      (X ::= a1) / st || (update st X n)
  | E_Seq : forall c1 c2 st st' st'',
      c1 / st  || st' ->
      c2 / st' || st'' ->
      (c1 ; c2) / st || st''
  | E_IfTrue : forall st st' b1 c1 c2,
      beval st b1 = true ->
      c1 / st || st' ->
      (IFB b1 THEN c1 ELSE c2 FI) / st || st'
  | E_IfFalse : forall st st' b1 c1 c2,
      beval st b1 = false ->
      c2 / st || st' ->
      (IFB b1 THEN c1 ELSE c2 FI) / st || st'
  | E_WhileEnd : forall b1 st c1,
      beval st b1 = false ->
      (WHILE b1 DO c1 END) / st || st
  | E_WhileLoop : forall st st' st'' b1 c1,
      beval st b1 = true ->
      c1 / st || st' ->
      (WHILE b1 DO c1 END) / st' || st'' ->
      (WHILE b1 DO c1 END) / st || st''

  where "c1 '/' st '||' st'" := (ceval st c1 st').

Tactic Notation "ceval_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "E_Skip" | Case_aux c "E_Asgn" | Case_aux c "E_Seq"
  | Case_aux c "E_IfTrue" | Case_aux c "E_IfFalse"
  | Case_aux c "E_WhileEnd" | Case_aux c "E_WhileLoop" ].

(* ####################################################### *)
(** * Hoare Rules *)

(** We copy verbatim the Hoare rules from Hoare.v. *)
(**
             ------------------------------ (hoare_asgn)
             {{assn_sub X a Q}} X::=a {{Q}}

             --------------------  (hoare_skip)
             {{ P }} SKIP {{ P }}

               {{ P }} c1 {{ Q }}
               {{ Q }} c2 {{ R }}
              ---------------------  (hoare_seq)
              {{ P }} c1;c2 {{ R }}

              {{P /\  b}} c1 {{Q}}
              {{P /\ ~b}} c2 {{Q}}
      ------------------------------------  (hoare_if)
      {{P}} IFB b THEN c1 ELSE c2 FI {{Q}}

               {{P /\ b}} c {{P}}
        -----------------------------------  (hoare_while)
        {{P}} WHILE b DO c END {{P /\ ~b}}

                {{P'}} c {{Q'}}
                   P ->> P'
                   Q' ->> Q
         -----------------------------   (hoare_consequence)
                {{P}} c {{Q}}
*)

Definition Assertion := state -> Prop.

Definition hoare_triple (P:Assertion) (c:com) (Q:Assertion) : Prop :=
  forall st st',
       c / st || st'  ->
       P st  ->
       Q st'.

Notation "{{ P }}  c  {{ Q }}" := (hoare_triple P c Q)
                                  (at level 90, c at next level)
                                  : hoare_spec_scope.
Open Scope hoare_spec_scope.

Definition assn_sub X a Q : Assertion :=
  fun (st : state) =>
    Q (update st X (aeval st a)).

Theorem hoare_asgn : forall Q X a,
  {{assn_sub X a Q}} (X ::= a) {{Q}}.
Proof.
  unfold hoare_triple.
  intros Q X a st st' HE HQ.
  inversion HE. subst.
  unfold assn_sub in HQ. assumption.  Qed.

Theorem hoare_skip : forall P,
     {{P}} SKIP {{P}}.
Proof.
  intros P st st' H HP. inversion H. subst.
  assumption.  Qed.

Theorem hoare_seq : forall P Q R c1 c2,
     {{Q}} c2 {{R}} ->
     {{P}} c1 {{Q}} ->
     {{P}} c1;c2 {{R}}.
Proof.
  intros P Q R c1 c2 H1 H2 st st' H12 Pre.
  inversion H12; subst.
  apply (H1 st'0 st'); try assumption.
  apply (H2 st st'0); assumption. Qed.

Definition bassn b : Assertion :=
  fun st => (beval st b = true).

Lemma bexp_eval_true : forall b st,
  beval st b = true -> (bassn b) st.
Proof.
  intros b st Hbe.
  unfold bassn. assumption.  Qed.

Lemma bexp_eval_false : forall b st,
  beval st b = false -> ~ ((bassn b) st).
Proof.
  intros b st Hbe contra.
  unfold bassn in contra.
  rewrite -> contra in Hbe. inversion Hbe.  Qed.

Theorem hoare_if : forall P Q b c1 c2,
  {{fun st => P st /\ bassn b st}} c1 {{Q}} ->
  {{fun st => P st /\ ~(bassn b st)}} c2 {{Q}} ->
  {{P}} (IFB b THEN c1 ELSE c2 FI) {{Q}}.
Proof.
  intros P Q b c1 c2 HTrue HFalse st st' HE HP.
  inversion HE; subst.
  Case "b is true".
    apply (HTrue st st').
      assumption.
      split. assumption.
             apply bexp_eval_true. assumption.
  Case "b is false".
    apply (HFalse st st').
      assumption.
      split. assumption.
             apply bexp_eval_false. assumption. Qed.

Lemma hoare_while : forall P b c,
  {{fun st => P st /\ bassn b st}} c {{P}} ->
  {{P}} WHILE b DO c END {{fun st => P st /\ ~ (bassn b st)}}.
Proof.
  intros P b c Hhoare st st' He HP.
  (* Like we've seen before, we need to reason by induction
     on He, because, in the "keep looping" case, its hypotheses
     talk about the whole loop instead of just c *)
  remember (WHILE b DO c END) as wcom.
  ceval_cases (induction He) Case; try (inversion Heqwcom); subst.

  Case "E_WhileEnd".
    split. assumption. apply bexp_eval_false.  assumption.

  Case "E_WhileLoop".
    apply IHHe2.  reflexivity.
    apply (Hhoare st st'); try assumption.
      split. assumption. apply bexp_eval_true. assumption.  Qed.

Definition assert_implies (P Q : Assertion) : Prop :=
  forall st, P st -> Q st.

Notation "P ->> Q" := (assert_implies P Q) (at level 80).
Notation "P <<->> Q" := (P ->> Q /\ Q ->> P) (at level 80).

Theorem hoare_consequence_pre : forall (P P' Q : Assertion) c,
  {{P'}} c {{Q}} ->
  P ->> P' ->
  {{P}} c {{Q}}.
Proof.
  intros P P' Q c Hhoare Himp.
  intros st st' Hc HP. apply (Hhoare st st').
  assumption. apply Himp. assumption. Qed.

Theorem hoare_consequence_post : forall (P Q Q' : Assertion) c,
  {{P}} c {{Q'}} ->
  Q' ->> Q ->
  {{P}} c {{Q}}.
Proof.
  intros P Q Q' c Hhoare Himp.
  intros st st' Hc HP.
  apply Himp.
  apply (Hhoare st st').
  assumption. assumption. Qed.

Theorem hoare_consequence : forall (P P' Q Q' : Assertion) c,
  {{P'}} c {{Q'}} ->
  P ->> P' ->
  Q' ->> Q ->
  {{P}} c {{Q}}.
Proof.
  intros P P' Q Q' c Hht HPP' HQ'Q.
  intros st st' Hc HP.
  apply HQ'Q. apply (Hht st st'). assumption.
  apply HPP'. assumption. Qed.

(* ####################################################### *)
(** ** Reasoning About Programs with Lists *)

(** Now let's look at a formal Hoare Logic proof for a program that
    works with lists.  We will verify the following program, which
    checks if the number [Y] occurs in the list [X], and if so sets
    [Z] to [1]. *)

Definition list_member :=
  WHILE BIsCons (AId X) DO
    IFB (BEq (AId Y) (AHead (AId X))) THEN
      Z ::= (ANum 1)
    ELSE
      SKIP
    FI;
    X ::= ATail (AId X)
  END.

(** The informal proof looks like this:
    {{ X = l /\ Y = n /\ Z = 0 }} =>
    {{ Y = n /\ exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p) }}
  WHILE (BIsCons X)
  DO
      {{ Y = n /\ (exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p))
               /\ (BIsCons X) }}
    IFB (Y == head X) THEN
        {{ Y = n
           /\ (exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p))
           /\ (BIsCons X)
           /\ Y == AHead X }} =>
        {{ Y = n /\ (exists p, p ++ tail X = l
                    /\ (1 = 1 <-> appears_in n p)) }}
      Z ::= 1
        {{ Y = n
           /\ (exists p, p ++ tail X = l /\ (Z = 1 <-> appears_in n p)) }}
    ELSE
        {{ Y = n
           /\ (exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p))
           /\ (BIsCons X)
           /\ ~ (Y == head X) }} =>
        {{ Y = n
           /\ (exists p, p ++ tail X = l /\ (Z = 1 <-> appears_in n p)) }}
      SKIP
        {{ Y = n
           /\ (exists p, p ++ tail X = l /\ (Z = 1 <-> appears_in n p)) }}
    FI;
       {{ Y = n
           /\ (exists p, p ++ tail X = l /\ (Z = 1 <-> appears_in n p)) }}
    X ::= ATail X
        {{ Y = n
           /\ (exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p)) }}
  END
     {{ Y = n
        /\ (exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p))
        /\ ~ (BIsCons X) }} =>
     {{ Z = 1 <-> appears_in n l }}

  The only interesting part of the proof is the choice of loop invariant:
      exists p, p ++ X = l /\ (Z = 1 <-> appears_in n p)
  This states that at each iteration of the loop, the original list
  [l] is equal to the append of the current value of [X] and some
  other list [p] which is not the value of any variable in the
  program, but keeps track of enough information from the original
  state to make the proof go through. (Such a [p] is sometimes called
  a "ghost variable"). *)

(** In order to show that such a list [p] exists, in each iteration we
    add the head of [X] to the _end_ of [p]. This needs the function
    [snoc], from Poly.v. *)

Fixpoint snoc {X:Type} (l:list X) (v:X) : (list X) :=
  match l with
  | nil      =>  [ v ]
  | cons h t => h :: (snoc t v)
  end.

(** The main proof uses several lemmas about [snoc] and [++]. *)

Lemma snoc_equation : forall (A : Type) (h:A) (x y : list A),
  snoc x h ++ y = x ++ h :: y.
Proof.
  intros A h x y.
  induction x.
    Case "x = []". reflexivity.
    Case "x = cons". simpl. rewrite IHx. reflexivity.
Qed.

Lemma appears_in_snoc1 : forall a l,
  appears_in a (snoc l a).
Proof.
  induction l.
    Case "l = []". apply ai_here.
    Case "l = cons". simpl. apply ai_later. apply IHl.
Qed.

Lemma appears_in_snoc2 : forall a b l,
  appears_in a l ->
  appears_in a (snoc l b).
Proof.
  induction l; intros H; inversion H; subst; simpl.
    Case "l = []". apply ai_here.
    Case "l = cons". apply ai_later. apply IHl. assumption.
Qed.

Lemma appears_in_snoc3 : forall a b l,
   appears_in a (snoc l b) ->
   (appears_in a l \/ a = b).
Proof.
   induction l; intros H.
   Case "l = []". inversion H.
     SCase "ai_here". right. reflexivity.
     SCase "ai_later". left. assumption.
   Case "l = cons". inversion H; subst.
     SCase "ai_here". left. apply ai_here.
     SCase "ai_later". destruct (IHl H1).
       left. apply ai_later. assumption.
       right. assumption.
Qed.

Lemma append_singleton_equation : forall (x : nat) l l',
  (l ++ [x]) ++ l' = l ++ x :: l'.
Proof.
  intros x l l'.
  induction l.
    reflexivity.
    simpl. rewrite IHl. reflexivity.
Qed.

Lemma append_nil : forall (A : Type) (l : list A),
  l ++ [] = l.
Proof.
  induction l.
    reflexivity.
    simpl. rewrite IHl. reflexivity.
Qed.

Theorem list_member_correct : forall l n,
  {{ fun st => aslist (st X) = l /\ asnat (st Y) = n /\ asnat (st Z) = 0 }}
  list_member
  {{ fun st => asnat (st Z) = 1 <-> appears_in n l }}.
Proof.
  intros l n.
  eapply hoare_consequence.
  apply hoare_while with (P := fun st =>
     asnat (st Y) = n
     /\ exists p, p ++ aslist (st X) = l
                  /\ (asnat (st Z) = 1 <-> appears_in n p)).
    (* The loop body preserves the invariant: *)
    eapply hoare_seq.
    apply hoare_asgn.
    apply hoare_if.
    Case "If taken".
      eapply hoare_consequence_pre.
      apply hoare_asgn.
      intros st [[[H1 [p [H2 H3]]] H9] H10].
      unfold assn_sub. split.
      (* (st Y) is still n *)
        rewrite update_neq; try (intro X; inversion X).
        rewrite update_neq; try (intro X; inversion X).
        assumption.
      (* and the interesting part of the invariant is preserved: *)
        (* X has to be a cons *)
        destruct (aslist (st X)) as [|h x'] eqn:Heqx.
          unfold bassn in H9. unfold beval in H9. unfold aeval in H9.
          rewrite -> Heqx in H9. inversion H9.

          exists (snoc p h).
          rewrite update_eq.
          unfold aeval. rewrite update_neq; try (intro X; inversion X).
          rewrite -> Heqx.
          split.
            rewrite snoc_equation. assumption.

            rewrite update_neq; try (intro X; inversion X).
            rewrite update_eq.
            split.
              simpl.
              unfold bassn in H10. unfold beval in H10.
              unfold aeval in H10. rewrite H1 in H10.
              rewrite -> Heqx in H10. simpl in H10.
              rewrite (beq_nat_true _ _ H10).
              intros. apply appears_in_snoc1.

              intros. reflexivity.
    Case "If not taken".
      eapply hoare_consequence_pre. apply hoare_skip.
      unfold assn_sub.
      intros st [[[H1 [p [H2 H3]]] H9] H10].
      split.
      (* (st Y) is still n *)
        rewrite update_neq; try (intro X; inversion X).
        assumption.
      (* and the interesting part of the invariant is preserved: *)
        (* X has to be a cons *)
        destruct (aslist (st X)) as [|h x'] eqn:Heqx.
          unfold bassn in H9. unfold beval in H9. unfold aeval in H9.
          rewrite -> Heqx in H9. inversion H9.

          exists (snoc p h).
          split.
            rewrite update_eq.
            unfold aeval. rewrite -> Heqx.
            rewrite snoc_equation. assumption.

            rewrite update_neq; try (intro X; inversion X).
            split.
              intros. apply appears_in_snoc2. apply H3. assumption.

              intros.  destruct (appears_in_snoc3 _ _ _ H).
              SCase "later".
                inversion H3 as [_ H3'].
                apply H3'. assumption.
              SCase "here (absurd)".
                subst.
                unfold bassn, beval, aeval in H10.
                rewrite not_true_iff_false in H10.
                apply beq_nat_false in H10.
                rewrite -> Heqx in H10. simpl in H10.
                apply ex_falso_quodlibet. apply H10. assumption.
  (* The invariant holds at the start of the loop: *)
  intros st [H1 [H2 H3]].
  rewrite H1. rewrite H2. rewrite H3.
  split.
    reflexivity.
    exists []. split.
      reflexivity.
      split; intros H; inversion H.
  (* At the end of the loop the invariant implies the right thing. *)
  simpl.   intros st [[H1 [p [H2 H3]]] H5].
  (* x must be [] *)
  unfold bassn in H5. unfold beval in H5. unfold aeval in H5.
  destruct (aslist (st X)) as [|h x'].
    rewrite append_nil in H2.
    rewrite <- H2.
    assumption.

    apply ex_falso_quodlibet. apply H5. reflexivity.
Qed.

(** **** Exercise: 3 stars (list_sum) *)
(** Here is a direct definition of the sum of the elements of a list,
    and an Imp program that computes the sum. *)

Definition sum l := fold_right plus 0 l.

Definition sum_program :=
  Y ::= ANum 0;
  WHILE (BIsCons (AId X)) DO
    Y ::= APlus (AId Y) (AHead (AId X)) ;
    X ::= ATail (AId X)
  END.

(** Provide an _informal_ proof of the following specification of
    [sum_program] in the form of a decorated version of the
    program. *)

Definition sum_program_spec := forall l,
  {{ fun st => aslist (st X) = l }}
  sum_program
  {{ fun st => asnat (st Y) = sum l }}.

(* FILL IN HERE *)
(** [] **)

(** **** Exercise: 4 stars (list_reverse) *)
(** Recall the function [rev] from Poly.v, for reversing lists. *)

Fixpoint rev {X:Type} (l:list X) : list X :=
  match l with
  | nil      => []
  | cons h t => snoc (rev t) h
  end.

(** Write an Imp program [list_reverse_program] that reverses
    lists. Formally prove that it satisfies the following
    specification:
    forall l : list nat,
      {{ X =  l /\ Y = nil }}
      list_reverse_program
      {{ Y = rev l }}.
    You may find the lemmas [append_nil] and [rev_equation] useful.
*)

Lemma rev_equation : forall (A : Type) (h : A) (x y : list A),
  rev (h :: x) ++ y = rev x ++ h :: y.
Proof.
  intros. simpl. apply snoc_equation.
Qed.

(* FILL IN HERE *)
(** [] *)

(** Finally, for a bigger example, let's redo the proof of
    [list_member_correct] from above using our new tools.

    Notice that the [verify] tactic leaves subgoals for each
    "interesting" use of [hoare_consequence] -- that is, for each [=>]
    that occurs in the decorated program, except for the ones that can
    be eliminated by repeated application of a few simple automated
    tactics. Each of these implications relies on a fact about lists,
    for example that [l ++ [] = l]. In other words, the Hoare logic
    infrastructure has taken care of the boilerplate reasoning about
    the execution of imperative programs, while the user has to prove
    lemmas that are specific to the problem domain (e.g. lists or
    numbers). *)

(* ####################################################### *)
(** * Formal Decorated Programs *)

(** Again, the definitions are copied verbatim from Hoare.v *)

Inductive dcom : Type :=
  | DCSkip :  Assertion -> dcom
  | DCSeq : dcom -> dcom -> dcom
  | DCAsgn : id -> aexp ->  Assertion -> dcom
  | DCIf : bexp ->  Assertion -> dcom ->  Assertion -> dcom
           -> Assertion-> dcom
  | DCWhile : bexp -> Assertion -> dcom -> Assertion -> dcom
  | DCPre : Assertion -> dcom -> dcom
  | DCPost : dcom -> Assertion -> dcom.

Tactic Notation "dcom_cases" tactic(first) ident(c) :=
  first;
  [ Case_aux c "Skip" | Case_aux c "Seq" | Case_aux c "Asgn"
  | Case_aux c "If" | Case_aux c "While"
  | Case_aux c "Pre" | Case_aux c "Post" ].

Notation "'SKIP' {{ P }}"
      := (DCSkip P)
      (at level 10) : dcom_scope.
Notation "l '::=' a {{ P }}"
      := (DCAsgn l a P)
      (at level 60, a at next level) : dcom_scope.
Notation "'WHILE' b 'DO' {{ Pbody }} d 'END' {{ Ppost }}"
      := (DCWhile b Pbody d Ppost)
      (at level 80, right associativity) : dcom_scope.
Notation "'IFB' b 'THEN' {{ P }} d 'ELSE' {{ P' }} d' 'FI' {{ Q }}"
      := (DCIf b P d P' d' Q)
      (at level 80, right associativity)  : dcom_scope.
Notation "'=>' {{ P }} d"
      := (DCPre P d)
      (at level 90, right associativity)  : dcom_scope.
Notation "{{ P }} d"
      := (DCPre P d)
      (at level 90)  : dcom_scope.
Notation "d '=>' {{ P }}"
      := (DCPost d P)
      (at level 91, right associativity)  : dcom_scope.
Notation " d ; d' "
      := (DCSeq d d')
      (at level 80, right associativity)  : dcom_scope.

Delimit Scope dcom_scope with dcom.

Example dec_while : dcom := (
  {{ fun st => True }}
  WHILE (BNot (BEq (AId X) (ANum 0)))
  DO
    {{ fun st => True /\ bassn (BNot (BEq (AId X) (ANum 0))) st}}
    X ::= (AMinus (AId X) (ANum 1))
    {{ fun _ => True }}
  END
  {{ fun st => True /\ ~bassn (BNot (BEq (AId X) (ANum 0))) st}} =>
  {{ fun st => asnat (st X) = 0 }}
) % dcom.

Fixpoint extract (d:dcom) : com :=
  match d with
  | DCSkip _           => SKIP
  | DCSeq d1 d2        => (extract d1 ; extract d2)
  | DCAsgn X a _       => X ::= a
  | DCIf b _ d1 _ d2 _ => IFB b THEN extract d1 ELSE extract d2 FI
  | DCWhile b _ d _    => WHILE b DO extract d END
  | DCPre _ d          => extract d
  | DCPost d _         => extract d
  end.

Fixpoint post (d:dcom) : Assertion :=
  match d with
  | DCSkip P                => P
  | DCSeq d1 d2             => post d2
  | DCAsgn X a Q            => Q
  | DCIf  _ _ d1 _ d2 Q     => Q
  | DCWhile b Pbody c Ppost => Ppost
  | DCPre _ d               => post d
  | DCPost c Q              => Q
  end.

Fixpoint pre (d:dcom) : Assertion :=
  match d with
  | DCSkip P                => fun st => True
  | DCSeq c1 c2             => pre c1
  | DCAsgn X a Q            => fun st => True
  | DCIf  _ _ t _ e _       => fun st => True
  | DCWhile b Pbody c Ppost => fun st => True
  | DCPre P c               => P
  | DCPost c Q              => pre c
  end.

Definition dec_correct (d:dcom) :=
  {{pre d}} (extract d) {{post d}}.

Fixpoint verification_conditions (P : Assertion) (d:dcom) : Prop :=
  match d with
  | DCSkip Q          =>
      (P ->> Q)
  | DCSeq d1 d2      =>
      verification_conditions P d1
      /\ verification_conditions (post d1) d2
  | DCAsgn X a Q      =>
      (P ->> assn_sub X a Q)
  | DCIf b P1 d1 P2 d2 Q =>
      ((fun st => P st /\ bassn b st) ->> P1)
      /\ ((fun st => P st /\ ~ (bassn b st)) ->> P2)
      /\ (Q = post d1) /\ (Q = post d2)
      /\ verification_conditions P1 d1
      /\ verification_conditions P2 d2
  | DCWhile b Pbody d Ppost      =>
      (* post d is the loop invariant and the initial precondition *)
      (P ->> post d)
      /\ (Pbody = (fun st => post d st /\ bassn b st))
      /\ (Ppost = (fun st => post d st /\ ~(bassn b st)))
      /\ verification_conditions Pbody d
  | DCPre P' d         =>
      (P ->> P') /\ verification_conditions P' d
  | DCPost d Q        =>
      verification_conditions P d /\ (post d ->> Q)
  end.

Theorem verification_correct : forall d P,
  verification_conditions P d -> {{P}} (extract d) {{post d}}.
Proof.
  dcom_cases (induction d) Case; intros P H; simpl in *.
  Case "Skip".
    eapply hoare_consequence_pre.
      apply hoare_skip.
      assumption.
  Case "Seq".
    inversion H as [H1 H2]. clear H.
    eapply hoare_seq.
      apply IHd2. apply H2.
      apply IHd1. apply H1.
  Case "Asgn".
    eapply hoare_consequence_pre.
      apply hoare_asgn.
      assumption.
  Case "If".
    inversion H as [HPre1 [HPre2 [Hd1 [Hd2 [HThen HElse]]]]]; clear H.
    subst.
    apply hoare_if.
      eapply hoare_consequence_pre. apply IHd1. eassumption. assumption.
      rewrite Hd2.
      eapply hoare_consequence_pre. apply IHd2. eassumption. assumption.
  Case "While".
    inversion H as [Hpre [Hbody [Hpost Hd]]]; subst; clear H.
    eapply hoare_consequence_pre.
    apply hoare_while with (P := post d).
      apply IHd. apply Hd.
      assumption.
  Case "Pre".
    inversion H as [HP Hd]; clear H.
    eapply hoare_consequence_pre. apply IHd. apply Hd. assumption.
  Case "Post".
    inversion H as [Hd HQ]; clear H.
    eapply hoare_consequence_post. apply IHd. apply Hd. assumption.
Qed.

Tactic Notation "verify" :=
  try apply verification_correct;
  repeat split;
  simpl; unfold assert_implies;
  unfold bassn in *; unfold beval in *; unfold aeval in *;
  unfold assn_sub; intros;
  repeat rewrite update_eq;
  repeat (rewrite update_neq; [| (intro X; inversion X)]);
  simpl in *;
  repeat match goal with [H : _ /\ _ |- _] => destruct H end;
  repeat rewrite not_true_iff_false in *;
  repeat rewrite not_false_iff_true in *;
  repeat rewrite negb_true_iff in *;
  repeat rewrite negb_false_iff in *;
  repeat rewrite beq_nat_true_iff in *;
  repeat rewrite beq_nat_false_iff in *;
  try eauto; try omega.

(** Finally, for a bigger example, let's redo the proof of
    [list_member_correct] from above using our new tools.

    Notice that the [verify] tactic leaves subgoals for each
    "interesting" use of [hoare_consequence] -- that is, for each [=>]
    that occurs in the decorated program, except for the ones that can
    be eliminated by repeated application of a few simple automated
    tactics. Each of these implications relies on a fact about lists,
    for example that [l ++ [] = l]. In other words, the Hoare logic
    infrastructure has taken care of the boilerplate reasoning about
    the execution of imperative programs, while the user has to prove
    lemmas that are specific to the problem domain (e.g. lists or
    numbers). *)

Definition list_member_dec (n : nat) (l : list nat) : dcom := (
    {{ fun st => aslist (st X) = l /\ asnat (st Y) = n /\ asnat (st Z) = 0 }}
  WHILE BIsCons (AId X)
  DO {{ fun st => (asnat (st Y) = n
               /\ (exists p, p ++ aslist (st X) = l
               /\ (asnat (st Z) = 1 <-> appears_in n p)))
               /\ bassn (BIsCons (AId X)) st }}
    IFB (BEq (AId Y) (AHead (AId X))) THEN
        {{ fun st =>
          ((asnat (st Y) = n
            /\ (exists p, p ++ aslist (st X) = l
                /\ (asnat (st Z) = 1 <-> appears_in n p)))
          /\ bassn (BIsCons (AId X)) st)
          /\ bassn (BEq (AId Y) (AHead (AId X))) st }}
        =>
        {{ fun st =>
            asnat (st Y) = n
            /\ (exists p, p ++ tail (aslist (st X)) = l
                 /\ (1 = 1 <-> appears_in n p)) }}
      Z ::= ANum 1
        {{ fun st => asnat (st Y) = n
             /\ (exists p, p ++ tail (aslist (st X)) = l
                  /\ (asnat (st Z) = 1 <-> appears_in n p)) }}
   ELSE
        {{ fun st =>
          ((asnat (st Y) = n
            /\ (exists p, p ++ aslist (st X) = l
                  /\ (asnat (st Z) = 1 <-> appears_in n p)))
          /\ bassn (BIsCons (AId X)) st)
          /\ ~bassn (BEq (AId Y) (AHead (AId X))) st }}
        =>
        {{ fun st =>
          asnat (st Y) = n
          /\ (exists p, p ++ tail (aslist (st X)) = l
               /\ (asnat (st Z) = 1 <-> appears_in n p)) }}
      SKIP
        {{ fun st => asnat (st Y) = n
            /\ (exists p, p ++ tail (aslist (st X)) = l
                 /\ (asnat (st Z) = 1 <-> appears_in n p)) }}
   FI
     {{ fun st => asnat (st Y) = n
        /\ (exists p, p ++ tail (aslist (st X)) = l
          /\ (asnat (st Z) = 1 <-> appears_in n p)) }}
   ;
   X ::= (ATail (AId X))
     {{ fun st  =>
         asnat (st Y) = n /\
         (exists p : list nat, p ++ aslist (st X) = l
           /\ (asnat (st Z) = 1 <-> appears_in n p)) }}
  END
   {{ fun st =>
     (asnat (st Y) = n
       /\ (exists p, p ++ aslist (st X) = l
            /\ (asnat (st Z) = 1 <-> appears_in n p)))
     /\ ~bassn (BIsCons (AId X)) st }}
   =>
   {{ fun st => asnat (st Z) = 1 <-> appears_in n l }}
) %dcom.

Theorem list_member_correct' : forall n l,
  dec_correct (list_member_dec n l).
Proof.
  intros n l. verify.
  Case "The loop precondition holds.".
    exists []. simpl. split.
      rewrite H. reflexivity.
      rewrite H1. split; intro Hc; inversion Hc.
  Case "IF taken".
    destruct H2 as [p [H3 H4]].
    (* We know X is non-nil. *)
    destruct (aslist (st X)) as [|h x'].
      inversion H1.
      exists (snoc p h).
      simpl. split.
         rewrite snoc_equation. assumption.
         split.
           rewrite H in H0.
           simpl in H0. rewrite H0.
           intros _. apply appears_in_snoc1.
           intros _. reflexivity.
  Case "If not taken".
    destruct H2 as [p [H3 H4]].
    (* We know X is non-nil. *)
    destruct (aslist (st X)) as [|h x'].
      inversion H1.
      exists (snoc p h).
      split. simpl.
        rewrite snoc_equation. assumption.
        split.
          intros. apply appears_in_snoc2. apply H4. assumption.
          intros Hai.  destruct (appears_in_snoc3 _ _ _ Hai).
          SCase "later". apply H4. assumption.
          SCase "here (absurd)".
            subst. simpl in H0.
            apply ex_falso_quodlibet. apply H0. assumption.
  Case "Loop postcondition implies desired conclusion (->)".
    destruct H2 as [p [H3 H4]].
    destruct (aslist (st X)) as [|h x'].
       rewrite append_nil in H3. subst. apply H4. assumption.
       inversion H1.
  Case "loop postcondition implies desired conclusion (<-)".
    destruct H2 as [p [H3 H4]].
    destruct (aslist (st X)) as [|h x'].
       rewrite append_nil in H3. subst. apply H4. assumption.
       inversion H1.
Qed.


About or_introl.
Print or_introl.
Print Implicit or_introl.

Print eq_refl.
About eq_refl.
Print Implicit eq_refl.

Print plus.
About plus.
Print Implicit plus.

About plus_n_O.

Implicit Arguments le_S [[n] m].
Print le_S.

About comparison.
Print comparison.

Definition foo := forall x, x = 0.
Parameter bar : foo.
Implicit Arguments bar [x].
About bar.
Print bar.

About Peano. (* Module *)
About existS2. (* Notation *)

Implicit Arguments eq_refl [[A] [x]] [[A]].
Print eq_refl.


(** * Dupfree vector *)
(* Author: Maximilian Wuttke *)

Require Import PslBase.Prelim.
Require Import PslBase.Tactics.Tactics.
Require Import PslBase.Vectors.Vectors.
Require Import PslBase.FiniteTypes.FinTypes.
Require PslBase.Lists.Dupfree.
Require Import Coq.Vectors.Vector.

Open Scope vector_scope.

Inductive dupfree X : forall n, Vector.t X n -> Prop :=
  dupfreeVN :
    dupfree (@Vector.nil X)
| dupfreeVC n (x : X) (V : Vector.t X n) :
    ~ Vector.In x V -> dupfree V -> dupfree (x ::: V).

Ltac vector_dupfree :=
  match goal with
  | [ |- dupfree (Vector.nil _) ] =>
    constructor
  | [ |- dupfree (?a ::: ?bs)] =>
    constructor; [vector_not_in | vector_dupfree]
  end.

Goal dupfree [| 4; 8; 15; 16; 23; 42 |].
Proof. vector_dupfree. Qed.

Goal dupfree [| Fin.F1 (n := 1) |].
Proof. vector_dupfree. Qed.

(*
(* This also works, but needs a bit to comile *)
Require Import PslBase.Vectors.FinNotation.
Goal dupfree ([| Fin4; Fin8; Fin15; Fin16; Fin23; Fin42 |] : Vector.t (Fin.t 43) _).
Proof. vector_dupfree. Qed.
*)

Lemma dupfree_cons (X : Type) (n : nat) (x : X) (xs : Vector.t X n) :
  dupfree (x ::: xs) -> dupfree xs /\ ~ In x xs.
Proof.
  intros H1. inv H1. now existT_eq'.
Qed.

Lemma dupfree_replace (X : Type) (n : nat) (xs : Vector.t X n) (x : X) :
  dupfree xs -> ~ In x xs -> forall i, dupfree (replace  xs i x).
Proof.
  revert x. induction xs; intros; cbn.
  - inv i.
  - dependent destruct i; cbn.
    + constructor; auto.
      * intros H1. contradict H0. now econstructor.
      * inv H. existT_eq'. assumption.
    + apply dupfree_cons in H as (H&H').
      assert (~In x xs).
      {
        intros H1. contradict H0. now constructor.
      }
      specialize (IHxs x H H1 p). constructor.
      * intros [ -> | H2] % In_replace. contradict H0. constructor. tauto.
      * tauto.
Qed.

Lemma dupfree_tabulate_injective (X : Type) (n : nat) (f : Fin.t n -> X) :
  (forall x y, f x = f y -> x = y) ->
  dupfree (tabulate f).
Proof.
  intros H. revert f H. induction n; intros; cbn.
  - constructor.
  - constructor.
    + intros (x & H2 % H) % in_tabulate. congruence.
    + eapply IHn. now intros x y -> % H % Fin.FS_inj.
Qed.

Lemma dupfree_map_injective (X Y : Type) (n : nat) (f : X -> Y) (V : Vector.t X n) :
  (forall x y, f x = f y -> x = y) ->
  dupfree V ->
  dupfree (map f V).
Proof.
  intros HInj. induction 1.
  - cbn. constructor.
  - cbn. constructor; auto. now intros (? & -> % HInj & ?) % vect_in_map_iff.
Qed.

Lemma tolist_dupfree (X : Type) (n : nat) (xs : Vector.t X n) :
  dupfree xs -> Dupfree.dupfree xs.
Proof.
  induction 1.
  - cbn. constructor.
  - cbn. constructor; auto. intros H1. contradict H. now apply tolist_In.
Qed.

Section Count.
  Variable (X : eqType).

  Definition count (n : nat) (x : X) (xs : t X n) :=
    fold_right (fun y c => if Dec (x = y) then S c else c) xs 0.

  Lemma count0_notIn (n : nat) (x : X) (xs : t X n) :
    count x xs = 0 -> ~ In x xs.
  Proof.
    revert x. induction xs; intros; cbn in *.
    - vector_not_in.
    - intros H1. decide (x=h); try congruence.
      apply In_cons in H1 as [-> | H1]; try tauto.
      eapply IHxs; eauto.
  Qed.

  Lemma count0_notIn' (n : nat) (x : X) (xs : t X n) :
    ~ In x xs -> count x xs = 0.
  Proof.
    induction xs; intros; cbn in *.
    - reflexivity.
    - decide (x = h) as [ -> | D ].
      + contradict H. constructor.
      + apply IHxs. intros H2. contradict H. now constructor.
  Qed.

  Lemma countDupfree (n : nat) (xs : t X n) :
    (forall x : X, In x xs -> count x xs = 1) <-> dupfree xs.
  Proof.
    split; intros H.
    {
      induction xs; cbn -[count] in *.
      - constructor.
      - constructor.
        + intros H2. specialize (H h ltac:(now constructor)). cbn in H.
          decide (h = h); try tauto. inv H.
          contradict H2. now eapply count0_notIn.
        + apply IHxs. intros x Hx. specialize (H x ltac:(now constructor)).
          cbn in H. decide (x = h); inv H; auto. rewrite H1.
          contradict Hx. now eapply count0_notIn.
    }
    {
      induction H as [ | n x' xs HIn HDup IH ]; intros; cbn in *.
      - inv H.
      - decide (x = x') as [ -> | D].
        + f_equal. exact (count0_notIn' HIn).
        + apply (IH x). now apply In_cons in H as [ -> | H].
    }
  Qed.

(* (* Test *)
End Count.
Compute let xs := [|1;2;3;4;5;6|] in
        let x  := 2 in
        let y  := 1 in
        let i  := Fin.F1 in
        Dec (x = y) + count x xs = Dec (x = xs[@i]) + count x (replace xs i y).
*)

  Lemma replace_nochange (n : nat) (xs : Vector.t X n) (p : Fin.t n) :
    replace xs p xs[@p] = xs.
  Proof.
    eapply eq_nth_iff. intros ? ? <-.
    decide (p = p1) as [ -> | D].
    - now rewrite replace_nth.
    - now rewrite replace_nth2.
  Qed.

  Lemma count_replace (n : nat) (xs : t X n) (x y : X) (i : Fin.t n) :
    Dec (x = y) + count x xs = Dec (x = xs[@i]) + count x (replace xs i y).
  Proof.
    induction xs; intros; cbn -[nth count] in *.
    - inv i.
    - dependent destruct i; cbn -[nth count] in *.
      + decide (x = y) as [ D | D ]; cbn -[nth count] in *; cbn -[bool2nat dec2bool count].
        * rewrite <- D in *. decide (x = h) as [ -> | D2]; cbn [dec2bool bool2nat plus]; auto.
          cbv [count]. cbn. rewrite D. decide (y = y); try tauto. decide (y = h); congruence.
        * decide (x = h); subst; cbn [bool2nat dec2bool plus]; cbv [count]; try reflexivity.
          -- cbn. decide (h = h); try tauto. decide (h = y); tauto.
          -- cbn. decide (x = h); try tauto. decide (x = y); tauto.
      + cbn. decide (x = y); cbn.
        * decide (x = h); cbn; f_equal.
          -- decide (x = xs[@p]); cbn; repeat f_equal; subst.
             ++ symmetry. now apply replace_nochange.
             ++ cbn in *. specialize (IHxs p). decide (h = xs[@p]); tauto.
          -- decide (x = xs[@p]); cbn; repeat f_equal; subst.
             ++ symmetry. now apply replace_nochange.
             ++ cbn in *. specialize (IHxs p). decide (y = xs[@p]); tauto.
        * decide (x = h); cbn; f_equal.
          -- decide (x = xs[@p]); cbn; f_equal; subst.
             ++ cbn in *. specialize (IHxs p). decide (xs[@p] = xs[@p]); cbn in *; try tauto.
             ++ specialize (IHxs p). cbn in *. decide (h = xs[@p]); cbn in *; tauto.
          -- decide (x = xs[@p]); cbn; auto.
             ++ specialize (IHxs p). cbn in *. decide (x = xs[@p]); cbn in *; tauto.
             ++ specialize (IHxs p). cbn in *. decide (x = xs[@p]); cbn in *; tauto.
  Qed.

End Count.

Lemma dupfree_nth_injective (X : Type) (n : nat) (xs : Vector.t X n) :
  dupfree xs -> forall (i j : Fin.t n), xs[@i] = xs[@j] -> i = j.
Proof.
  induction 1; intros; cbn -[nth] in *.
  - inv i.
  - dependent destruct i; dependent destruct j; cbn -[nth] in *; auto.
    + cbn in *. contradict H. eapply vect_nth_In; eauto.
    + cbn in *. contradict H. eapply vect_nth_In; eauto.
    + f_equal. now apply IHdupfree.
Qed.