{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# OPTIONS_GHC -Wall -Werror #-}
module Documentation.SBV.Examples.Transformers.SymbolicEval where
import Control.Monad.Except (Except, ExceptT, MonadError, mapExceptT, runExceptT, throwError)
import Control.Monad.Identity (Identity(runIdentity))
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Reader (MonadReader(reader), asks, ReaderT, runReaderT)
import Control.Monad.Trans (lift)
import Data.SBV.Dynamic (SVal)
import Data.SBV.Internals (SBV(SBV), unSBV)
import Data.SBV.Trans
import Data.SBV.Trans.Control
newtype Alloc a = Alloc { runAlloc :: SymbolicT (ExceptT String IO) a }
deriving (Functor, Applicative, Monad, MonadIO,
MonadError String, MonadSymbolic)
data Env = Env { envX :: SBV Integer
, envY :: SBV Integer
, result :: Maybe SVal
}
deriving (Eq, Show)
alloc :: String -> Alloc (SBV Integer)
alloc "" = throwError "tried to allocate unnamed value"
alloc nm = free nm
allocEnv :: Alloc Env
allocEnv = do
x <- alloc "x"
y <- alloc "y"
pure $ Env x y Nothing
data Term :: * -> * where
Var :: String -> Term r
Lit :: Integer -> Term Integer
Plus :: Term Integer -> Term Integer -> Term Integer
LessThan :: Term Integer -> Term Integer -> Term Bool
GreaterThan :: Term Integer -> Term Integer -> Term Bool
Equals :: Term Integer -> Term Integer -> Term Bool
Not :: Term Bool -> Term Bool
Or :: Term Bool -> Term Bool -> Term Bool
And :: Term Bool -> Term Bool -> Term Bool
Implies :: Term Bool -> Term Bool -> Term Bool
newtype Eval a = Eval { unEval :: ReaderT Env (Except String) a }
deriving (Functor, Applicative, Monad, MonadReader Env, MonadError String)
unsafeCastSBV :: SBV a -> SBV b
unsafeCastSBV = SBV . unSBV
eval :: Term r -> Eval (SBV r)
eval (Var "x") = asks $ unsafeCastSBV . envX
eval (Var "y") = asks $ unsafeCastSBV . envY
eval (Var "result") = do mRes <- reader result
case mRes of
Nothing -> throwError "unknown variable"
Just sv -> pure $ SBV sv
eval (Var _) = throwError "unknown variable"
eval (Lit i) = pure $ literal i
eval (Plus t1 t2) = (+) <$> eval t1 <*> eval t2
eval (LessThan t1 t2) = (.<) <$> eval t1 <*> eval t2
eval (GreaterThan t1 t2) = (.>) <$> eval t1 <*> eval t2
eval (Equals t1 t2) = (.==) <$> eval t1 <*> eval t2
eval (Not t) = sNot <$> eval t
eval (Or t1 t2) = (.||) <$> eval t1 <*> eval t2
eval (And t1 t2) = (.&&) <$> eval t1 <*> eval t2
eval (Implies t1 t2) = (.=>) <$> eval t1 <*> eval t2
runEval :: Env -> Term a -> Except String (SBV a)
runEval env term = runReaderT (unEval $ eval term) env
newtype Program a = Program (Term a)
newtype Result = Result SVal
mkResult :: SBV a -> Result
mkResult = Result . unSBV
runProgramEval :: Env -> Program a -> Except String Result
runProgramEval env (Program term) = mkResult <$> runEval env term
newtype Property = Property (Term Bool)
runPropertyEval :: Result -> Env -> Property -> Except String (SBV Bool)
runPropertyEval (Result res) env (Property term) =
runEval (env { result = Just res }) term
data CheckResult = Proved | Counterexample Integer Integer
deriving (Eq, Show)
generalize :: Monad m => Identity a -> m a
generalize = pure . runIdentity
newtype Q a = Q { runQ :: QueryT (ExceptT String IO) a }
deriving (Functor, Applicative, Monad, MonadIO, MonadError String, MonadQuery)
mkQuery :: Env -> Q CheckResult
mkQuery env = do
satResult <- checkSat
case satResult of
Sat -> Counterexample <$> getValue (envX env)
<*> getValue (envY env)
Unsat -> pure Proved
Unk -> throwError "unknown"
check :: Program a -> Property -> IO (Either String CheckResult)
check program prop = runExceptT $ runSMTWith z3 $ do
env <- runAlloc allocEnv
test <- lift $ mapExceptT generalize $ do
res <- runProgramEval env program
runPropertyEval res env prop
constrain $ sNot test
query $ runQ $ mkQuery env
ex1 :: IO (Either String CheckResult)
ex1 = check (Program $ Var "x" `Plus` Lit 1 `Plus` Var "y")
(Property $ Var "result" `LessThan` Lit 10)
ex2 :: IO (Either String CheckResult)
ex2 = check (Program $ Var "x" `Plus` Var "y")
(Property $ (positive (Var "x") `And` positive (Var "y"))
`Implies` (Var "result" `GreaterThan` Lit 1))
where positive t = t `GreaterThan` Lit 0
ex3 :: IO (Either String CheckResult)
ex3 = check (Program $ Var "notAValidVar")
(Property $ Var "result" `LessThan` Lit 10)