Require Import Frap.

(* The following definition closely mirrors a standard BNF grammar for expressions.
 * It defines abstract syntax trees of arithmetic expressions. *)
Inductive arith : Set :=
| Const (n : nat)
| Plus (e1 e2 : arith)
| Times (e1 e2 : arith).

(* Here are a few examples of specific expressions. *)
Example ex1 := Const 42.
Example ex2 := Plus (Const 1) (Times (Const 2) (Const 3)).

(* How many nodes appear in the tree for an expression? *)
Fixpoint size (e : arith) : nat :=
  match e with
  | Const _ => 1
  | Plus e1 e2 => 1 + size e1 + size e2
  | Times e1 e2 => 1 + size e1 + size e2
  end.

(* Here's how to run a program (evaluate a term) in Coq. *)
Compute size ex1.
Compute size ex2.

(* What's the longest path from the root of a syntax tree to a leaf? *)
Fixpoint depth (e : arith) : nat :=
  match e with
  | Const _ => 1
  | Plus e1 e2 => 1 + max (depth e1) (depth e2)
  | Times e1 e2 => 1 + max (depth e1) (depth e2)
  end.

Compute depth ex1.
Compute depth ex2.

(* Our first proof!
 * Size is an upper bound on depth. *)
Theorem depth_le_size : forall e, depth e <= size e.
Proof.
Admitted.

(* A silly recursive function: swap the operand orders of all binary operators. *)
Fixpoint commuter (e : arith) : arith :=
  match e with
  | Const _ => e
  | Plus e1 e2 => Plus (commuter e2) (commuter e1)
  | Times e1 e2 => Times (commuter e2) (commuter e1)
  end.

Compute commuter ex1.
Compute commuter ex2.

(* [commuter] has all the appropriate interactions with other functions (and itself). *)

Theorem size_commuter : forall e, size (commuter e) = size e.
Proof.
Admitted.

Theorem depth_commuter : forall e, depth (commuter e) = depth e.
Proof.
Admitted.

Theorem commuter_inverse : forall e, commuter (commuter e) = e.
Proof.
Admitted.


















(* Now we go back and add this constructor to [arith]:
   <<
  | Var (x : var)
   >>

(* Now that we have variables, we can consider new operations,
 * like substituting an expression for a variable. *)
Fixpoint substitute (inThis : arith) (replaceThis : var) (withThis : arith) : arith :=
  match inThis with
  | Const _ => inThis
  | Var x => if x ==v replaceThis then withThis else inThis
  | Plus e1 e2 => Plus (substitute e1 replaceThis withThis) (substitute e2 replaceThis withThis)
  | Times e1 e2 => Times (substitute e1 replaceThis withThis) (substitute e2 replaceThis withThis)
  end.

(* An intuitive property about how much [substitute] might increase depth. *)
Theorem substitute_depth : forall replaceThis withThis inThis,
  depth (substitute inThis replaceThis withThis) <= depth inThis + depth withThis.
Proof.
  admit.
Qed.

(* A silly self-substitution has no effect. *)
Theorem substitute_self : forall replaceThis inThis,
  substitute inThis replaceThis (Var replaceThis) = inThis.
Proof.
  admit.
Qed.

(* We can do substitution and commuting in either order. *)
Theorem substitute_commuter : forall replaceThis withThis inThis,
  commuter (substitute inThis replaceThis withThis)
  = substitute (commuter inThis) replaceThis (commuter withThis).
Proof.
  admit.
Qed.

(* *Constant folding* is one of the classic compiler optimizations.
 * We repeatedly find opportunities to replace fancier expressions
 * with known constant values. *)
Fixpoint constantFold (e : arith) : arith :=
  match e with
  | Const _ => e
  | Var _ => e
  | Plus e1 e2 =>
    let e1' := constantFold e1 in
    let e2' := constantFold e2 in
    match e1', e2' with
    | Const n1, Const n2 => Const (n1 + n2)
    | Const 0, _ => e2'
    | _, Const 0 => e1'
    | _, _ => Plus e1' e2'
    end
  | Times e1 e2 =>
    let e1' := constantFold e1 in
    let e2' := constantFold e2 in
    match e1', e2' with
    | Const n1, Const n2 => Const (n1 * n2)
    | Const 1, _ => e2'
    | _, Const 1 => e1'
    | Const 0, _ => Const 0
    | _, Const 0 => Const 0
    | _, _ => Times e1' e2'
    end
  end.

(* This is supposed to be an *optimization*, so it had better not *increase*
 * the size of an expression! *)
Theorem size_constantFold : forall e, size (constantFold e) <= size e.
Proof.
  admit.
Qed.

(* Business as usual, with another commuting law *)
Theorem commuter_constantFold : forall e, commuter (constantFold e) = constantFold (commuter e).
Proof.
  admit.
Qed.

(* To define a further transformation, we first write a roundabout way of
 * testing whether an expression is a constant. *)
Definition isConst (e : arith) : option nat :=
  match e with
  | Const n => Some n
  | _ => None
  end.

(* Our next target is a function that finds multiplications by constants
 * and pushes the multiplications to the leaves of syntax trees.
 * This helper function takes a coefficient [multiplyBy] that should be
 * applied to an expression. *)
Fixpoint pushMultiplicationInside' (multiplyBy : nat) (e : arith) : arith :=
  match e with
  | Const n => Const (multiplyBy * n)
  | Var _ => Times (Const multiplyBy) e
  | Plus e1 e2 => Plus (pushMultiplicationInside' multiplyBy e1)
                       (pushMultiplicationInside' multiplyBy e2)
  | Times e1 e2 =>
    match isConst e1 with
    | Some k => pushMultiplicationInside' (k * multiplyBy) e2
    | None => Times (pushMultiplicationInside' multiplyBy e1) e2
    end
  end.

(* The overall transformation just fixes the initial coefficient as [1]. *)
Definition pushMultiplicationInside (e : arith) : arith :=
  pushMultiplicationInside' 1 e.

(* Let's prove this boring arithmetic property, so that we may use it below. *)
Lemma n_times_0 : forall n, n * 0 = 0.
Proof.
  linear_arithmetic.
Qed.

(* A fun fact about pushing multiplication inside:
 * the coefficient has no effect on depth!
 * Let's show that any coefficient is equivalent to coefficient 0. *)
Lemma depth_pushMultiplicationInside'_irrelevance0 : forall e multiplyBy,
  depth (pushMultiplicationInside' multiplyBy e)
  = depth (pushMultiplicationInside' 0 e).
Proof.
  admit.
Qed.

(* Let's prove that pushing-inside has only a small effect on depth,
 * considering for now only coefficient 0. *)
Lemma depth_pushMultiplicationInside' : forall e,
  depth (pushMultiplicationInside' 0 e) <= S (depth e).
Proof.
  admit.
Qed.

Theorem depth_pushMultiplicationInside : forall e,
  depth (pushMultiplicationInside e) <= S (depth e).
Proof.
  admit.
Qed.
*)
