-- | -- Type classes and built-in implementation for primitive Haskell types -- module Ntha.Z3.Class ( -- ** Types whose values are encodable to Z3 internal AST Z3Encoded(..), -- ** Types representable as Z3 Sort -- XXX: Unsound now -- XXX: Too flexible, can be used to encode Type ADT Z3Sorted(..), -- ** Type proxy helper, used with Z3Sorted Z3Sort(..), -- ** Types with reserved value for Z3 encoding use -- XXX: Magic value for built-in types Z3Reserved(..), -- ** Monad which can be instantiated into a concrete context SMT(..) ) where import Ntha.Z3.Logic import Z3.Monad import Control.Monad.Except import qualified Data.Map as M import qualified Data.Set as S data Z3Sort a = Z3Sort class Z3Encoded a where encode :: SMT m e => a -> m e AST -- | XXX: Unsound class Z3Sorted a where -- | Map a value to Sort, the value should be a type-level thing sort :: SMT m e => a -> m e Sort sort _ = sortPhantom (Z3Sort :: Z3Sort a) -- | Map a Haskell type to Sort sortPhantom :: SMT m e => Z3Sort a -> m e Sort sortPhantom _ = smtError "sort error" class Z3Encoded a => Z3Reserved a where def :: a class (MonadError String (m e), MonadZ3 (m e)) => SMT m e where -- | Globally unique id genFreshId :: m e Int -- | Given data type declarations, extra field, and the SMT monad, return the fallible result in IO monad runSMT :: Z3Sorted ty => [(String, [(String, [(String, ty)])])] -> e -> m e a -> IO (Either String a) -- | Binding a variable String name to two things: an de Brujin idx as Z3 AST generated by mkBound and binder's Sort bindQualified :: String -> AST -> Sort -> m e () -- | Get the above AST -- FIXME: The context management need extra -- we need to make sure that old binding wouldn't be destoryed -- XXX: We shouldn't expose a Map here. A fallible query interface is better getQualifierCtx :: m e (M.Map String (AST, Sort)) -- | Get the preprocessed datatype context, a map from ADT's type name to its Z3 Sort -- XXX: We shouldn't expose a Map here. A fallible query interface is better getDataTypeCtx :: m e (M.Map String Sort) -- | Get extra getExtra :: m e e -- | Set extra modifyExtra :: (e -> e) -> m e () -- | User don't have to import throwError smtError :: String -> m e a smtError = throwError instance Z3Reserved Int where def = -1 -- XXX: Magic number instance Z3Sorted Int where sortPhantom _ = mkIntSort instance Z3Encoded Int where encode i = mkIntSort >>= mkInt i instance Z3Reserved Double where def = -1.0 -- XXX: Magic number instance Z3Sorted Double where sortPhantom _ = mkRealSort instance Z3Encoded Double where encode = mkRealNum instance Z3Reserved Bool where def = False -- XXX: Magic number instance Z3Sorted Bool where sortPhantom _ = mkBoolSort instance Z3Encoded Bool where encode = mkBool -- The basic idea: -- For each (k, v), assert in Z3 that if we select k from array we will get -- the same value v -- HACK: to set a default value for rest fields (or else we always get the last asserted value -- as default, which is certainly not complying to finite map's definition), thus the -- user should guarantee that he/she will never never think this value as a vaid one, -- if not, he/she might get "a valid value mapped to a invalid key" semantics instance (Z3Sorted k, Z3Encoded k, Z3Sorted v, Z3Reserved v) => Z3Encoded (M.Map k v) where encode m = do fid <- genFreshId arrSort <- sort m arr <- mkFreshConst ("map" ++ "_" ++ show fid) arrSort mapM_ (\(k, v) -> do kast <- encode k vast <- encode v sel <- mkSelect arr kast mkEq sel vast >>= assert) (M.toList m) arrValueDef <- mkArrayDefault arr vdef <- encode (def :: v) mkEq arrValueDef vdef >>= assert return arr instance (Z3Sorted k, Z3Sorted v) => Z3Sorted (M.Map k v) where sortPhantom _ = do sk <- sortPhantom (Z3Sort :: Z3Sort k) sv <- sortPhantom (Z3Sort :: Z3Sort v) mkArraySort sk sv -- Basic idea: -- Set v =def= Map v {0, 1} -- Thank god, this is much more sound instance (Z3Sorted v, Z3Encoded v) => Z3Encoded (S.Set v) where encode s = do setSort <- sort s fid <- genFreshId arr <- mkFreshConst ("set" ++ "_" ++ show fid) setSort mapM_ (\e -> do ast <- encode e sel <- mkSelect arr ast one <- (mkIntSort >>= mkInt 1) mkEq sel one >>= assert) (S.toList s) arrValueDef <- mkArrayDefault arr zero <- (mkIntSort >>= mkInt 0) mkEq zero arrValueDef >>= assert return arr instance Z3Sorted v => Z3Sorted (S.Set v) where sortPhantom _ = do sortElem <- sortPhantom (Z3Sort :: Z3Sort v) intSort <- mkIntSort mkArraySort sortElem intSort instance (Z3Sorted t, Z3Sorted ty, Z3Encoded a) => Z3Encoded (Pred t ty a) where encode PTrue = mkTrue encode PFalse = mkFalse encode (PConj p1 p2) = do a1 <- encode p1 a2 <- encode p2 mkAnd [a1, a2] encode (PDisj p1 p2) = do a1 <- encode p1 a2 <- encode p2 mkOr [a1, a2] encode (PXor p1 p2) = do a1 <- encode p1 a2 <- encode p2 mkXor a1 a2 encode (PNeg p) = encode p >>= mkNot encode (PForAll x ty p) = do sym <- mkStringSymbol x xsort <- sort ty -- "0" is de brujin idx for current binder -- it is passed to Z3 which returns an intenal (idx :: AST) -- This (idx :: AST) will be used to replace the variable -- in the abstraction body when encountered, thus it is stored -- in context by bindQualified we provide -- XXX: we should save and restore qualifier context here idx <- mkBound 0 xsort local $ do bindQualified x idx xsort body <- encode p -- The first [] is [Pattern], which is not really useful here mkForall [] [sym] [xsort] body encode (PExists x ty p) = do sym <- mkStringSymbol x xsort <- sort ty idx <- mkBound 0 xsort local $ do bindQualified x idx xsort a <- encode p mkExists [] [sym] [xsort] a -- HACK encode (PExists2 x y ty p) = do sym1 <- mkStringSymbol x sym2 <- mkStringSymbol y xsort <- sort ty idx1 <- mkBound 0 xsort idx2 <- mkBound 1 xsort local $ do bindQualified x idx1 xsort bindQualified y idx2 xsort a <- encode p mkExists [] [sym1, sym2] [xsort, xsort] a encode (PImpli p1 p2) = do a1 <- encode p1 a2 <- encode p2 mkImplies a1 a2 encode (PIff p1 p2) = do a1 <- encode p1 a2 <- encode p2 mkIff a1 a2 encode (PAssert a) = encode a