(***********************************************************************)
(**  * Connecting nominal and LN semantics *)
(***********************************************************************)
(** Our final goal is to show that the abstract nominal machine implements the same semantics as the LN substitution-based small step relation.  We'll do this by proving that any time the abstract machine takes a step, we can decode the machine configuration before and after the step to LN expressions, and those expressions are either identical or related by the LN step relation.  << machine_step (h,t,s)  ------>  (h',t',s')            nominal terms |                  | | decode           | decode v                  v e       - - ->     e'           locally nameless terms step e e' or e = e' >>  This result is the [simulate_step] lemma near the end of this file.  *)
(***********************************************************************)
(** ** Translating nominal terms to LN terms *)
(***********************************************************************)
(** We decode named terms to LN terms through the use of the [close_exp_wrt_exp] function. This function replaces all occurrences of a given atom by a new bound variable.  This function is defined in the [Stlc.Lemmas] module and can be automatically generated by LNgen.  *)
Fixpoint nom_to_exp (ne : n_exp) : exp :=
match ne with
| n_var x => var_f x
| n_app e1 e2 => app (nom_to_exp e1) (nom_to_exp e2)
| n_abs x e1 => abs (close_exp_wrt_exp x (nom_to_exp e1))
end.
(** We also define a translation from machine configurations to LN terms. In this case we must substitute all definitions in the heap through the terms and create all of the applications in the stack. *)
Fixpoint apply_heap (h : heap) (e : exp) : exp  :=
match h with
| nil => e
| (x , e') :: h' => apply_heap h' ([x ~> nom_to_exp e'] e)
end.
(* Note that the stack could have some heap definitions in it, so we apply the substitutions in the heap there too. *)
Fixpoint apply_stack h (s : list frame) (e :exp) : exp :=
match s with
| nil => e
| n_app2 e' :: s' => apply_stack h s' (app e (apply_heap h (nom_to_exp e')))
end.
(** The full decode function puts all of these parts together. *)
Definition decode (c:configuration) : exp  :=
match c with
| (h,e,s) => apply_stack h s (apply_heap h (nom_to_exp e))
end.
(** Here's an example translation from the machine step demo. *)
Definition conf1 := ([(Y,n_var Z)], n_abs X (n_var X), [n_app2 (n_var Y)]).
Example decode1 : decode conf1 = (app (abs (var_b 0)) (var_f Z)).
(***********************************************************************)
(** ** Connecting free variable functions.                             *)
(***********************************************************************)
(** Here is the first result about our decoding: that the two free variable functions agree.  In this part of the file, we will take advantage of automation provided by the Lemmas module. In particular, this module defines a database of rewriting hints (called [lngen]) that can be used to automatically rewrite LN terms into simpler form. *)
(** As we prove new lemmas, we can also extend the hint database with new rewritings. *)
Hint Rewrite fv_nom_fv_exp_eq : lngen.
Hint Resolve fv_nom_fv_exp_eq : lngen.
(** The Metatheory library contains two powerful tactics for simplifying goals:  - [default_steps]: repeat a bunch of simplifying steps, such as simplifying the goal, inverting simple hypotheses, etc.  - [default_simp]: above plus case analysis for booleans and other sums  Below, we modify the behavior of these tactics by updating the following two definitions, so that the [lngen] hint databases will be available.  *)
Ltac default_auto        ::= auto with lngen.
Ltac default_autorewrite ::= autorewrite with lngen.
(** We also add a few more rewriting lemmas to the hint database to automate our proofs. *)
Hint Rewrite subst_exp_open_exp_wrt_exp : lngen.
Hint Rewrite swap_size_eq : lngen.
Hint Resolve le_S_n : lngen.
(***********************************************************************)
(** ** Decoded terms are locally closed *)
(***********************************************************************)
(** Next, we show that our decoding of nominal terms and configurations produces locally closed LN terms.  Again, these proofs are highly automatable. *)
Hint Resolve nom_to_exp_lc : lngen.
Hint Resolve apply_heap_lc : lngen.
(** *** Exercise: [apply_stack_lc]  State and prove a lemma called [apply_stack_lc] and add it to the [lngen] hint database so that the proof for [decode_lc] goes through. *)
Hint Resolve apply_stack_lc : lngen.
(* /SOLUTION *)
(***********************************************************************)
(** ** Properties of apply_heap *)
(***********************************************************************)
(** Since the heap is just an iterated substitution, it inherits properties from [subst_exp].  Below, [alist induction] is a tactic from the metatheory library for induction over association lists (such as the heap).  *)
Hint Rewrite apply_heap_abs : lngen.
Hint Rewrite apply_heap_app : lngen.
(** *** Exercise: [apply_heap_open]  This function is the [apply_heap] analogue to [subst_exp_open_exp_wrt_exp], commuting the heap-based substitution with the [open_exp_wrt_exp] operation. *)
Hint Rewrite apply_heap_open : lngen.
(** This last lemma "unsimpl"s the [apply_heap] function. *)
(***********************************************************************)
(** ** Stacks as evaluation contexts                                    *)
(***********************************************************************)
(** Here is a quick lemma that uses the properties defined above. It shows that the stack behaves like an *evaluation context*, lifting a small-step reduction to a larger term. *)
(***********************************************************************)
(** * Connecting "freshening" *)
(***********************************************************************)
(** The abstract machine uses the nominal [swap] operation to make sure that the variables in abstractions are "fresh".  To be able prove that this machine implements the step relation for LN terms, we need to connect this operation to a LN version of "freshening".  The [swap_spec] lemma below states that connection. If a variable [y] is "fresh", then substituting with it (in the LN representation) is the same as swapping with it (in the nominal representation).  << Lemma swap_spec : forall  n w y, y `notin` fv_exp (nom_to_exp n) -> w <> y -> [w ~> var_f y] (nom_to_exp n) = nom_to_exp (swap w y n). >>  *)
(** *** Exercise [close_exp_wrt_exp_freshen] *)
(***********************************************************************)
(** ** Connection for alpha-equivalence                                 *)
(***********************************************************************)
(** *** Challenge Exercise: [aeq_nom_to_exp]  Show that alpha-equivalence for the nominal representation is definitional equality for LN terms.  This result is not necessary for the simulation lemmas below, but it is another example of the a proof that takes advantage of the [swap_spec] and [close_exp_wrt_exp_freshen] lemmas  above.  The second proof is much more challenging than the first and requires lemmas from [Lemmas.v].  *)
(***********************************************************************)
(** * Scoped configurations                                            *)
(***********************************************************************)
(** Not all abstract machine steps simulate small-steps of the substitution-based STLC.  For example, if the domain of the heap is not unique (i.e. it has multiple definitions for the same variable) then its evaluation may not produce expected results.  Similarly, if the [avoid] set that we pass to each machine step does not include initial the free variables of the term, then we could capture them in strange ways.  Therefore, we will restrict our correctness lemmas so that they only apply to _well-scoped_ configurations; as defined below. *)
(***********************************************************************)
(** ** Scoped heaps                                                    *)
(***********************************************************************)
(** Well-scoped heaps behave "telescopically".  Each binding (x,e) added to the heap is for a unique name x and the free variables of e are bound in the remainder of the heap.  The scoping relation is parameterized by [D], an "ambient scope". This will let us reason about the execution of the abstract machine for terms with free variables. *)
Inductive scoped_heap (D : atoms) : heap -> Prop :=
| scoped_nil  : scoped_heap D nil
| scoped_cons : forall x e h,
x `notin` dom h \u D ->
fv_exp (nom_to_exp e) [<=] dom h \u D ->
scoped_heap D h ->
scoped_heap D ((x,e)::h).
(** *** Recommended (Challenge) Exercise [apply_heap_get]  We can use [get] to look up expressions in the heap. However, to know that we have the right result we need to know that the heap is well-scoped, i.e.  that later bindings do not affect earlier ones.  State a lemma about the scoping of expressions that appear in the heap and use it to finish the [apply_heap_get] lemma below.  *)
(* /SOLUTION *)
(***********************************************************************)
(** ** Scoped stacks                                                    *)
(***********************************************************************)
(** We also care about the free variables that can appear in stacks. (We will use this in the definition of well-scoped configurations below.) *)
Fixpoint fv_stack s :=
match s with
nil => {}
| n_app2 e :: s => fv_exp (nom_to_exp e) \u fv_stack s
end.
(** Stacks that are well-scoped can discard irrelevant bindings from the heap. *)
(***********************************************************************)
(** * Simulation                                                       *)
(***********************************************************************)
(** A scoped configuration is one where all free variables in terms are either bound in the heap or come from [D] *)
Inductive scoped_conf : atoms -> configuration -> Prop :=
scoped_conf_witness : forall D h e s,
scoped_heap D h ->
fv_exp (nom_to_exp e) [<=] dom h \u D ->
fv_stack s [<=] dom h \u D  ->
scoped_conf D (h,e,s).
(** *** Exercise [simulate_step]  After stepping through the simulation result below, finish the missing case.  *)

(* Could be zero or one steps! *)
Lemma simulate_step : forall D h e s h' e' s' ,
machine_step D (h,e,s) = TakeStep _ (h',e',s') ->
scoped_conf D (h,e,s) ->
decode (h,e,s) = decode (h',e',s') \/
step (decode (h,e,s)) (decode (h',e',s')).