{-# LANGUAGE Safe #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE PatternGuards #-}
module SimpleSMT
(
Solver(..)
, newSolver
, ackCommand
, simpleCommand
, simpleCommandMaybe
, loadFile
, SExpr(..)
, showsSExpr, readSExpr
, Logger(..)
, newLogger
, withLogLevel
, logMessageAt
, logIndented
, setLogic, setLogicMaybe
, setOption, setOptionMaybe
, push, pushMany
, pop, popMany
, inNewScope
, declare
, declareFun
, declareDatatype
, define
, defineFun
, assert
, check
, Result(..)
, getExprs, getExpr
, getConsts, getConst
, Value(..)
, fam
, fun
, const
, tInt
, tBool
, tReal
, tArray
, tBits
, int
, real
, bool
, bvBin
, bvHex
, value
, not
, and
, andMany
, or
, orMany
, xor
, implies
, ite
, eq
, distinct
, gt
, lt
, geq
, leq
, bvULt
, bvULeq
, bvSLt
, bvSLeq
, add
, addMany
, sub
, neg
, mul
, abs
, div
, mod
, divisible
, realDiv
, concat
, extract
, bvNot
, bvNeg
, bvAnd
, bvXOr
, bvOr
, bvAdd
, bvSub
, bvMul
, bvUDiv
, bvURem
, bvSDiv
, bvSRem
, bvShl
, bvLShr
, bvAShr
, signExtend
, zeroExtend
, select
, store
) where
import Prelude hiding (not, and, or, abs, div, mod, concat, const)
import qualified Prelude as P
import Data.Char(isSpace)
import Data.List(unfoldr)
import Data.Bits(testBit)
import Data.IORef(newIORef, atomicModifyIORef, modifyIORef', readIORef,
writeIORef)
import System.Process(runInteractiveProcess, waitForProcess)
import System.IO (hFlush, hGetLine, hGetContents, hPutStrLn, stdout, hClose)
import System.Exit(ExitCode)
import qualified Control.Exception as X
import Control.Concurrent(forkIO)
import Control.Monad(forever,when)
import Text.Read(readMaybe)
import Data.Ratio((%), numerator, denominator)
import Numeric(showHex, readHex)
data Result = Sat
| Unsat
| Unknown
deriving (Eq,Show)
data Value = Bool !Bool
| Int !Integer
| Real !Rational
| Bits !Int !Integer
| Other !SExpr
deriving (Eq,Show)
data SExpr = Atom String
| List [SExpr]
deriving (Eq, Ord, Show)
showsSExpr :: SExpr -> ShowS
showsSExpr ex =
case ex of
Atom x -> showString x
List es -> showChar '(' .
foldr (\e m -> showsSExpr e . showChar ' ' . m)
(showChar ')') es
readSExpr :: String -> Maybe (SExpr, String)
readSExpr (c : more) | isSpace c = readSExpr more
readSExpr (';' : more) = readSExpr $ drop 1 $ dropWhile (/= '\n') more
readSExpr ('(' : more) = do (xs,more1) <- list more
return (List xs, more1)
where
list (c : txt) | isSpace c = list txt
list (')' : txt) = return ([], txt)
list txt = do (v,txt1) <- readSExpr txt
(vs,txt2) <- list txt1
return (v:vs, txt2)
readSExpr txt = case break end txt of
(as,bs) | P.not (null as) -> Just (Atom as, bs)
_ -> Nothing
where end x = x == ')' || isSpace x
data Solver = Solver
{ command :: SExpr -> IO SExpr
, stop :: IO ExitCode
}
newSolver :: String ->
[String] ->
Maybe Logger ->
IO Solver
newSolver exe opts mbLog =
do (hIn, hOut, hErr, h) <- runInteractiveProcess exe opts Nothing Nothing
let info a = case mbLog of
Nothing -> return ()
Just l -> logMessage l a
_ <- forkIO $ forever (do errs <- hGetLine hErr
info ("[stderr] " ++ errs))
`X.catch` \X.SomeException {} -> return ()
getResponse <-
do txt <- hGetContents hOut
ref <- newIORef (unfoldr readSExpr txt)
return $ atomicModifyIORef ref $ \xs ->
case xs of
[] -> (xs, Nothing)
y : ys -> (ys, Just y)
let cmd c = do let txt = showsSExpr c ""
info ("[send->] " ++ txt)
hPutStrLn hIn txt
hFlush hIn
command c =
do cmd c
mb <- getResponse
case mb of
Just res -> do info ("[<-recv] " ++ showsSExpr res "")
return res
Nothing -> fail "Missing response from solver"
stop =
do cmd (List [Atom "exit"])
ec <- waitForProcess h
X.catch (do hClose hIn
hClose hOut
hClose hErr)
(\ex -> info (show (ex::X.IOException)))
return ec
solver = Solver { .. }
setOption solver ":print-success" "true"
setOption solver ":produce-models" "true"
return solver
loadFile :: Solver -> FilePath -> IO ()
loadFile s file = loadString s =<< readFile file
loadString :: Solver -> String -> IO ()
loadString s str = go (dropComments str)
where
go txt
| all isSpace txt = return ()
| otherwise =
case readSExpr txt of
Just (e,rest) -> command s e >> go rest
Nothing -> fail $ unlines [ "Failed to parse SMT file."
, txt
]
dropComments = unlines . map dropComment . lines
dropComment xs = case break (== ';') xs of
(as,_:_) -> as
_ -> xs
ackCommand :: Solver -> SExpr -> IO ()
ackCommand proc c =
do res <- command proc c
case res of
Atom "success" -> return ()
_ -> fail $ unlines
[ "Unexpected result from the SMT solver:"
, " Expected: success"
, " Result: " ++ showsSExpr res ""
]
simpleCommand :: Solver -> [String] -> IO ()
simpleCommand proc = ackCommand proc . List . map Atom
simpleCommandMaybe :: Solver -> [String] -> IO Bool
simpleCommandMaybe proc c =
do res <- command proc (List (map Atom c))
case res of
Atom "success" -> return True
Atom "unsupported" -> return False
_ -> fail $ unlines
[ "Unexpected result from the SMT solver:"
, " Expected: success or unsupported"
, " Result: " ++ showsSExpr res ""
]
setOption :: Solver -> String -> String -> IO ()
setOption s x y = simpleCommand s [ "set-option", x, y ]
setOptionMaybe :: Solver -> String -> String -> IO Bool
setOptionMaybe s x y = simpleCommandMaybe s [ "set-option", x, y ]
setLogic :: Solver -> String -> IO ()
setLogic s x = simpleCommand s [ "set-logic", x ]
setLogicMaybe :: Solver -> String -> IO Bool
setLogicMaybe s x = simpleCommandMaybe s [ "set-logic", x ]
push :: Solver -> IO ()
push proc = pushMany proc 1
pop :: Solver -> IO ()
pop proc = popMany proc 1
pushMany :: Solver -> Integer -> IO ()
pushMany proc n = simpleCommand proc [ "push", show n ]
popMany :: Solver -> Integer -> IO ()
popMany proc n = simpleCommand proc [ "pop", show n ]
inNewScope :: Solver -> IO a -> IO a
inNewScope s m =
do push s
m `X.finally` pop s
declare :: Solver -> String -> SExpr -> IO SExpr
declare proc f t = declareFun proc f [] t
declareFun :: Solver -> String -> [SExpr] -> SExpr -> IO SExpr
declareFun proc f as r =
do ackCommand proc $ fun "declare-fun" [ Atom f, List as, r ]
return (const f)
declareDatatype ::
Solver ->
String ->
[String] ->
[(String, [(String, SExpr)])] ->
IO ()
declareDatatype proc t [] cs =
ackCommand proc $
fun "declare-datatype" $
[ Atom t
, List [ List (Atom c : [ List [Atom s, argTy] | (s, argTy) <- args]) | (c, args) <- cs ]
]
declareDatatype proc t ps cs =
ackCommand proc $
fun "declare-datatype" $
[ Atom t
, fun "par" $
[ List (map Atom ps)
, List [ List (Atom c : [ List [Atom s, argTy] | (s, argTy) <- args]) | (c, args) <- cs ]
]
]
define :: Solver ->
String ->
SExpr ->
SExpr ->
IO SExpr
define proc f t e = defineFun proc f [] t e
defineFun :: Solver ->
String ->
[(String,SExpr)] ->
SExpr ->
SExpr ->
IO SExpr
defineFun proc f as t e =
do ackCommand proc $ fun "define-fun"
$ [ Atom f, List [ List [const x,a] | (x,a) <- as ], t, e]
return (const f)
assert :: Solver -> SExpr -> IO ()
assert proc e = ackCommand proc $ fun "assert" [e]
check :: Solver -> IO Result
check proc =
do res <- command proc (List [ Atom "check-sat" ])
case res of
Atom "unsat" -> return Unsat
Atom "unknown" -> return Unknown
Atom "sat" -> return Sat
_ -> fail $ unlines
[ "Unexpected result from the SMT solver:"
, " Expected: unsat, unknown, or sat"
, " Result: " ++ showsSExpr res ""
]
sexprToVal :: SExpr -> Value
sexprToVal expr =
case expr of
Atom "true" -> Bool True
Atom "false" -> Bool False
Atom ('#' : 'b' : ds)
| Just n <- binLit ds -> Bits (length ds) n
Atom ('#' : 'x' : ds)
| [(n,[])] <- readHex ds -> Bits (4 * length ds) n
Atom txt
| Just n <- readMaybe txt -> Int n
List [ Atom "-", x ]
| Int a <- sexprToVal x -> Int (negate a)
List [ Atom "/", x, y ]
| Int a <- sexprToVal x
, Int b <- sexprToVal y -> Real (a % b)
_ -> Other expr
where
binLit cs = do ds <- mapM binDigit cs
return $ sum $ zipWith (*) (reverse ds) powers2
powers2 = 1 : map (2 *) powers2
binDigit '0' = Just 0
binDigit '1' = Just 1
binDigit _ = Nothing
getExprs :: Solver -> [SExpr] -> IO [(SExpr, Value)]
getExprs proc vals =
do res <- command proc $ List [ Atom "get-value", List vals ]
case res of
List xs -> mapM getAns xs
_ -> fail $ unlines
[ "Unexpected response from the SMT solver:"
, " Exptected: a list"
, " Result: " ++ showsSExpr res ""
]
where
getAns expr =
case expr of
List [ e, v ] -> return (e, sexprToVal v)
_ -> fail $ unlines
[ "Unexpected response from the SMT solver:"
, " Expected: (expr val)"
, " Result: " ++ showsSExpr expr ""
]
getConsts :: Solver -> [String] -> IO [(String, Value)]
getConsts proc xs =
do ans <- getExprs proc (map Atom xs)
return [ (x,e) | (Atom x, e) <- ans ]
getExpr :: Solver -> SExpr -> IO Value
getExpr proc x =
do [ (_,v) ] <- getExprs proc [x]
return v
getConst :: Solver -> String -> IO Value
getConst proc x = getExpr proc (Atom x)
fam :: String -> [Integer] -> SExpr
fam f is = List (Atom "_" : Atom f : map (Atom . show) is)
fun :: String -> [SExpr] -> SExpr
fun f [] = Atom f
fun f as = List (Atom f : as)
const :: String -> SExpr
const f = fun f []
tInt :: SExpr
tInt = const "Int"
tBool :: SExpr
tBool = const "Bool"
tReal :: SExpr
tReal = const "Real"
tArray :: SExpr ->
SExpr ->
SExpr
tArray x y = fun "Array" [x,y]
tBits :: Integer ->
SExpr
tBits w = fam "BitVec" [w]
bool :: Bool -> SExpr
bool b = const (if b then "true" else "false")
int :: Integer -> SExpr
int x | x < 0 = neg (int (negate x))
| otherwise = Atom (show x)
real :: Rational -> SExpr
real x = realDiv (int (denominator x)) (int (numerator x))
bvBin :: Int -> Integer -> SExpr
bvBin w v = const ("#b" ++ bits)
where
bits = reverse [ if testBit v n then '1' else '0' | n <- [ 0 .. w - 1 ] ]
bvHex :: Int -> Integer -> SExpr
bvHex w v
| v >= 0 = const ("#x" ++ padding ++ hex)
| otherwise = bvHex w (2^w + v)
where
hex = showHex v ""
padding = replicate (P.div (w + 3) 4 - length hex) '0'
value :: Value -> SExpr
value val =
case val of
Bool b -> bool b
Int n -> int n
Real r -> real r
Bits w v | P.mod w 4 == 0 -> bvHex w v
| otherwise -> bvBin w v
Other o -> o
not :: SExpr -> SExpr
not p = fun "not" [p]
and :: SExpr -> SExpr -> SExpr
and p q = fun "and" [p,q]
andMany :: [SExpr] -> SExpr
andMany xs = if null xs then bool True else fun "and" xs
or :: SExpr -> SExpr -> SExpr
or p q = fun "or" [p,q]
orMany :: [SExpr] -> SExpr
orMany xs = if null xs then bool False else fun "or" xs
xor :: SExpr -> SExpr -> SExpr
xor p q = fun "xor" [p,q]
implies :: SExpr -> SExpr -> SExpr
implies p q = fun "=>" [p,q]
ite :: SExpr -> SExpr -> SExpr -> SExpr
ite x y z = fun "ite" [x,y,z]
eq :: SExpr -> SExpr -> SExpr
eq x y = fun "=" [x,y]
distinct :: [SExpr] -> SExpr
distinct xs = if null xs then bool True else fun "distinct" xs
gt :: SExpr -> SExpr -> SExpr
gt x y = fun ">" [x,y]
lt :: SExpr -> SExpr -> SExpr
lt x y = fun "<" [x,y]
geq :: SExpr -> SExpr -> SExpr
geq x y = fun ">=" [x,y]
leq :: SExpr -> SExpr -> SExpr
leq x y = fun "<=" [x,y]
bvULt :: SExpr -> SExpr -> SExpr
bvULt x y = fun "bvult" [x,y]
bvULeq :: SExpr -> SExpr -> SExpr
bvULeq x y = fun "bvule" [x,y]
bvSLt :: SExpr -> SExpr -> SExpr
bvSLt x y = fun "bvslt" [x,y]
bvSLeq :: SExpr -> SExpr -> SExpr
bvSLeq x y = fun "bvsle" [x,y]
add :: SExpr -> SExpr -> SExpr
add x y = fun "+" [x,y]
addMany :: [SExpr] -> SExpr
addMany xs = if null xs then int 0 else fun "+" xs
sub :: SExpr -> SExpr -> SExpr
sub x y = fun "-" [x,y]
neg :: SExpr -> SExpr
neg x = fun "-" [x]
mul :: SExpr -> SExpr -> SExpr
mul x y = fun "*" [x,y]
abs :: SExpr -> SExpr
abs x = fun "abs" [x]
div :: SExpr -> SExpr -> SExpr
div x y = fun "div" [x,y]
mod :: SExpr -> SExpr -> SExpr
mod x y = fun "mod" [x,y]
divisible :: SExpr -> Integer -> SExpr
divisible x n = List [ fam "divisible" [n], x ]
realDiv :: SExpr -> SExpr -> SExpr
realDiv x y = fun "/" [x,y]
concat :: SExpr -> SExpr -> SExpr
concat x y = fun "concat" [x,y]
signExtend :: Integer -> SExpr -> SExpr
signExtend i x = List [ fam "sign_extend" [i], x ]
zeroExtend :: Integer -> SExpr -> SExpr
zeroExtend i x = List [ fam "zero_extend" [i], x ]
extract :: SExpr -> Integer -> Integer -> SExpr
extract x y z = List [ fam "extract" [y,z], x ]
bvNot :: SExpr -> SExpr
bvNot x = fun "bvnot" [x]
bvAnd :: SExpr -> SExpr -> SExpr
bvAnd x y = fun "bvand" [x,y]
bvOr :: SExpr -> SExpr -> SExpr
bvOr x y = fun "bvor" [x,y]
bvXOr :: SExpr -> SExpr -> SExpr
bvXOr x y = fun "bvxor" [x,y]
bvNeg :: SExpr -> SExpr
bvNeg x = fun "bvneg" [x]
bvAdd :: SExpr -> SExpr -> SExpr
bvAdd x y = fun "bvadd" [x,y]
bvSub :: SExpr -> SExpr -> SExpr
bvSub x y = fun "bvsub" [x,y]
bvMul :: SExpr -> SExpr -> SExpr
bvMul x y = fun "bvmul" [x,y]
bvUDiv :: SExpr -> SExpr -> SExpr
bvUDiv x y = fun "bvudiv" [x,y]
bvURem :: SExpr -> SExpr -> SExpr
bvURem x y = fun "bvurem" [x,y]
bvSDiv :: SExpr -> SExpr -> SExpr
bvSDiv x y = fun "bvsdiv" [x,y]
bvSRem :: SExpr -> SExpr -> SExpr
bvSRem x y = fun "bvsrem" [x,y]
bvShl :: SExpr -> SExpr -> SExpr
bvShl x y = fun "bvshl" [x,y]
bvLShr :: SExpr -> SExpr -> SExpr
bvLShr x y = fun "bvlshr" [x,y]
bvAShr :: SExpr -> SExpr -> SExpr
bvAShr x y = fun "bvashr" [x,y]
select :: SExpr -> SExpr -> SExpr
select x y = fun "select" [x,y]
store :: SExpr ->
SExpr ->
SExpr ->
SExpr
store x y z = fun "store" [x,y,z]
data Logger = Logger
{ logMessage :: String -> IO ()
, logLevel :: IO Int
, logSetLevel:: Int -> IO ()
, logTab :: IO ()
, logUntab :: IO ()
}
withLogLevel :: Logger -> Int -> IO a -> IO a
withLogLevel Logger { .. } l m =
do l0 <- logLevel
X.bracket_ (logSetLevel l) (logSetLevel l0) m
logIndented :: Logger -> IO a -> IO a
logIndented Logger { .. } = X.bracket_ logTab logUntab
logMessageAt :: Logger -> Int -> String -> IO ()
logMessageAt logger l msg = withLogLevel logger l (logMessage logger msg)
newLogger :: Int -> IO Logger
newLogger l =
do tab <- newIORef 0
lev <- newIORef 0
let logLevel = readIORef lev
logSetLevel = writeIORef lev
shouldLog m =
do cl <- logLevel
when (cl >= l) m
logMessage x = shouldLog $
do let ls = lines x
t <- readIORef tab
putStr $ unlines [ replicate t ' ' ++ l | l <- ls ]
hFlush stdout
logTab = shouldLog (modifyIORef' tab (+ 2))
logUntab = shouldLog (modifyIORef' tab (subtract 2))
return Logger { .. }