packages feed

hermit-0.4.0.0: src/HERMIT/Shell/Proof.hs

{-# LANGUAGE TypeFamilies, DeriveDataTypeable, FlexibleContexts, ScopedTypeVariables #-}

module HERMIT.Shell.Proof where

import Control.Arrow
import Control.Monad.Error
import Control.Monad.State

import Data.Dynamic
import Data.List
import Data.Maybe (isNothing)

import HERMIT.Core
import HERMIT.External
import HERMIT.GHC
import HERMIT.Kernel.Scoped
import HERMIT.Kure
import HERMIT.Utilities

import HERMIT.Dictionary.Common
import HERMIT.Dictionary.Debug
import HERMIT.Dictionary.Induction
import HERMIT.Dictionary.Reasoning
import HERMIT.Dictionary.Rules

import HERMIT.Plugin.Types
import HERMIT.PrettyPrinter.Common
import qualified HERMIT.PrettyPrinter.Clean as Clean

import HERMIT.Shell.ScriptToRewrite
import HERMIT.Shell.Types

import System.IO

--------------------------------------------------------------------------------------------------------

externals :: [External]
externals =
    [ external "script-to-proof" scriptToProof
        [ "Convert a loaded script to an equality proof, by applying the script as a LHS to RHS rewrite." ]
    , external "script-both-sides-to-proof" scriptBothSidesToProof
        [ "Convert a pair of loaded scripts to a proof, by applying the first script to the LHS and the second script to the RHS." ]
    , external "rewrite-to-proof" (rewriteToProof . extractR :: RewriteH Core -> ProofH)
        [ "Convert a rewrite to an equality proof, by applying it to the LHS." ]
    , external "rewrite-both-sides-to-proof" ((\ r1 r2 -> rewriteBothSidesToProof (extractR r1) (extractR r2)) :: RewriteH Core -> RewriteH Core -> ProofH)
        [ "Convert a pair of rewrites to a proof, by applying the first rewrite to the LHS and the second rewrite to the RHS." ]
    , external "lemma-to-proof" lemmaToProof
        [ "Convert a (proven) lemma to an equality proof." ]
    , external "inductive-proof" (inductiveProofExt :: String -> [String] -> [ScriptName] -> ProofH)
        [ "inductive-proof <id-name> [<data-con-name>] [<script-name>]"
        , "Build an equality proof by induction on the named identifier."
        , "Takes a list of proofs (in the form of scripts converting the LHS to the RHS) for each data constructor case,"
        , "in the same order as the given list of data constructor names."
        , "For example: inductive-proof 'xs [ '[] , ': ] [ \"NilCaseScript\" , \"ConsCaseScript\" ]"
        , "The induction hypotheses are available for use under the names ind-hyp-0, ind-hyp-1, etc..."
        ]
    , external "inductive-proof-both-sides" (inductiveProofBothSidesExt :: String -> [String] -> [ScriptName] -> [ScriptName] -> ProofH)
        [ "inductive-proof-both-sides <id-name> [<data-con-name>] [<script-name>] [<script-name>]"
        , "As inductive-proof, but takes scripts to apply to the RHS of each case as well as the LHS."
        ]
    , external "flip-proof" flipProof
        [ "Flip the LHS and RHS of a proof."
        , "Example usage: flip-proof (rewrite-to-proof r)"
        ]
    , external "rule-to-lemma" RuleToLemma
        [ "Create a lemma from a GHC RULE." ]
    , external "show-lemmas" ShowLemmas
        [ "List lemmas." ]
    , external "lemma" ((\s -> promoteExprBiR . lemma False s) :: CommandLineState -> LemmaName -> BiRewriteH Core)
        [ "Generate a bi-directional rewrite from a proven lemma." ]
    , external "lemma-unsafe" ((\s -> promoteExprBiR . lemma True s) :: CommandLineState -> LemmaName -> BiRewriteH Core)
        [ "Generate a bi-directional rewrite from a lemma, even if it is unproven." ]
    , external "verify-lemma" VerifyLemma
        [ "Prove a lemma." ]
    , external "lemma-lhs-intro" (lemmaLhsIntroR :: CommandLineState -> LemmaName -> RewriteH Core)
        [ "Introduce the LHS of a lemma as a non-recursive binding, in either an expression or a program."
        , "body ==> let v = lhs in body" ] .+ Introduce .+ Shallow
    , external "lemma-rhs-intro" (lemmaRhsIntroR :: CommandLineState -> LemmaName -> RewriteH Core)
        [ "Introduce the RHS of a lemma as a non-recursive binding, in either an expression or a program."
        , "body ==> let v = rhs in body" ] .+ Introduce .+ Shallow
    ]

--------------------------------------------------------------------------------------------------------

data ProofH = RewritingProof ScriptOrRewrite ScriptOrRewrite                                             -- ^ Prove by rewriting both sides to a common intermediate expression.
            | InductiveProof (Id -> Bool) [((Maybe DataCon -> Bool), ScriptOrRewrite, ScriptOrRewrite)]  -- ^ Prove by induction.  'Nothing' is the 'undefined' case.
            | UserProof (TranslateH CoreExprEquality ())                                                 -- ^ A user-defined proof technique.
            | ProofH (CoreExprEquality -> CLT IO ())                                                     -- ^ User-defined proof with full access to shell monad stack.

type ScriptOrRewrite = Either ScriptName (RewriteH CoreExpr) -- The named script should be convertible to a Rewrite.

--------------------------------------------------------------------------------------------------------

data ProofCommand
    = RuleToLemma RuleNameString
    | VerifyLemma LemmaName ProofH
    | ShowLemmas
    deriving (Typeable)

instance Extern ProofCommand where
    type Box ProofCommand = ProofCommand
    box i = i
    unbox i = i

data ProofBox = ProofBox ProofH deriving Typeable

instance Extern ProofH where
    type Box ProofH = ProofBox
    box = ProofBox
    unbox (ProofBox p) = p

--------------------------------------------------------------------------------------------------------

-- | Verify an equality by applying a user-supplied predicate.
--   If the predicate holds, HERMIT accepts the equality as proven.
userProofTechnique :: TranslateH CoreExprEquality () -> ProofH
userProofTechnique = UserProof

--------------------------------------------------------------------------------------------------------

scriptToProof :: ScriptName -> ProofH
scriptToProof s = RewritingProof (Left s) (Right idR)

scriptBothSidesToProof :: ScriptName -> ScriptName -> ProofH
scriptBothSidesToProof s1 s2 = RewritingProof (Left s1) (Left s2)

rewriteToProof :: RewriteH CoreExpr -> ProofH
rewriteToProof r = RewritingProof (Right r) (Right idR)

rewriteBothSidesToProof :: RewriteH CoreExpr -> RewriteH CoreExpr -> ProofH
rewriteBothSidesToProof r1 r2 = RewritingProof (Right r1) (Right r2)

--------------------------------------------------------------------------------------------------------

inductiveProof :: (Id -> Bool) -> [((Maybe DataCon -> Bool), ScriptName)] -> ProofH
inductiveProof p cases = InductiveProof p (map (\ (dp,s) -> (dp, Left s, Right idR)) cases)

inductiveProofBothSides :: (Id -> Bool) -> [((Maybe DataCon -> Bool), ScriptName, ScriptName)] -> ProofH
inductiveProofBothSides p cases = InductiveProof p (map (\ (dp,s1,s2) -> (dp, Left s1, Left s2)) cases)

--------------------------------------------------------------------------------------------------------

-- inductiveProofExt :: String -> [(String, ScriptName)] -> ProofH
-- inductiveProofExt idn cases = inductiveProof (cmpString2Var idn) [ ((cmpString2Name dcn . dataConName), sn) | (dcn,sn) <- cases ]

-- TODO: Upgrade the parser so that this can be a list of pairs.
inductiveProofExt :: String -> [String] -> [ScriptName] -> ProofH
inductiveProofExt idn dcns sns = inductiveProof (cmpString2Var idn) (zip (caseNamePreds dcns) sns)

-- inductiveProofBothSidesExt :: String -> [(String, ScriptName, ScriptName)] -> ProofH
-- inductiveProofBothSidesExt idn cases = inductiveProofBothSides (cmpString2Var idn) [ ((cmpString2Name dcn . dataConName), sln, srn) | (dcn,sln,srn) <- cases ]

-- TODO: Upgrade the parser so that this can be a list of triples.
inductiveProofBothSidesExt :: String -> [String] -> [ScriptName] -> [ScriptName] -> ProofH
inductiveProofBothSidesExt idn dcns s1ns s2ns = inductiveProofBothSides (cmpString2Var idn) (zip3 (caseNamePreds dcns) s1ns s2ns)

-- isNothing for the undefined case
caseNamePreds :: [String] -> [Maybe DataCon -> Bool]
caseNamePreds dcns = isNothing : [ maybe False (cmpString2Name dcn . dataConName) | dcn <- dcns ]

--------------------------------------------------------------------------------------------------------

flipProof :: ProofH -> ProofH
flipProof (RewritingProof sr1 sr2)   = RewritingProof sr2 sr1
flipProof (InductiveProof pr cases)  = InductiveProof pr [ (dp,s2,s1) | (dp,s1,s2) <- cases ]
flipProof (UserProof t)              = UserProof (arr flipCoreExprEquality >>> t)
flipProof (ProofH p)                 = ProofH (p . flipCoreExprEquality)

--------------------------------------------------------------------------------------------------------

getLemmaByName :: Monad m => CommandLineState -> LemmaName -> m Lemma
getLemmaByName st nm =
    case [ lm | lm@(n,_,_) <- cl_lemmas st, n == nm ] of
        []    -> fail ("No lemma named: " ++ nm)
        (l:_) -> return l

lemma :: Bool -> CommandLineState -> LemmaName -> BiRewriteH CoreExpr
lemma ok st nm = beforeBiR
                   (do (_,equality,proven) <- getLemmaByName st nm
                       guardMsg (proven || ok) ("Lemma " ++ nm ++ " has not been proven.")
                       return equality
                   )
                   birewrite

lemmaToProof :: CommandLineState -> LemmaName -> ProofH
lemmaToProof st nm = rewriteToProof (forwardT (lemma False st nm))

--------------------------------------------------------------------------------------------------------

lemmaNameToEqualityT :: Monad m => CommandLineState -> LemmaName -> m CoreExprEquality
lemmaNameToEqualityT st nm =
  do (_,eq,_) <- getLemmaByName st nm
     return eq

-- | @e@ ==> @let v = lhs in e@  (also works in a similar manner at Program nodes)
lemmaLhsIntroR :: CommandLineState -> LemmaName -> RewriteH Core
lemmaLhsIntroR st = lemmaNameToEqualityT st >=> eqLhsIntroR

-- | @e@ ==> @let v = rhs in e@  (also works in a similar manner at Program nodes)
lemmaRhsIntroR :: CommandLineState -> LemmaName -> RewriteH Core
lemmaRhsIntroR st = lemmaNameToEqualityT st >=> eqRhsIntroR

--------------------------------------------------------------------------------------------------------

performProofCommand :: MonadIO m => ProofCommand -> CLT m ()
performProofCommand (RuleToLemma nm) = do
    st <- gets cl_pstate
    equality <- queryS (ps_kernel st) (ruleNameToEqualityT nm :: TranslateH Core CoreExprEquality) (mkKernelEnv st) (ps_cursor st)
    modify $ \ s -> s { cl_lemmas = (nm,equality,False) : cl_lemmas s }

performProofCommand (VerifyLemma nm proof) = do
    st <- get
    (_,equality,_) <- getLemmaByName st nm
    prove equality proof -- this is like a guard
    markProven nm

performProofCommand ShowLemmas = gets cl_lemmas >>= \ ls -> forM_ (reverse ls) printLemma

printLemma :: MonadIO m => Lemma -> CLT m ()
printLemma (nm, CoreExprEquality bs lhs rhs, proven) = do
    st <- gets cl_pstate
    let k    = ps_kernel st
        env  = mkKernelEnv st
        sast = ps_cursor st
        pos  = ps_pretty_opts st
        pp   = ps_pretty st
        pr :: [Var] -> CoreExpr -> TranslateH Core DocH
        pr vs e = return e >>> withVarsInScope vs (extractT $ liftPrettyH pos pp)
    cl_putStr nm
    cl_putStrLn $ if proven then " (Proven)" else " (Not Proven)"
    unless (null bs) $ do
        forallDoc <- queryS k (return bs >>> extractT (liftPrettyH pos Clean.ppForallQuantification) :: TranslateH Core DocH) env sast -- TODO: rather than hardwiring the Clean PP here, we should store a pretty printer in the shell state, which should match the main PP, and be updated correspondingly.
        liftIO $ ps_render st stdout pos (Right forallDoc)
    lDoc <- queryS k (pr bs lhs) env sast
    rDoc <- queryS k (pr bs rhs) env sast
    liftIO $ ps_render st stdout pos (Right lDoc)
    cl_putStrLn "=="
    liftIO $ ps_render st stdout pos (Right rDoc)
    cl_putStrLn ""

--------------------------------------------------------------------------------------------------------

-- | Prove a lemma using the given proof in the current kernel context.
-- Required to fail if proof fails.
prove :: MonadIO m => CoreExprEquality -> ProofH -> CLT m ()
prove eq (RewritingProof lp rp) = do
    (lrr, rrr) <- getRewrites (lp, rp)
    st <- gets cl_pstate
    queryS (ps_kernel st) (return eq >>> verifyCoreExprEqualityT (lrr, rrr) :: TranslateH Core ()) (mkKernelEnv st) (ps_cursor st)

-- InductiveProof (Id -> Bool) [((DataCon -> Bool), ScriptOrRewrite, ScriptOrRewrite)]
-- inductionOnT :: forall c. (AddBindings c, ReadBindings c, ReadPath c Crumb, ExtendPath c Crumb, Walker c Core)
--              => (Id -> Bool)
--              -> (DataCon -> [BiRewrite c HermitM CoreExpr] -> CoreExprEqualityProof c HermitM)
--              -> Translate c HermitM CoreExprEquality ()
prove eq@(CoreExprEquality bs lhs rhs) (InductiveProof idPred caseProofs) = do
    st <- get
    let ps = cl_pstate st

    i <- setFailMsg "specified identifier is not universally quantified in this equality lemma." $ soleElement (filter idPred bs)
    cases <- queryS (ps_kernel ps) (inductionCaseSplit bs i lhs rhs :: TranslateH Core [(Maybe DataCon,[Var],CoreExpr,CoreExpr)]) (mkKernelEnv ps) (ps_cursor ps)

    forM_ cases $ \ (mdc,vs,lhsE,rhsE) -> do

        (lp,rp) <- getProofsForCase mdc caseProofs

        let vs_matching_i_type = filter (typeAlphaEq (varType i) . varType) vs
            -- Generate list of specialized induction hypotheses for the recursive cases.
            eqs = [ discardUniVars $ instantiateCoreExprEqVar i (Var i') eq
                  | i' <- vs_matching_i_type ]
            brs = map birewrite eqs
            nms = [ "ind-hyp-" ++ show n | n :: Int <- [0..] ]

        forM_ [ (nm, e, True) | (nm,e) <- zip nms eqs ] printLemma
        catchError (do put $ addToDict (zip nms brs) st
                       (l,r) <- getRewrites (lp,rp)
                       prove (CoreExprEquality (delete i bs ++ vs) lhsE rhsE) (rewriteBothSidesToProof l r) -- recursion!
                   )
                   (\ err -> put st >> throwError err)
        put st -- put original state (with original dictionary) back

prove eq (UserProof t) =
  do st <- gets cl_pstate
     queryS (ps_kernel st) (return eq >>> t :: TranslateH Core ()) (mkKernelEnv st) (ps_cursor st)

prove eq (ProofH p) = clm2clt (p eq)

getProofsForCase :: Monad m => Maybe DataCon -> [(Maybe DataCon -> Bool, ScriptOrRewrite, ScriptOrRewrite)] -> m (ScriptOrRewrite, ScriptOrRewrite)
getProofsForCase mdc cases =
  let dcnm = maybe "undefined" getOccString mdc
   in case [ (l,r) | (dcPred, l, r) <- cases, dcPred mdc ] of
        []  -> fail $ "no case for " ++ dcnm
        [p] -> return p
        _   -> fail $ "more than one case for " ++ dcnm

addToDict :: [(String, BiRewriteH CoreExpr)] -> CommandLineState -> CommandLineState
addToDict rs st = st { cl_dict = foldr (\ (nm,r) -> addToDictionary (external nm (promoteExprBiR (beforeBiR (observeR ("Applying " ++ nm ++ " to: ")) (const r)) :: BiRewriteH Core) [])) (cl_dict st) rs }

{-
       let verifyInductiveCaseT :: (DataCon,[Var],CoreExpr,CoreExpr) -> Translate c HermitM x ()
           verifyInductiveCaseT (con,vs,lhsE,rhsE) =
                let vs_matching_i_type = filter (typeAlphaEq (varType i) . varType) vs
                    eqs = [ discardUniVars (instantiateCoreExprEq [(i,Var i')] eq) | i' <- vs_matching_i_type ]
                    brs = map birewrite eqs -- These eqs now have no universally quantified variables.
                                            -- Thus they can only be used on variables in the induction hypothesis.
                                            -- TODO: consider whether this is unneccassarily restrictive
                    caseEq = CoreExprEquality (delete i bs ++ vs) lhsE rhsE
                in return caseEq >>> verifyCoreExprEqualityT (genCaseAltProofs con brs)

       mapM_ verifyInductiveCaseT cases
-}

getRewrites :: MonadState CommandLineState m => (ScriptOrRewrite, ScriptOrRewrite) -> m (RewriteH CoreExpr, RewriteH CoreExpr)
getRewrites (l,r) = liftM2 (,) (getRewrite l) (getRewrite r)

getRewrite :: MonadState CommandLineState m => ScriptOrRewrite -> m (RewriteH CoreExpr)
getRewrite = either (lookupScript >=> liftM extractR . scriptToRewrite) return

markProven :: MonadState CommandLineState m => LemmaName -> m ()
markProven nm = modify $ \ st -> st { cl_lemmas = [ (n,e, if n == nm then True else p) | (n,e,p) <- cl_lemmas st ] }