/- Copyright (c) 2019 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura -/ import Lean.Data.LBool import Lean.Meta.InferType namespace Lean.Meta private abbrev withInstantiatedMVars (e : Expr) (k : Expr → OptionT MetaM α) : OptionT MetaM α := do let eNew ← instantiateMVars e if eNew.getAppFn.isMVar then failure else k eNew /-- Evaluate simple `Nat` expressions. Remark: this method assumes the given expression has type `Nat`. -/ partial def evalNat : Expr → OptionT MetaM Nat | Expr.lit (Literal.natVal n) _ => return n | Expr.mdata _ e _ => evalNat e | Expr.const `Nat.zero .. => return 0 | e@(Expr.app ..) => visit e | e@(Expr.mvar ..) => visit e | _ => failure where visit e := do let f := e.getAppFn match f with | Expr.mvar .. => withInstantiatedMVars e evalNat | Expr.const c _ _ => let nargs := e.getAppNumArgs if c == ``Nat.succ && nargs == 1 then let v ← evalNat (e.getArg! 0) return v+1 else if c == ``Nat.add && nargs == 2 then let v₁ ← evalNat (e.getArg! 0) let v₂ ← evalNat (e.getArg! 1) return v₁ + v₂ else if c == ``Nat.sub && nargs == 2 then let v₁ ← evalNat (e.getArg! 0) let v₂ ← evalNat (e.getArg! 1) return v₁ - v₂ else if c == ``Nat.mul && nargs == 2 then let v₁ ← evalNat (e.getArg! 0) let v₂ ← evalNat (e.getArg! 1) return v₁ * v₂ else if c == ``Add.add && nargs == 4 then let v₁ ← evalNat (e.getArg! 2) let v₂ ← evalNat (e.getArg! 3) return v₁ + v₂ else if c == ``Sub.sub && nargs == 4 then let v₁ ← evalNat (e.getArg! 2) let v₂ ← evalNat (e.getArg! 3) return v₁ - v₂ else if c == ``Mul.mul && nargs == 4 then let v₁ ← evalNat (e.getArg! 2) let v₂ ← evalNat (e.getArg! 3) return v₁ * v₂ else if c == ``HAdd.hAdd && nargs == 6 then let v₁ ← evalNat (e.getArg! 3) let v₂ ← evalNat (e.getArg! 5) return v₁ + v₂ else if c == ``HSub.hSub && nargs == 6 then let v₁ ← evalNat (e.getArg! 4) let v₂ ← evalNat (e.getArg! 5) return v₁ - v₂ else if c == ``HMul.hMul && nargs == 6 then let v₁ ← evalNat (e.getArg! 4) let v₂ ← evalNat (e.getArg! 5) return v₁ * v₂ else if c == ``OfNat.ofNat && nargs == 3 then evalNat (e.getArg! 1) else failure | _ => failure /- Quick function for converting `e` into `s + k` s.t. `e` is definitionally equal to `Nat.add s k`. -/ private partial def getOffsetAux : Expr → Bool → OptionT MetaM (Expr × Nat) | e@(Expr.app _ a _), top => do let f := e.getAppFn match f with | Expr.mvar .. => withInstantiatedMVars e (getOffsetAux · top) | Expr.const c _ _ => let nargs := e.getAppNumArgs if c == ``Nat.succ && nargs == 1 then do let (s, k) ← getOffsetAux a false pure (s, k+1) else if c == ``Nat.add && nargs == 2 then do let v ← evalNat (e.getArg! 1) let (s, k) ← getOffsetAux (e.getArg! 0) false pure (s, k+v) else if c == ``Add.add && nargs == 4 then do let v ← evalNat (e.getArg! 3) let (s, k) ← getOffsetAux (e.getArg! 2) false pure (s, k+v) else if c == ``HAdd.hAdd && nargs == 6 then do let v ← evalNat (e.getArg! 5) let (s, k) ← getOffsetAux (e.getArg! 4) false pure (s, k+v) else if top then failure else pure (e, 0) | _ => if top then failure else pure (e, 0) | e, top => if top then failure else pure (e, 0) private def getOffset (e : Expr) : OptionT MetaM (Expr × Nat) := getOffsetAux e true private partial def isOffset : Expr → OptionT MetaM (Expr × Nat) | e@(Expr.app _ a _) => let f := e.getAppFn match f with | Expr.mvar .. => withInstantiatedMVars e isOffset | Expr.const c _ _ => let nargs := e.getAppNumArgs if (c == ``Nat.succ && nargs == 1) || (c == ``Nat.add && nargs == 2) || (c == ``Add.add && nargs == 4) || (c == ``HAdd.hAdd && nargs == 6) then getOffset e else failure | _ => failure | _ => failure private def isNatZero (e : Expr) : MetaM Bool := do match (← evalNat e) with | some v => v == 0 | _ => false private def mkOffset (e : Expr) (offset : Nat) : MetaM Expr := do if offset == 0 then return e else if (← isNatZero e) then return mkNatLit offset else return mkAppB (mkConst ``Nat.add) e (mkNatLit offset) def isDefEqOffset (s t : Expr) : MetaM LBool := do let ifNatExpr (x : MetaM LBool) : MetaM LBool := do let type ← inferType s -- Remark: we use `withNewMCtxDepth` to make sure we don't assing metavariables when performing the `isDefEq` test if (← withNewMCtxDepth <| Meta.isExprDefEqAux type (mkConst ``Nat)) then x else return LBool.undef let isDefEq (s t) : MetaM LBool := ifNatExpr <| toLBoolM <| Meta.isExprDefEqAux s t match (← isOffset s) with | some (s, k₁) => match (← isOffset t) with | some (t, k₂) => -- s+k₁ =?= t+k₂ if k₁ == k₂ then isDefEq s t else if k₁ < k₂ then isDefEq s (← mkOffset t (k₂ - k₁)) else isDefEq (← mkOffset s (k₁ - k₂)) t | none => match (← evalNat t) with | some v₂ => -- s+k₁ =?= v₂ if v₂ ≥ k₁ then isDefEq s (mkNatLit $ v₂ - k₁) else ifNatExpr <| return LBool.false | none => return LBool.undef | none => match (← evalNat s) with | some v₁ => match (← isOffset t) with | some (t, k₂) => -- v₁ =?= t+k₂ if v₁ ≥ k₂ then isDefEq (mkNatLit $ v₁ - k₂) t else ifNatExpr <| return LBool.false | none => match (← evalNat t) with | some v₂ => ifNatExpr <| return (v₁ == v₂).toLBool -- v₁ =?= v₂ | none => return LBool.undef | none => return LBool.undef end Lean.Meta