packages feed

yices-easy-0.1: Yices/Easy/Run.hs

{-# LANGUAGE
    PatternGuards
  , Rank2Types #-}

-- | Running the Yices SMT solver.

module Yices.Easy.Run
  ( -- * Solving
    solve
    -- * Debugging
  , dump
  ) where

import Yices.Easy.Types

import Data.Maybe
import Data.Ratio
import Control.Applicative
import Control.Monad
import Control.Exception
import Foreign
import Foreign.C

import qualified Data.Map              as M
import qualified Foreign.Marshal.Utils as MU

import qualified Bindings.Yices.Internal as Y

type PExpr  = Ptr Y.YExpr
type PType  = Ptr Y.YType
type PCtx   = Ptr Y.YContext
type PVar   = Ptr Y.YVarDecl
type PModel = Ptr Y.YModel

type Env = M.Map Ident PVar

fI :: (Integral a, Num b) => a -> b
fI = fromIntegral

buildExpr :: Env -> PCtx -> Expr -> IO PExpr
buildExpr env ctx = go where
  lift1 x     f = go x >>= f
  lift2 x y   f = join $ liftM2 f (go x) (go y)
  lift3 x y z f = join $ liftM3 f (go x) (go y) (go z)
  liftN xs f = do
    ys <- mapM go xs
    withArrayLen ys $ \n a -> f a (fI n)

  go (LitBool b) = (if b then Y.c_mk_true else Y.c_mk_false) ctx
  go (LitNum (FromInt    i)) = Y.c_mk_num ctx $ fI i
  go (LitNum (FromString s)) = withCString s $ Y.c_mk_num_from_string ctx

  go (Var i)
    | Just d <- M.lookup i env = Y.c_mk_var_from_decl ctx d
    | otherwise                = error ("undefined variable: " ++ i)

  go (Apply   f xs ) = lift1 f $ \fp -> liftN xs $ Y.c_mk_app ctx fp
  go (Arith   o es ) = liftN es $ getArith o ctx
  go (Logic   o es ) = liftN es $ getLogic o ctx
  go (Compare o x y) = lift2 x y $ getCompare o ctx
  go (Not       x  ) = lift1 x $ Y.c_mk_not ctx

  go (IfThenElse x y z) = lift3 x y z $ Y.c_mk_ite ctx

  go (LitBitvec (FromULong n v)) = Y.c_mk_bv_constant ctx (fI n) v
  go (LitBitvec (FromBits bs)) = withArrayLen (map (fI.fromEnum) bs) $
    \n xs -> Y.c_mk_bv_constant_from_array ctx (fI n) xs

  go (BitArith     o x y) = lift2 x y $ getBArith     o  ctx
  go (BitLogic     o x y) = lift2 x y $ getBLogic     o  ctx
  go (BitCompare s o x y) = lift2 x y $ getBCompare s o  ctx
  go (BitMinus       x  ) = lift1 x   $ Y.c_mk_bv_minus  ctx
  go (BitNot         x  ) = lift1 x   $ Y.c_mk_bv_not    ctx
  go (BitConcat      x y) = lift2 x y $ Y.c_mk_bv_concat ctx

  go (BitShift d f  n  x) = lift1 x $ \p -> getBShift d f ctx p (fI n)
  go (BitExtract n0 n1 x) = lift1 x $ Y.c_mk_bv_extract ctx (fI n1) (fI n0)
  go (BitSignEx     n  x) = lift1 x $ \p -> Y.c_mk_bv_sign_extend ctx p (fI n)

  getArith Add = Y.c_mk_sum
  getArith Sub = Y.c_mk_sub
  getArith Mul = Y.c_mk_mul

  getLogic And = Y.c_mk_and
  getLogic Or  = Y.c_mk_or
  getLogic Xor = error "Yices.Easy.Run: no xor for booleans"

  getCompare Eq = Y.c_mk_eq
  getCompare Ne = Y.c_mk_diseq
  getCompare Gt = Y.c_mk_gt
  getCompare Ge = Y.c_mk_ge
  getCompare Lt = Y.c_mk_lt
  getCompare Le = Y.c_mk_le

  getBArith Add = Y.c_mk_bv_add
  getBArith Sub = Y.c_mk_bv_sub
  getBArith Mul = Y.c_mk_bv_mul

  getBLogic And = Y.c_mk_bv_and
  getBLogic Or  = Y.c_mk_bv_or
  getBLogic Xor = Y.c_mk_bv_xor

  getBCompare _ Eq = Y.c_mk_eq
  getBCompare _ Ne = Y.c_mk_diseq
  getBCompare Signed   Gt = Y.c_mk_bv_sgt
  getBCompare Signed   Ge = Y.c_mk_bv_sge
  getBCompare Signed   Lt = Y.c_mk_bv_slt
  getBCompare Signed   Le = Y.c_mk_bv_sle
  getBCompare Unsigned Gt = Y.c_mk_bv_gt
  getBCompare Unsigned Ge = Y.c_mk_bv_ge
  getBCompare Unsigned Lt = Y.c_mk_bv_lt
  getBCompare Unsigned Le = Y.c_mk_bv_le

  getBShift L B0 = Y.c_mk_bv_shift_left0
  getBShift L B1 = Y.c_mk_bv_shift_left1
  getBShift R B0 = Y.c_mk_bv_shift_right0
  getBShift R B1 = Y.c_mk_bv_shift_right1

buildType :: PCtx -> Type -> IO PType
buildType ctx = go where
  lift1 x     f = go x >>= f
  liftN xs f = do
    ys <- mapM go xs
    withArrayLen ys $ \n a -> f a (fI n)

  go (TyName xs) = withCString xs $ Y.c_mk_type ctx
  go (TyFun xs r) = lift1 r $ \rp -> liftN xs $ \xp n ->
    Y.c_mk_function_type ctx xp n rp
  go (TyBitvec n) = Y.c_mk_bitvector_type ctx (fI n)
  go (TyTuple xs) = liftN xs $ \xp n -> MU.with xp $ \xpp ->
    Y.c_mk_tuple_type ctx xpp n

withContext :: Context -> (Env -> PCtx -> IO a) -> IO a
withContext (Context ds as) act
    = bracket Y.c_mk_context Y.c_del_context $ \ctx -> do
  let mkD m (Declare x t) = do
        tp <- buildType ctx t
        dp <- withCString x $ \xp -> Y.c_mk_var_decl ctx xp tp
        return $ M.insert x dp m
  Y.c_enable_type_checker 1
  env <- foldM mkD M.empty ds
  let mkA (Assert e) = buildExpr env ctx e >>= Y.c_assert ctx
  mapM_ mkA as
  act env ctx 

-- | Dump some information about a context
-- and expression to standard output.
dump :: Context -> Expr -> IO ()
dump c e = withContext c $ \env ctx -> do
  ep <- buildExpr env ctx e
  Y.c_dump_context ctx
  putStrLn "expression:"
  Y.c_pp_expr ep
  putStrLn ""

get :: Env -> PModel -> Get -> IO (Maybe (Ident, Value))
get env mdl (Get v t) = do
  x <- go (M.lookup v env)
  return ((,) v <$> x) where

  go (Just dp) = case t of
    VarBool     -> (Just . fromLBool) <$>    Y.c_get_value mdl dp
    VarInt      -> chk ValInt    $ Y.c_get_int_value       mdl dp
    VarRational -> withRational  $ Y.c_get_arith_value     mdl dp
    VarDouble   -> chk ValDouble $ Y.c_get_double_value    mdl dp
    VarBitvec n -> withVec n     $ Y.c_get_bitvector_value mdl dp (fI n)
  go Nothing = return Nothing

  fromLBool 1 = ValBool True
  fromLBool _ = ValBool False

  chk f a = alloca $ \p -> do
    x <- a p
    if x /= 1
      then return Nothing
      else (Just . f) <$> peek p
  withRational a = alloca $ \n -> alloca $ \d -> do
    x <- a n d
    if x /= 1
      then return Nothing
      else (Just . ValRational) <$> liftM2 (%) (fI <$> peek n) (fI <$> peek d)
  withVec n a = allocaArray n $ \p -> do
    x <- a p
    if x /= 1
      then return Nothing
      else (Just . ValBitvec . map (toEnum.fI)) <$> peekArray n p

-- | Solve a satisfiability query.
--
-- If satisfiable, returns values for the specified
-- variables.
solve :: Query -> IO Result
solve (Query c ts) = withContext c $ \env ctx -> do
  sat <- Y.c_check ctx
  case sat of
    1 -> do
      mdl <- Y.c_get_model ctx
      (Sat . M.fromList . catMaybes) <$> mapM (get env mdl) ts
    0 -> return Unsat
    _ -> return Unknown