liquid-fixpoint-0.5.0.0: src/Language/Fixpoint/Smt/Interface.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UndecidableInstances #-}
-- | This module contains an SMTLIB2 interface for
-- 1. checking the validity, and,
-- 2. computing satisfying assignments
-- for formulas.
-- By implementing a binary interface over the SMTLIB2 format defined at
-- http://www.smt-lib.org/
-- http://www.grammatech.com/resource/smt/SMTLIBTutorial.pdf
module Language.Fixpoint.Smt.Interface (
-- * Commands
Command (..)
-- * Responses
, Response (..)
-- * Typeclass for SMTLIB2 conversion
, SMTLIB2 (..)
-- * Creating and killing SMTLIB2 Process
, Context (..)
, makeContext
, makeContextNoLog
, makeContextWithSEnv
, cleanupContext
-- * Execute Queries
, command
, smtWrite
-- * Query API
, smtDecl
, smtAssert
, smtCheckUnsat
, smtCheckSat
, smtBracket
, smtDistinct
-- * Theory Symbols
-- , theorySymbols
-- smt_set_funs
-- * Check Validity
, checkValid, checkValidWithContext
, checkValids
, makeZ3Context
) where
import Language.Fixpoint.Types.Config (SMTSolver (..))
import Language.Fixpoint.Misc (errorstar)
import Language.Fixpoint.Types.Errors
import Language.Fixpoint.Utils.Files
import Language.Fixpoint.Types
import Language.Fixpoint.Smt.Types
import Language.Fixpoint.Smt.Theories (preamble)
import Language.Fixpoint.Smt.Serialize (initSMTEnv)
import Control.Applicative ((<|>))
import Control.Monad
import Data.Char
import Data.Monoid
import qualified Data.Text as T
import Data.Text.Format hiding (format)
import qualified Data.Text.IO as TIO
-- import qualified Data.Text.Lazy as LT
-- import qualified Data.Text.Lazy.IO as LTIO
import System.Directory
import System.Console.CmdArgs.Verbosity
import System.Exit hiding (die)
import System.FilePath
import System.IO (IOMode (..), hClose, hFlush, openFile)
import System.Process
import qualified Data.Attoparsec.Text as A
import Text.PrettyPrint.HughesPJ (text)
{-
runFile f
= readFile f >>= runString
runString str
= runCommands $ rr str
runCommands cmds
= do me <- makeContext Z3
mapM_ (T.putStrLn . smt2) cmds
zs <- mapM (command me) cmds
return zs
-}
-- TODO take makeContext's Bool from caller instead of always using False?
makeZ3Context :: FilePath -> [(Symbol, Sort)] -> IO Context
makeZ3Context f xts
= do me <- makeContextWithSEnv False Z3 f $ fromListSEnv xts
smtDecls me (toListSEnv initSMTEnv)
smtDecls me xts
return me
checkValidWithContext :: Context -> [(Symbol, Sort)] -> Expr -> Expr -> IO Bool
checkValidWithContext me xts p q
= smtBracket me $ do smtDecls me xts
smtAssert me $ pAnd [p, PNot q]
smtCheckUnsat me
-- | type ClosedPred E = {v:Pred | subset (vars v) (keys E) }
-- checkValid :: e:Env -> ClosedPred e -> ClosedPred e -> IO Bool
checkValid :: Bool -> FilePath -> [(Symbol, Sort)] -> Expr -> Expr -> IO Bool
checkValid u f xts p q
= do me <- makeContext u Z3 f
smtDecls me xts
smtAssert me $ pAnd [p, PNot q]
smtCheckUnsat me
-- | If you already HAVE a context, where all the variables have declared types
-- (e.g. if you want to make MANY repeated Queries)
-- checkValid :: e:Env -> [ClosedPred e] -> IO [Bool]
checkValids :: Bool -> FilePath -> [(Symbol, Sort)] -> [Expr] -> IO [Bool]
checkValids u f xts ps
= do me <- makeContext u Z3 f
smtDecls me xts
forM ps $ \p ->
smtBracket me $
smtAssert me (PNot p) >> smtCheckUnsat me
-- debugFile :: FilePath
-- debugFile = "DEBUG.smt2"
--------------------------------------------------------------------------
-- | SMT IO --------------------------------------------------------------
--------------------------------------------------------------------------
--------------------------------------------------------------------------
command :: Context -> Command -> IO Response
--------------------------------------------------------------------------
command me !cmd = {-# SCC "command" #-} say cmd >> hear cmd
where
say = smtWrite me . runSmt2 (smtenv me)
hear CheckSat = smtRead me
hear (GetValue _) = smtRead me
hear _ = return Ok
smtWrite :: Context -> T.Text -> IO ()
smtWrite me !s = smtWriteRaw me s
smtRead :: Context -> IO Response
smtRead me = {-# SCC "smtRead" #-}
do ln <- smtReadRaw me
res <- A.parseWith (smtReadRaw me) responseP ln
case A.eitherResult res of
Left e -> errorstar $ "SMTREAD:" ++ e
Right r -> do
maybe (return ()) (\h -> hPutStrLnNow h $ format "; SMT Says: {}" (Only $ show r)) (cLog me)
-- when (verbose me) $ TIO.putStrLn $ format "SMT Says: {}" (Only $ show r)
return r
responseP = {-# SCC "responseP" #-} A.char '(' *> sexpP
<|> A.string "sat" *> return Sat
<|> A.string "unsat" *> return Unsat
<|> A.string "unknown" *> return Unknown
sexpP = {-# SCC "sexpP" #-} A.string "error" *> (Error <$> errorP)
<|> Values <$> valuesP
errorP = A.skipSpace *> A.char '"' *> A.takeWhile1 (/='"') <* A.string "\")"
valuesP = A.many1' pairP <* A.char ')'
pairP = {-# SCC "pairP" #-}
do A.skipSpace
A.char '('
!x <- symbolP
A.skipSpace
!v <- valueP
A.char ')'
return (x,v)
symbolP = {-# SCC "symbolP" #-} symbol <$> A.takeWhile1 (not . isSpace)
valueP = {-# SCC "valueP" #-} negativeP
<|> A.takeWhile1 (\c -> not (c == ')' || isSpace c))
negativeP
= do v <- A.char '(' *> A.takeWhile1 (/=')') <* A.char ')'
return $ "(" <> v <> ")"
-- {- pairs :: {v:[a] | (len v) mod 2 = 0} -> [(a,a)] -}
-- pairs :: [a] -> [(a,a)]
-- pairs !xs = case L.splitAt 2 xs of
-- ([],_) -> []
-- ([x,y], zs) -> (x,y) : pairs zs
smtWriteRaw :: Context -> T.Text -> IO ()
smtWriteRaw me !s = {-# SCC "smtWriteRaw" #-} do
hPutStrLnNow (cOut me) s
-- whenLoud $ TIO.appendFile debugFile (s <> "\n")
maybe (return ()) (`hPutStrLnNow` s) (cLog me)
smtReadRaw :: Context -> IO Raw
smtReadRaw me = {-# SCC "smtReadRaw" #-} TIO.hGetLine (cIn me)
hPutStrLnNow h !s = TIO.hPutStrLn h s >> hFlush h
--------------------------------------------------------------------------
-- | SMT Context ---------------------------------------------------------
--------------------------------------------------------------------------
--------------------------------------------------------------------------
makeContext :: Bool -> SMTSolver -> FilePath -> IO Context
--------------------------------------------------------------------------
makeContext u s f
= do me <- makeProcess s
pre <- smtPreamble u s me
createDirectoryIfMissing True $ takeDirectory smtFile
hLog <- openFile smtFile WriteMode
let me' = me { cLog = Just hLog }
mapM_ (smtWrite me') pre
return me'
where
smtFile = extFileName Smt2 f
makeContextWithSEnv :: Bool -> SMTSolver -> FilePath -> SMTEnv -> IO Context
makeContextWithSEnv u s f env
= (\cxt -> cxt {smtenv = env}) <$> makeContext u s f
makeContextNoLog :: Bool -> SMTSolver -> IO Context
makeContextNoLog u s
= do me <- makeProcess s
pre <- smtPreamble u s me
mapM_ (smtWrite me) pre
return me
makeProcess :: SMTSolver -> IO Context
makeProcess s
= do (hOut, hIn, _ ,pid) <- runInteractiveCommand $ smtCmd s
loud <- isLoud
return Ctx { pId = pid
, cIn = hIn
, cOut = hOut
, cLog = Nothing
, verbose = loud
, smtenv = initSMTEnv
}
--------------------------------------------------------------------------
cleanupContext :: Context -> IO ExitCode
--------------------------------------------------------------------------
cleanupContext (Ctx {..})
= do hClose cIn
hClose cOut
maybe (return ()) hClose cLog
waitForProcess pId
{- "z3 -smt2 -in" -}
{- "z3 -smtc SOFT_TIMEOUT=1000 -in" -}
{- "z3 -smtc -in MBQI=false" -}
smtCmd Z3 = "z3 -smt2 -in"
smtCmd Mathsat = "mathsat -input=smt2"
smtCmd Cvc4 = "cvc4 --incremental -L smtlib2"
-- DON'T REMOVE THIS! z3 changed the names of options between 4.3.1 and 4.3.2...
smtPreamble u Z3 me
= do smtWrite me "(get-info :version)"
v:_ <- T.words . (!!1) . T.splitOn "\"" <$> smtReadRaw me
if T.splitOn "." v `versionGreater` ["4", "3", "2"]
then return $ z3_432_options ++ preamble u Z3
else return $ z3_options ++ preamble u Z3
smtPreamble u s _
= return $ preamble u s
versionGreater (x:xs) (y:ys)
| x > y = True
| x == y = versionGreater xs ys
| x < y = False
versionGreater _ [] = True
versionGreater [] _ = False
versionGreater _ _ = errorstar "Interface.versionGreater called with bad arguments"
-----------------------------------------------------------------------------
-- | SMT Commands -----------------------------------------------------------
-----------------------------------------------------------------------------
smtPush, smtPop :: Context -> IO ()
smtPush me = interact' me Push
smtPop me = interact' me Pop
smtDecls :: Context -> [(Symbol, Sort)] -> IO ()
smtDecls me xts = forM_ xts (\(x,t) -> smtDecl me x t)
smtDecl :: Context -> Symbol -> Sort -> IO ()
smtDecl me x t = interact' me (Declare x ins out)
where
(ins, out) = deconSort t
deconSort :: Sort -> ([Sort], Sort)
deconSort t = case functionSort t of
Just (_, ins, out) -> (ins, out)
Nothing -> ([] , t )
smtCheckSat :: Context -> Expr -> IO Bool
smtCheckSat me p
-- hack now this is used only for checking gradual condition.
= smtAssert me p >> (ans <$> command me CheckSat)
where
ans Sat = True
ans _ = False
smtAssert :: Context -> Expr -> IO ()
smtAssert me p = interact' me (Assert Nothing p)
smtDistinct :: Context -> [Expr] -> IO ()
smtDistinct me az = interact' me (Distinct az)
smtCheckUnsat :: Context -> IO Bool
smtCheckUnsat me = respSat <$> command me CheckSat
smtBracket :: Context -> IO a -> IO a
smtBracket me a = do smtPush me
r <- a
smtPop me
return r
respSat Unsat = True
respSat Sat = False
respSat Unknown = False
respSat r = die $ err dummySpan $ text ("crash: SMTLIB2 respSat = " ++ show r)
interact' me cmd = void $ command me cmd
-- DON'T REMOVE THIS! z3 changed the names of options between 4.3.1 and 4.3.2...
z3_432_options
= [ "(set-option :auto-config false)"
, "(set-option :model true)"
, "(set-option :model.partial false)"
, "(set-option :smt.mbqi false)" ]
z3_options
= [ "(set-option :auto-config false)"
, "(set-option :model true)"
, "(set-option :model-partial false)"
, "(set-option :mbqi false)" ]