A translation for untyped arithmetic expressions in Coq

Example from CPDT of a translation from a source to language translation (untyped).

A walkthrough of an example from Chapter 2 of Certified Programming with Dependent Types by A. Chlipala where terms in one language (source language) are converted to terms in another language (target language) while preserving their meaning. Both languages are untyped.

Source and target language are defined within coq, along with a function to translate source language terms to corresponding terms in the target language. Finally, it is proven that the translation is correct.

Thanks a bunch to the people on coq zulip who helped me understand this. Mistakes are my own (and as usual, corrections much appreciated!).

(This blog post is quite verbose as output from coq is also included in generous measure.)

Source language

First, let's define a source language.

Let's say that it has the following requirements:

Let's define the allowed binary operations (these will be used for the target language as well).

Inductive binop : Set := Plus | Mult.

ie, the allowed operations are addition (Plus) and multiplication (Mult). Remember that these are names that we gave and as of now they don't have any meaning. But we'll give them meaning (with a binopDenote function).

(Only addition and multiplication operations are included in the source language to keep things simpler.

Subtraction of nat could lead to situations like

Compute Nat.sub 2 3.  (* not a negative number *)
(*
= 0
     : nat
*)

and division could result in stuff like

Compute Nat.div 3 2.  (* not 1.5 *)
(*
= 1
     : nat
*)

Compute Nat.div 3 0.  (* not divide by zero error *)
(*
= 0
     : nat
*)

because after all, nat represents natural numbers. )

Check Plus.
(*
Plus
     : binop
*)

Now let's define the type of expressions, which consists of constants and binary operations.

Inductive exp : Set :=
| Const : nat -> exp
| Binop : binop -> exp -> exp -> exp.

Expressions need either be nat constants (Const) or a binary operation taking two expressions to return an expression.

Compute Const 3.
(*
= Const 3
     : exp
*)

Compute Binop Plus (Const 3) (Const 4).
(*
= Binop Plus (Const 3) (Const 4)
     : exp
*)

Okay, time to give meaning (semantics) to what we have defined so far (we use denotational semantics). First, let's do it for binop via a binopDenote function.

Definition binopDenote (op : binop) : nat -> nat -> nat :=
  match op with
  | Plus => plus  (* Same as Nat.add *)
  | Mult => mult  (* Same as Nat.mul *)
  end.

We map what we defined for the source language (our object language here) to corresponding values in Coq (our meta language).

(* Just an example. This is not the way this is meant to be used *)
Compute (binopDenote Plus) 3 4.
(*
= 7
     : nat
*)

We can't use Const values yet as we haven't given meaning to exp values at this point. Let's fix that a expDenote function.

Fixpoint expDenote (e : exp) : nat :=
  match e with
  | Const n => n
  | Binop op e1 e2 => (binopDenote op) (expDenote e1) (expDenote e2)
  end.

Since nat is only type of constants in the source language, the normalized value of any exp would be a nat.

Now we can do

(* 3 + 4 *)
Compute expDenote (Binop Plus (Const 3) (Const 4)).
(*
= 7
     : nat
*)

(* 3 + (4 * 2) +1 *)
Definition foo :=
  (Binop Plus
    (Binop Plus
      (Const 3)
      (Binop Mult
        (Const 4)
        (Const 2)))
    (Const 1)).
Compute expDenote foo.
(*
= 12
     : nat
*)

Source language has been defined. Let's define the target language next.

Target language

Let's say that our target language has the following properties:

Target language consists of instructions stored in the stack. The type of instructions may be defined as

Inductive instr : Set :=
| iConst : nat -> instr
| iBinop : binop -> instr.

iBinop doesn't take operands as parameters as the operands will be obtained from the stack during evaluation.

Compute iConst 4.
(*
= iConst 4
     : instr
*)

Compute iBinop Plus.
(*
= iBinop Plus
     : instr
*)

Type of stack, which is just a list of nat values here, may be defined as

Definition stack : Set := list nat.

where the head of the list is the top of the stack.

A small check to see if it's okay:

Check [3; 2]%list : stack.
(*
[3; 2] : stack
     : stack
*)

Now let's give meaning to instr with a instrDenote function.

Definition instrDenote (i : instr) (s : stack) : option stack :=
 match i with
 | iConst n => Some (n :: s)
 | iBinop b =>
    match s with
    | (s2 :: s1 :: ss) =>
      let nval := ((binopDenote b) s1 s2) in
        Some (nval :: ss)
    | _ => None  (* Stack prematurely empty! *)
    end
 end.

If i is a binary operation (iBinop), it is executed using operands obtained by popping two values from the stack the result of which is pushed back into it. But if i is constant (iConst), instrDenote merely pushes the constant value into the stack.

Return type is option stack because sometimes problems like stack underflow can happen and it may not be possible to evaluate the instruction in which case None is returned.

We re-use the binop and binopDenote that we had defined earlier as it's the same for both source and target languages.

Right, let's give instrDenote a whirl.

Compute instrDenote (iConst 4) []%list.
(*
= Some [4]
     : option stack
*)

Compute instrDenote (iBinop Plus) [3; 4]%list.
(*
= Some [7]
     : option stack
*)

Compute instrDenote (iBinop Plus) [3]%list.
(*
= None
     : option stack
*)

It seems to be working okay.

We could use a function to give meaning to a list of instructions all at once. As a step towards that, let's define a type, program, which represents a list of instr (after all, a program is a list of instructions).

Definition prog : Set := list instr.

Small check:

Check [iConst 3; iBinop Plus; iBinop Mult]%list : prog.
(*
*[iConst 3; iBinop Plus; iBinop Mult] : prog
     : prog
)

Now we give meaning to a prog:

Fixpoint progDenote (p : prog) (s : stack) : option stack :=
  match p with
  | (px::pxs) =>
    match (instrDenote px s) with
    | Some s' => progDenote pxs s'
    | None => None
    end
  | _ => Some s
  end.

In addition to a prog value, progDenote takes a stack containing the values that the program might need.

Giving progDenote a whirl,

Compute progDenote [iConst 3; iBinop Plus; iBinop Mult]%list [4; 5]%list.
(*
= Some [35]
     : option stack
*)

Yep, looks alright.

Translation

We can now make a function to translate (or compile) source language terms to equivalent terms in the target language.

Fixpoint compile (e : exp) : list instr :=
  match e with
  | Const n => [iConst n]%list
  | Binop op e1 e2 =>  (compile e2) ++ (compile e1) ++ [iBinop op]%list
  end.

An example use:

Definition foo :=
  (Binop Plus
    (Binop Plus
      (Const 3)
      (Binop Mult
        (Const 4)
        (Const 2)))
    (Const 1)).
Compute compile foo.
(*
= [iConst 3; iConst 4; iConst 2; iBinop Mult; 
       iBinop Plus; iConst 1; iBinop Plus]
     : list instr
*)

Recap of definitions

So, to put all the definitions that we made so far together,

(** * Source language **)

Inductive binop : Set := Plus | Mult.

Inductive exp : Set :=
| Const : nat -> exp
| Binop : binop -> exp -> exp -> exp.

Definition binopDenote (op : binop) : nat -> nat -> nat :=
  match op with
  | Plus => plus  (* Same as Nat.add *)
  | Mult => mult  (* Same as Nat.mul *)
  end.

Fixpoint expDenote (e : exp) : nat :=
  match e with
  | Const n => n
  | Binop op e1 e2 => (binopDenote op) (expDenote e1) (expDenote e2)
  end.



(** * Target language **)

Inductive instr : Set :=
| iConst : nat -> instr
| iBinop : binop -> instr.

Definition stack : Set := list nat.

Definition instrDenote (i : instr) (s : stack) : option stack :=
 match i with
 | iConst n => Some (n :: s)
 | iBinop b =>
    match s with
    | (s2 :: s1 :: ss) =>
      let nval := ((binopDenote b) s1 s2) in
        Some (nval :: ss)
    | _ => None  (* Stack prematurely empty! *)
    end
 end.

Definition prog : Set := list instr.

Fixpoint progDenote (p : prog) (s : stack) : option stack :=
  match p with
  | (px::pxs) =>
    match (instrDenote px s) with
    | Some s' => progDenote pxs s'
    | None => None
    end
  | _ => Some s
  end.



(** * Translation *)

Fixpoint compile (e : exp) : list instr :=
  match e with
  | Const n => [iConst n]%list
  | Binop op e1 e2 =>  (compile e1) ++ (compile e2) ++ [iBinop op]%list
  end.

Translation correctness proof

Okay, we now have a way to translate source language terms to target language terms. Let's try proving that this translation is correct.

For this, it is sufficient to prove that

∀ (e : exp) (p : prog) (s : stack),
  progDenote ((compile e) ++ p) s = progDenote p ((expDenote e)::s)

ie, the meaning of a program (ie, list instr) obtained by compile-ing an exp, to which another program p has been appended, when used with a stack s is same as the meaning of p when having the same s with the meaning of the same exp pushed at the top of it.

Let's try proving that in Coq.

Theorem compile_correct : forall (e : exp) (p : prog) (s : stack),
  progDenote ((compile e) ++ p) s = progDenote p ((expDenote e)::s).
Proof.
(* Goal:
1 subgoal


========================= (1 / 1)

forall (e : exp) (p : prog) (s : stack),
progDenote (compile e ++ p) s = progDenote p (expDenote e :: s)
*)

Doing induction on e.

induction e.
(*
2 subgoals

n : nat

========================= (1 / 2)

forall (p : prog) (s : stack),
progDenote (compile (Const n) ++ p) s =
progDenote p (expDenote (Const n) :: s)

========================= (2 / 2)

forall (p : prog) (s : stack),
progDenote (compile (Binop b e1 e2) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)
*)

Now we got two (sub-)goals.

See how e : exp went away from the forall.

An exp can be one of two things: Const or Binop. Hence performing induction on e : exp leads to two sub-goals, with each sub-goal having a possible exp value.

When e : exp is replaced with a Const n,So, progDenote (compile e ++ p) s = progDenote p (expDenote e :: s) changes as follows:

| Old term                        | New e (e ↦)   | New term                                      |
|---------------------------------+---------------+-----------------------------------------------|
| progDenote (compile e ++ p) s = |               | progDenote (compile (Const n) ++ p) s =       |
| progDenote p (expDenote e :: s) | Const n       | progDenote p (expDenote (Const n) :: s)       |
|---------------------------------+---------------+-----------------------------------------------|
| progDenote (compile e ++ p) s = |               | progDenote (compile (Binop b e1 e2) ++ p) s = |
| progDenote p (expDenote e :: s) | Binop b e1 e2 | progDenote p (expDenote (Binop b e1 e2) :: s) |

Sub-goal 1

The goal is:

1 subgoal
(1 unfocused at this level)

n : nat

========================= (1 / 1)

forall (p : prog) (s : stack),
progDenote (compile (Const n) ++ p) s =
progDenote p (expDenote (Const n) :: s)

First, let's make the variables in the forall quantifier free with intros.

- intros.
(*
1 subgoal
(1 unfocused at this level)

n : nat
p : prog
s : stack

========================= (1 / 1)

progDenote (compile (Const n) ++ p) s =
progDenote p (expDenote (Const n) :: s)
*)

The unfold tactic can replace 'an identifier with its definition'. fold is for the other way around.

Replacing the occurrences of compile with its definitions so that 'coq can see them' with unfold,

unfold compile.
(*
1 subgoal
(1 unfocused at this level)

n : nat
p : prog
s : stack

========================= (1 / 1)

progDenote ([iConst n] ++ p) s = progDenote p (expDenote (Const n) :: s)
*)

The progDenote (compile (Const n) ++ p) s changed to progDenote ([iConst n] ++ p) s like

progDenote (compile (Const n) ++ p) s 

↓

progDenote (
  (fix compile (e : exp) : list instr :=
    match e with
    | Const n => [iConst n]%list
    | Binop op e1 e2 =>  (compile e1) ++ (compile e2) ++ [iBinop op]%list
    end)
  (Const n) ++ p) s.

↓

progDenote ([iConst n]%list ++ p) s.

Let's see what simpl can make out of this:

simpl.
(*
1 subgoal
(1 unfocused at this level)

n : nat
p : prog
s : stack

========================= (1 / 1)

progDenote p (n :: s) = progDenote p (n :: s)
*)

simpl was able to make the following changes to the goal:

| Old                                     | New                   |
|-----------------------------------------+-----------------------|
| progDenote ([iConst n] ++ p) s          | progDenote p (n :: s) |
| progDenote p (expDenote (Const n) :: s) | progDenote p (n :: s) |

The second change is straightforward as expDenote (Const n) evaluates to to n.

The change to progDenote ([iConst n] ++ p) s is not that straightforward. It looks something like a 'partial' evaluation of progDenote.

That's because simpl is not so simple as it sounds. It's sort of a special tactic. As the docs suggest, it is a 'clever' tactic meant to give the most readable result. (for example, a goal with a bunch of unfolded fixpoint functions isn't so readable but their folded version is.)

simpl 'reduces a term to something still readable instead of fully normalizing it'. (That probably means complicated heuristics to determine what is 'readable' and what is not.)

Since by this point, both LHS and RHS of the goal are same, we can finish off this sub-goal with

reflexivity.

On to the second sub-goal!

Sub-goal 2

The goal is:

1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)

========================= (1 / 1)

forall (p : prog) (s : stack),
progDenote (compile (Binop b e1 e2) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)

Let's introduce the variables like in the other sub-goal,

- intros.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote (compile (Binop b e1 e2) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)
*)

Replacing compile with its definitions using unfold,

unfold compile.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote
  (((fix compile (e : exp) : list instr :=
       match e with
       | Const n => [iConst n]
       | Binop op e0 e3 => compile e0 ++ compile e3 ++ [iBinop op]
       end) e1 ++
    (fix compile (e : exp) : list instr :=
       match e with
       | Const n => [iConst n]
       | Binop op e0 e3 => compile e0 ++ compile e3 ++ [iBinop op]
       end) e2 ++ [iBinop b]) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)
*)

e0 and e3 are names chosen by coq (since e1 and e2 are names that are already used).

progDenote (compile (Binop b e1 e2) ++ p) s changed as

progDenote (compile (Binop b e1 e2) ++ p) s

↓

progDenote (
  (fix compile (e : exp) : list instr := 
   match e with
   | Const n => [iConst n]%list
   | Binop op e0 e3 =>  (compile e0) ++ (compile e3) ++ [iBinop op]%list
   end)
  (Binop b e1 e2) ++ p) s

↓

progDenote (
  (compile e1) ++
  (compile e2) ++
  [iBinop b]%list ++
  p) s

↓

progDenote (
  (fix compile (e : exp) : list instr :=
   match e with
   | Const n => [iConst n]%list
   | Binop op e0 e3 => (compile e0) ++ (compile e3) ++ [iBinop op]%list
   end) e1 ++
  (fix compile (e : exp) : list instr :=
   match e with
   | Const n => [iConst n]%list
   | Binop op e0 e3 => (compile e0) ++ (compile e3) ++ [iBinop op]%list
   end) e2 ++
  [iBinop b]%list ++
  p) s

Now, we fold compile back up.

It might seem pointless at first to do a fold right after a unfold on the same function and one may feel tempted to compare it with something like -2 + 2 of basic arithmetic, something that changes nothing, but it isn't so.

Because here, unfold revealed what compile is made up of, allowing coq to see deep into the definition of compile itself.

If we do fold on compile now, coq will readily change the goal to

progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)

So,

fold compile.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =
progDenote p (expDenote (Binop b e1 e2) :: s)
*)

The definitions of compile merely got replaced by the name compile.

Unfolding expDenote next,

unfold expDenote.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =
progDenote p
  (binopDenote b
     ((fix expDenote (e : exp) : nat :=
         match e with
         | Const n => n
         | Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)
         end) e1)
     ((fix expDenote (e : exp) : nat :=
         match e with
         | Const n => n
         | Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)
         end) e2) :: s)
*)

The change in goal happened like

progDenote p (expDenote (Binop b e1 e2) :: s)

↓

progDenote p (
  (fix expDenote (e : exp) : nat :=
     match e with
     | Const n => n
     | Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)
     end)
  (Binop b e1 e2) :: s)

↓

progDenote p (
  (binopDenote b
    (expDenote e1)
    (expDenote e2)
  ) :: s)

↓

progDenote p (
  (binopDenote b
  ((fix expDenote (e : exp) : nat :=
      match e with
      | Const n => n
      | Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)
      end) e1)
  ((fix expDenote (e : exp) : nat :=
      match e with
      | Const n => n
      | Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)
      end) e2)
  ) :: s)

Now folding expDenote back up,

fold expDenote.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

Now we need the help of a proof that attests to the associativity property of list concatenation. Coq has a lemma for that built-in, app_assoc_reverse, which is defined as:

app_assoc_reverse
     : forall (A : Type) (l m n : list A), (l ++ m) ++ n = l ++ m ++ n

Using app_assoc_reverse for a left-to-right rewrite,

rewrite app_assoc_reverse.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote (compile e1 ++ (compile e2 ++ [iBinop b]) ++ p) s =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

The goal changed from

progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s
             |        |    |                      |     |
             +--------+    +----------------------+     +
                 l                    m                 n

to

progDenote (compile e1 ++ (compile e2 ++ [iBinop b]) ++ p) s
            |        |     |                      |     |
            +--------+     +----------------------+     +
                 l                     m                n

Coming back to the proof, it's time for a few more rewrites…

rewrite IHe1.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

progDenote ((compile e2 ++ [iBinop b]) ++ p) (expDenote e1 :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

rewrite app_assoc_reverse.
(*
progDenote (compile e2 ++ [iBinop b] ++ p) (expDenote e1 :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

rewrite IHe2.
(*
progDenote ([iBinop b] ++ p) (expDenote e2 :: expDenote e1 :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

Unfolding only the first progDenote (the one in the LHS of goal), evaluating and folding it back up (not really necessary, just so that it will look nice. In fact, the proof can finish at this point with a reflexivity) and finally finishing off the goal,

unfold progDenote at 1.
(*
1 subgoal

b : binop
e1, e2 : exp
IHe1 : forall (p : prog) (s : stack),
       progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)
IHe2 : forall (p : prog) (s : stack),
       progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)
p : prog
s : stack

========================= (1 / 1)

(fix progDenote (p0 : prog) (s0 : stack) {struct p0} : option stack :=
   match p0 with
   | [] => Some s0
   | px :: pxs =>
       match instrDenote px s0 with
       | Some s' => progDenote pxs s'
       | None => None
       end
   end) ([iBinop b] ++ p) (expDenote e2 :: expDenote e1 :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

simpl.  (* the 'not-so-simple' simpl :) *)
(*
(fix progDenote (p0 : prog) (s0 : stack) {struct p0} : option stack :=
   match p0 with
   | [] => Some s0
   | px :: pxs =>
       match instrDenote px s0 with
       | Some s' => progDenote pxs s'
       | None => None
       end
   end) p (binopDenote b (expDenote e1) (expDenote e2) :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

fold progDenote.
(*
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s) =
progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)
*)

reflexivity.
(*
0 subgoals

All goals completed.
*)

And we're done! We've proven that the translation is indeed correct.

References

Proof steps

An overview of the proof steps along with the changing goals.

Second column has the goal after applying the tactic in the first column of the corresponding row.

| Step                       | Goal                                                                     |
|----------------------------+--------------------------------------------------------------------------|
|                            | 1 subgoal                                                                |
|                            |                                                                          |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | forall (e : exp) (p : prog) (s : stack),                                 |
|                            | progDenote (compile e ++ p) s = progDenote p (expDenote e :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| induction e.               |                                                                          |
| (*                         | 2 subgoals                                                               |
| Induction on e.            |                                                                          |
| Generates 2 sub-goals.     | n : nat                                                                  |
| *)                         |                                                                          |
|                            | ========================= (1 / 2)                                        |
|                            |                                                                          |
|                            | forall (p : prog) (s : stack),                                           |
|                            | progDenote (compile (Const n) ++ p) s =                                  |
|                            | progDenote p (expDenote (Const n) :: s)                                  |
|                            |                                                                          |
|                            | ========================= (2 / 2)                                        |
|                            |                                                                          |
|                            | forall (p : prog) (s : stack),                                           |
|                            | progDenote (compile (Binop b e1 e2) ++ p) s =                            |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|                            |                                                                          |
|                            |                                                                          |
|----------------------------+--------------------------------------------------------------------------|
| (*                         | 1 subgoal                                                                |
| Focusing on 1st sub-goal   | (1 unfocused at this level)                                              |
| *)                         |                                                                          |
|                            | n : nat                                                                  |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | forall (p : prog) (s : stack),                                           |
|                            | progDenote (compile (Const n) ++ p) s =                                  |
|                            | progDenote p (expDenote (Const n) :: s)                                  |
|                            |                                                                          |
|----------------------------+--------------------------------------------------------------------------|
| intros.                    | 1 subgoal                                                                |
| (*                         | (1 unfocused at this level)                                              |
| Free variables             |                                                                          |
| *)                         | n : nat                                                                  |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote (compile (Const n) ++ p) s =                                  |
|                            | progDenote p (expDenote (Const n) :: s)                                  |
|----------------------------+--------------------------------------------------------------------------|
| unfold compile.            | 1 subgoal                                                                |
|                            | (1 unfocused at this level)                                              |
|                            |                                                                          |
|                            | n : nat                                                                  |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ([iConst n] ++ p) s = progDenote p (expDenote (Const n) :: s) |
|----------------------------+--------------------------------------------------------------------------|
| simpl.                     | 1 subgoal                                                                |
|                            | (1 unfocused at this level)                                              |
|                            |                                                                          |
|                            | n : nat                                                                  |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote p (n :: s) = progDenote p (n :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| reflexivity.               | 0 subgoals                                                               |
| (*                         | (1 unfocused at this level)                                              |
| 1 of 2 sub-goals complete  |                                                                          |
| *)                         | Next goal (use bullet '-'):                                              |
|                            |                                                                          |
|                            | forall (p : prog) (s : stack),                                           |
|                            | progDenote (compile (Binop b e1 e2) ++ p) s =                            |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| (*                         | 1 subgoal                                                                |
| Focusing on 2nd sub-goal   |                                                                          |
| *)                         | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | forall (p : prog) (s : stack),                                           |
|                            | progDenote (compile (Binop b e1 e2) ++ p) s =                            |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| intros.                    | 1 subgoal                                                                |
| (*                         |                                                                          |
| Free variables             | b : binop                                                                |
| *)                         | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote (compile (Binop b e1 e2) ++ p) s =                            |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| unfold compile.            | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote                                                               |
|                            | (((fix compile (e : exp) : list instr :=                                 |
|                            | match e with                                                             |
|                            | │ Const n => [iConst n]                                                  |
|                            | │ Binop op e0 e3 => compile e0 ++ compile e3 ++ [iBinop op]              |
|                            | end) e1 ++                                                               |
|                            | (fix compile (e : exp) : list instr :=                                   |
|                            | match e with                                                             |
|                            | │ Const n => [iConst n]                                                  |
|                            | │ Binop op e0 e3 => compile e0 ++ compile e3 ++ [iBinop op]              |
|                            | end) e2 ++ [iBinop b]) ++ p) s =                                         |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| fold compile.              | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =           |
|                            | progDenote p (expDenote (Binop b e1 e2) :: s)                            |
|----------------------------+--------------------------------------------------------------------------|
| unfold expDenote.          | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =           |
|                            | progDenote p                                                             |
|                            | (binopDenote b                                                           |
|                            | ((fix expDenote (e : exp) : nat :=                                       |
|                            | match e with                                                             |
|                            | │ Const n => n                                                           |
|                            | │ Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)         |
|                            | end) e1)                                                                 |
|                            | ((fix expDenote (e : exp) : nat :=                                       |
|                            | match e with                                                             |
|                            | │ Const n => n                                                           |
|                            | │ Binop op e0 e3 => binopDenote op (expDenote e0) (expDenote e3)         |
|                            | end) e2) :: s)                                                           |
|----------------------------+--------------------------------------------------------------------------|
| fold expDenote.            | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ((compile e1 ++ compile e2 ++ [iBinop b]) ++ p) s =           |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| rewrite app_assoc_reverse. | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote (compile e1 ++ (compile e2 ++ [iBinop b]) ++ p) s =           |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| rewrite IHe1.              | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ((compile e2 ++ [iBinop b]) ++ p) (expDenote e1 :: s) =       |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| rewrite app_assoc_reverse. | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote (compile e2 ++ [iBinop b] ++ p) (expDenote e1 :: s) =         |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| rewrite IHe2.              | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote ([iBinop b] ++ p) (expDenote e2 :: expDenote e1 :: s) =       |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| unfold progDenote at 1.    | 1 subgoal                                                                |
| (*                         |                                                                          |
| Unfold only the first      | b : binop                                                                |
| occurrence of progDenote   | e1, e2 : exp                                                             |
| *)                         | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | (fix progDenote (p0 : prog) (s0 : stack) {struct p0} : option stack :=   |
|                            | match p0 with                                                            |
|                            | │ [] => Some s0                                                          |
|                            | │ px :: pxs =>                                                           |
|                            | match instrDenote px s0 with                                             |
|                            | │ Some s' => progDenote pxs s'                                           |
|                            | │ None => None                                                           |
|                            | end                                                                      |
|                            | end) ([iBinop b] ++ p) (expDenote e2 :: expDenote e1 :: s) =             |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| simpl.                     | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | (fix progDenote (p0 : prog) (s0 : stack) {struct p0} : option stack :=   |
|                            | match p0 with                                                            |
|                            | │ [] => Some s0                                                          |
|                            | │ px :: pxs =>                                                           |
|                            | match instrDenote px s0 with                                             |
|                            | │ Some s' => progDenote pxs s'                                           |
|                            | │ None => None                                                           |
|                            | end                                                                      |
|                            | end) p (binopDenote b (expDenote e1) (expDenote e2) :: s) =              |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| fold progDenote.           | 1 subgoal                                                                |
|                            |                                                                          |
|                            | b : binop                                                                |
|                            | e1, e2 : exp                                                             |
|                            | IHe1 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e1 ++ p) s = progDenote p (expDenote e1 :: s)        |
|                            | IHe2 : forall (p : prog) (s : stack),                                    |
|                            | progDenote (compile e2 ++ p) s = progDenote p (expDenote e2 :: s)        |
|                            | p : prog                                                                 |
|                            | s : stack                                                                |
|                            |                                                                          |
|                            | ========================= (1 / 1)                                        |
|                            |                                                                          |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s) =        |
|                            | progDenote p (binopDenote b (expDenote e1) (expDenote e2) :: s)          |
|----------------------------+--------------------------------------------------------------------------|
| reflexivity.               | 0 subgoals                                                               |
| (*                         |                                                                          |
| 2 of 2 sub-goals complete  | All goals completed.                                                     |
| *)                         |                                                                          |
|----------------------------+--------------------------------------------------------------------------|

So, the whole proof in copy-pastable form is:

Theorem compile_correct : forall (e : exp) (p : prog) (s : stack),
  progDenote ((compile e) ++ p) s = progDenote p ((expDenote e)::s).
Proof.
  induction e.
  - intros.
    unfold compile.
    simpl.
    reflexivity.
  - intros.
    unfold compile.
    fold compile.
    unfold expDenote.
    fold expDenote.
    rewrite app_assoc_reverse.
    rewrite IHe1.
    rewrite app_assoc_reverse.
    rewrite IHe2.
    unfold progDenote at 1.
    simpl.
    fold progDenote.
    reflexivity.
Qed.