Require Import List.
Require Import MyList.
Require Import MyTactics.
Require Export Autosubst.Autosubst.
Require Export AutosubstExtra.
Require Export Autosubst_EOS.
Require Export Autosubst_FreeVars.

(* -------------------------------------------------------------------------- *)

(* Metal is a lambda-calculus where every lambda-abstractions must be closed.
   It is equipped with pairs and projections. It is intended to serve as the
   target of closure conversion. *)

(* -------------------------------------------------------------------------- *)

(* Syntax. *)

Inductive metal :=
| MVar  : var -> metal
| MLam  : {bind metal} -> metal
| MApp  : metal -> metal -> metal
| MLet  : metal -> {bind metal} -> metal
| MPair : metal -> metal -> metal
| MPi   : nat -> metal -> metal
.

Instance Ids_metal : Ids metal. derive. Defined.
Instance Rename_metal : Rename metal. derive. Defined.
Instance Subst_metal : Subst metal. derive. Defined.
Instance SubstLemmas_metal : SubstLemmas metal. derive. Qed.

Instance IdsLemmas_metal : IdsLemmas metal.
Proof. econstructor. intros. injections. eauto. Qed.

(* -------------------------------------------------------------------------- *)

(* We equip the calculus with pairs, instead of general tuples, because
   this makes life in Coq simpler -- we would otherwise have an occurrence
   of [list metal] in the definition of [metal], and would need to ask for
   a custom induction scheme. *)

(* Instead, we simulate tuples using nested pairs. This would of course be
   inefficient in real life, but fur our purposes, should be fine. *)

Fixpoint MTuple ts :=
  match ts with
  | nil =>
      (* A dummy value serves as the tail. *)
      MLam (MVar 0)
  | t :: ts =>
      MPair t (MTuple ts)
  end.

Fixpoint MProj i t :=
  match i with
  | 0 =>
      (* Take the left pair component. *)
      MPi 0 t
  | S i =>
      (* Take the right pair component and continue. *)
      MProj i (MPi 1 t)
  end.

Definition MLetPair t u :=
  MLet (* x_0 = *) (MPi 0 t) (
    MLet (* x_1 = *) (MPi 1 (lift 1 t))
      u
  ).

(* -------------------------------------------------------------------------- *)

(* [MMultiLet 0 ts u] is a sequence of [MLet] bindings. [n] variables, where
   [n] is [length ts], are bound to the terms [ts] in the term [u]. The
   recursive structure of the definition is such that the *last* term in the
   list [ts] is bound *last*, hence is referred to as variable 0 in [u]. *)

Fixpoint MMultiLet i ts u :=
  match ts with
  | nil =>
      u
  | t :: ts =>
      MLet (lift i t) (MMultiLet (i + 1) ts u)
  end.

(* The auxiliary parameter [i] in [MMultiLet i ts u] is required for the
   recursive definition to go through, but, as far as the end user is
   concerned, is not very useful. It can be eliminated as follows. *)

Lemma MMultiLet_ij:
  forall ts delta i j u,
  i + delta = j ->
  MMultiLet j ts u = MMultiLet i (map (fun t => lift delta t) ts) u.
Proof.
  induction ts; intros; simpl; eauto.
  f_equal.
  { asimpl. congruence. }
  { erewrite IHts. eauto. omega. }
Qed.

Lemma MMultiLet_i0:
  forall i ts u,
  MMultiLet i ts u = MMultiLet 0 (map (fun t => lift i t) ts) u.
Proof.
  eauto using MMultiLet_ij with omega.
Qed.

(* -------------------------------------------------------------------------- *)

(* [MVars n] is the list [MVar 0, ..., MVar (n-1)]. *)

Definition MVars (n : nat) : list metal :=
  map MVar (nats n).

(* -------------------------------------------------------------------------- *)

(* [MProjs n t] is the list [MProj n t, ..., MProj 1 t]. *)

Definition MProjs (n : nat) (t : metal) :=
  map (fun i => MProj (i + 1) t) (rev_nats n).

(* This list has length [n]. *)

Lemma length_MProjs:
  forall n t,
  length (MProjs n t) = n.
Proof.
  unfold MProjs. intros. rewrite map_length, length_rev_nats. eauto.
Qed.

Hint Rewrite length_MProjs : length.

(* -------------------------------------------------------------------------- *)

(* Substitution (boilerplate lemmas). *)

Lemma subst_MVar:
  forall x sigma,
  (MVar x).[sigma] = sigma x.
Proof.
  intros. autosubst.
Qed.

Lemma subst_MLam:
  forall t sigma,
  (MLam t).[sigma] = MLam t.[up sigma].
Proof.
  intros. autosubst.
Qed.

Lemma subst_MApp:
  forall t1 t2 sigma,
  (MApp t1 t2).[sigma] = MApp t1.[sigma] t2.[sigma].
Proof.
  intros. autosubst.
Qed.

Lemma subst_MLet:
  forall t1 t2 sigma,
  (MLet t1 t2).[sigma] = MLet t1.[sigma] t2.[up sigma].
Proof.
  intros. autosubst.
Qed.

Lemma subst_MPair:
  forall t1 t2 sigma,
  (MPair t1 t2).[sigma] = MPair t1.[sigma] t2.[sigma].
Proof.
  intros. autosubst.
Qed.

Lemma subst_MPi:
  forall i t sigma,
  (MPi i t).[sigma] = MPi i t.[sigma].
Proof.
  intros. autosubst.
Qed.

Lemma subst_MTuple:
  forall ts sigma,
  (MTuple ts).[sigma] = MTuple (map (subst sigma) ts).
Proof.
  induction ts; intros; asimpl; [ | rewrite IHts ]; reflexivity.
Qed.

Lemma subst_MProj:
  forall n t sigma,
  (MProj n t).[sigma] = MProj n t.[sigma].
Proof.
  induction n; intros; asimpl; [ | rewrite IHn ]; autosubst.
Qed.

Lemma subst_MLetPair:
  forall t u sigma,
  (MLetPair t u).[sigma] = MLetPair t.[sigma] u.[upn 2 sigma].
Proof.
  unfold MLetPair. intros. autosubst.
Qed.

Lemma subst_MMultiLet:
  forall ts i u sigma,
  (MMultiLet i ts u).[upn i sigma] =
  MMultiLet i (map (subst sigma) ts) u.[upn (length ts) (upn i sigma)].
Proof.
  induction ts; intros; asimpl; f_equal.
  { erewrite plus_upn by tc. eauto. }
  { rewrite IHts.
    repeat erewrite upn_upn by tc.
    do 3 f_equal. omega. }
Qed.

Lemma subst_MMultiLet_0:
  forall ts u sigma,
  (MMultiLet 0 ts u).[sigma] =
  MMultiLet 0 (map (subst sigma) ts) u.[upn (length ts) sigma].
Proof.
  intros.
  change sigma with (upn 0 sigma) at 1.
  eapply subst_MMultiLet.
Qed.

Lemma subst_MVars:
  forall n sigma,
  map (subst sigma) (MVars n) = map sigma (nats n).
Proof.
  intros. unfold MVars. rewrite map_map. reflexivity.
Qed.

Lemma subst_MProjs:
  forall n t sigma,
  map (subst sigma) (MProjs n t) = MProjs n t.[sigma].
Proof.
  unfold MProjs. induction n; intros; simpl; eauto.
  rewrite subst_MProj, IHn. eauto.
Qed.

Hint Rewrite
  subst_MVar subst_MLam subst_MApp subst_MLet subst_MPair subst_MPi
  subst_MTuple subst_MProj subst_MLetPair
  subst_MMultiLet subst_MMultiLet_0
  subst_MVars subst_MProjs
: subst.

(* -------------------------------------------------------------------------- *)

(* Free variables (boilerplate lemmas). *)

Lemma fv_MVar:
  forall x k,
  fv k (MVar x) <-> x < k.
Proof.
  eauto using fv_ids_eq.
Qed.

Ltac prove_fv :=
  unfold fv; intros; asimpl;
  split; intros; unpack; [ injections | f_equal ]; eauto.

Lemma fv_MLam:
  forall t k,
  fv k (MLam t) <-> fv (k + 1) t.
Proof.
  prove_fv.
Qed.

Lemma fv_MApp:
  forall t1 t2 k,
  fv k (MApp t1 t2) <-> fv k t1 /\ fv k t2.
Proof.
  prove_fv.
Qed.

Lemma fv_MLet:
  forall t1 t2 k,
  fv k (MLet t1 t2) <-> fv k t1 /\ fv (k + 1) t2.
Proof.
  prove_fv.
Qed.

Lemma fv_MPair:
  forall t1 t2 k,
  fv k (MPair t1 t2) <-> fv k t1 /\ fv k t2.
Proof.
  prove_fv.
Qed.

Lemma fv_MPi:
  forall i t k,
  fv k (MPi i t) <-> fv k t.
Proof.
  prove_fv.
Qed.

Hint Rewrite fv_MVar fv_MLam fv_MApp fv_MLet fv_MPair fv_MPi : fv.

Lemma fv_MTuple:
  forall k ts,
  fv k (MTuple ts) <-> Forall (fv k) ts.
Proof.
  induction ts; simpl; intros; fv; split; intros; unpack.
  { econstructor. }
  { omega. }
  { rewrite IHts in *. econstructor; eauto. }
  { rewrite IHts in *. pick Forall invert. eauto. }
Qed.

Lemma fv_MProj:
  forall k n t,
  fv k (MProj n t) <-> fv k t.
Proof.
  induction n; intros; simpl; [ | rewrite IHn ]; fv; tauto.
Qed.

Lemma fv_MLetPair:
  forall k t u,
  fv k (MLetPair t u) <-> fv k t /\ fv (k + 2) u.
Proof.
  intros. unfold MLetPair. fv; tc.
  replace (k + 1 + 1) with (k + 2) by omega.
  tauto.
Qed.

Local Lemma MLet_inj:
  forall t1 u1 t2 u2,
  MLet t1 u1 = MLet t2 u2 <-> t1 = t2 /\ u1 = u2.
Proof.
  split; intros; injections; unpack; subst; eauto.
Qed.

Local Lemma cons_cons_eq:
  forall {A} (x1 x2 : A) xs1 xs2,
  x1 :: xs1 = x2 :: xs2 <->
  x1 = x2 /\ xs1 = xs2.
Proof.
  split; intros; injections; unpack; subst; eauto.
Qed.

Local Lemma MMultiLet_inj:
  forall ts1 u1 ts2 u2 i,
  length ts1 = length ts2 ->
  MMultiLet i ts1 u1 = MMultiLet i ts2 u2 <->
  ts1 = ts2 /\ u1 = u2.
Proof.
  induction ts1; destruct ts2; intros; simpl;
  try solve [ false; simpl in *; omega ].
  { tauto. }
  { rewrite MLet_inj.
    rewrite lift_injn_eq.
    rewrite IHts1 by (simpl in *; omega).
    rewrite cons_cons_eq.
    tauto. }
Qed.

Local Lemma map_inj:
  forall {A} (f : A -> A) xs,
  map f xs = xs <->
  Forall (fun x => f x = x) xs.
Proof.
  induction xs; simpl; intros; split; intros; eauto using Forall;
  injections.
  { econstructor; tauto. }
  { pick Forall invert. f_equal; tauto. }
Qed.

Lemma fv_MMultiLet_0:
  forall ts u k,
  fv k (MMultiLet 0 ts u) <->
  Forall (fv k) ts /\ fv (k + length ts) u.
Proof.
  intros. unfold fv.
  autorewrite with subst.
  rewrite MMultiLet_inj by eauto using map_length.
  rewrite upn_upn, Nat.add_comm.
  rewrite map_inj.
  tauto.
Qed.

Lemma fv_MVars:
  forall n,
  Forall (fv n) (MVars n) <->
  True.
Proof.
  split; [ eauto | intros _ ].
  unfold MVars, nats.
  eapply Forall_map.
  eapply Forall_seq; intros.
  fv. omega.
Qed.

Lemma fv_MProjs:
  forall k n t,
  fv k t -> (* not an equivalence, as [k] could be zero *)
  Forall (fv k) (MProjs n t).
Proof.
  unfold MProjs. induction n; simpl; intros;
  econstructor; [ rewrite fv_MProj |]; eauto.
Qed.

Hint Rewrite
  fv_MTuple fv_MProj fv_MLetPair fv_MMultiLet_0
  fv_MVars
  (* not fv_MProjs *)
: fv.

(* -------------------------------------------------------------------------- *)

(* The following lemma is analogous to [fv_unaffected_regular], except it does
   not have a regularity hypothesis, which makes it more pleasant to use. It is
   proved by induction on terms, which is why we are unable to prove it in a
   generic setting. *)

Lemma fv_unaffected:
  forall t k sigma,
  fv k t ->
  t.[upn k sigma] = t.
Proof.
  induction t; intros; fv; unpack; asimpl; repeat rewrite Nat.add_1_r in *;
  try solve [ eauto using upn_k_sigma_x with typeclass_instances
            | f_equal; eauto ].
Qed.

(* A corollary. *)

Lemma closed_unaffected:
  forall t sigma,
  closed t ->
  t.[sigma] = t.
Proof.
  unfold closed. intros.
  rewrite <- (upn0 sigma).
  eauto using fv_unaffected.
Qed.
