inductive BinOp where | plus | times inductive Expr where | const : Nat → Expr | binOp : BinOp → Expr → Expr → Expr def BinOp.denote (op : BinOp) : Nat → Nat → Nat := match op with | plus => Nat.add | times => Nat.mul def Expr.denote (e : Expr) : Nat := match e with | const v => v | binOp op a b => op.denote a.denote b.denote /-- info: 42 -/ #guard_msgs in #eval Expr.denote (Expr.const 42) /-- info: 42 -/ #guard_msgs in #eval Expr.denote (.const 42) /-- info: 42 -/ #guard_msgs in #eval Expr.denote <| .const 42 /-- info: 5 -/ #guard_msgs in #eval Expr.denote <| .binOp .plus (.const 2) (.const 3) /-- info: 6 -/ #guard_msgs in #eval Expr.denote <| .binOp .times (.const 2) (.const 3) #eval (.binOp .times (.binOp .plus (.const 4) (.const 2)) (.const 3) : Expr).denote def e₁ : Expr := .binOp .times (.binOp .plus (.const 4) (.const 2)) (.const 3) example : e₁.denote = 18 := rfl inductive Instr where | const : Nat → Instr | binOp : BinOp → Instr abbrev Prog := List Instr abbrev Stack := List Nat def Instr.denote (i : Instr) (s : Stack) : Option Stack := match i, s with | const v, s => some (v :: s) | binOp op, a::b::s' => some (op.denote a b :: s') | _, _ => none def Prog.denote (p : Prog) (s : Stack) : Option Stack := match p with | [] => some s | i :: (p' : Prog) => match i.denote s with | none => none | some s' => p'.denote s' def Expr.compile (e : Expr) : Prog := match e with | const v => [.const v] | binOp op a b => b.compile ++ a.compile ++ [.binOp op] theorem compile_correct' (e : Expr) (p : Prog) (s : Stack) : (e.compile ++ p : Prog).denote s = p.denote (e.denote :: s) := by induction e generalizing p s with | const => rfl | binOp => simp [Expr.compile, Expr.denote, Prog.denote, Instr.denote, List.append_assoc, *] theorem compile_correct (e : Expr) : e.compile.denote [] = some [e.denote] := by have := compile_correct' e [] [] simp [Prog.denote] at this assumption