{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
module Cryptol.REPL.Monad (
REPL(..), runREPL
, io
, raise
, stop
, catch
, rPutStrLn
, rPutStr
, rPrint
, REPLException(..)
, rethrowEvalError
, getFocusedEnv
, getModuleEnv, setModuleEnv
, getDynEnv, setDynEnv
, uniqify, freshName
, getTSyns, getNewtypes, getVars
, whenDebug
, getExprNames
, getTypeNames
, getPropertyNames
, getModNames
, LoadedModule(..), getLoadedMod, setLoadedMod, clearLoadedMod
, setEditPath, getEditPath
, setSearchPath, prependSearchPath
, getPrompt
, shouldContinue
, unlessBatch
, asBatch
, disableLet
, enableLet
, getLetEnabled
, validEvalContext
, updateREPLTitle
, setUpdateREPLTitle
, EnvVal(..)
, OptionDescr(..)
, setUser, getUser, getKnownUser, tryGetUser
, userOptions
, getUserSatNum
, getUserShowProverStats
, getUserProverValidate
, getPutStr
, getLogger
, setPutStr
, smokeTest
, Smoke(..)
) where
import Cryptol.REPL.Trie
import Cryptol.Eval (EvalError)
import qualified Cryptol.ModuleSystem as M
import qualified Cryptol.ModuleSystem.Env as M
import qualified Cryptol.ModuleSystem.Name as M
import qualified Cryptol.ModuleSystem.NamingEnv as M
import Cryptol.Parser (ParseError,ppError)
import Cryptol.Parser.NoInclude (IncludeError,ppIncludeError)
import Cryptol.Parser.NoPat (Error)
import Cryptol.Parser.Position (emptyRange, Range(from))
import qualified Cryptol.TypeCheck.AST as T
import qualified Cryptol.TypeCheck as T
import qualified Cryptol.IR.FreeVars as T
import qualified Cryptol.Utils.Ident as I
import Cryptol.Utils.PP
import Cryptol.Utils.Panic (panic)
import Cryptol.Utils.Logger(Logger, logPutStr, funLogger)
import qualified Cryptol.Parser.AST as P
import Cryptol.Symbolic (proverNames, lookupProver, SatNum(..))
import Control.Monad (ap,unless,when)
import Control.Monad.Base
import Control.Monad.IO.Class
import Control.Monad.Trans.Control
import Data.Char (isSpace)
import Data.IORef
(IORef,newIORef,readIORef,modifyIORef,atomicModifyIORef)
import Data.List (intercalate, isPrefixOf, unfoldr, sortBy)
import Data.Maybe (catMaybes)
import Data.Ord (comparing)
import Data.Typeable (Typeable)
import System.Directory (findExecutable)
import qualified Control.Exception as X
import qualified Data.Map as Map
import qualified Data.Set as Set
import Text.Read (readMaybe)
import Data.SBV.Dynamic (sbvCheckSolverInstallation)
import Prelude ()
import Prelude.Compat
data LoadedModule = LoadedModule
{ lName :: Maybe P.ModName
, lPath :: FilePath
}
data RW = RW
{ eLoadedMod :: Maybe LoadedModule
, eEditFile :: Maybe FilePath
, eContinue :: Bool
, eIsBatch :: Bool
, eModuleEnv :: M.ModuleEnv
, eUserEnv :: UserEnv
, eLogger :: Logger
, eLetEnabled :: Bool
, eUpdateTitle :: REPL ()
}
defaultRW :: Bool -> Logger -> IO RW
defaultRW isBatch l = do
env <- M.initialModuleEnv
return RW
{ eLoadedMod = Nothing
, eEditFile = Nothing
, eContinue = True
, eIsBatch = isBatch
, eModuleEnv = env
, eUserEnv = mkUserEnv userOptions
, eLogger = l
, eLetEnabled = True
, eUpdateTitle = return ()
}
mkPrompt :: RW -> String
mkPrompt rw
| eIsBatch rw = ""
| detailedPrompt = withEdit ++ "> "
| otherwise = modLn ++ "> "
where
detailedPrompt = False
modLn =
case lName =<< eLoadedMod rw of
Nothing -> "cryptol"
Just m
| M.isLoadedParamMod m (M.meLoadedModules (eModuleEnv rw)) ->
modName ++ "(parameterized)"
| otherwise -> modName
where modName = pretty m
withFocus =
case eLoadedMod rw of
Nothing -> modLn
Just m ->
case lName m of
Nothing -> ":r to reload " ++ lPath m ++ "\n" ++ modLn
Just _ -> modLn
withEdit =
case eEditFile rw of
Nothing -> withFocus
Just e
| Just f <- lPath <$> eLoadedMod rw
, f == e -> withFocus
| otherwise -> ":e to edit " ++ e ++ "\n" ++ withFocus
newtype REPL a = REPL { unREPL :: IORef RW -> IO a }
runREPL :: Bool -> Logger -> REPL a -> IO a
runREPL isBatch l m = do
ref <- newIORef =<< defaultRW isBatch l
unREPL m ref
instance Functor REPL where
{-# INLINE fmap #-}
fmap f m = REPL (\ ref -> fmap f (unREPL m ref))
instance Applicative REPL where
{-# INLINE pure #-}
pure = return
{-# INLINE (<*>) #-}
(<*>) = ap
instance Monad REPL where
{-# INLINE return #-}
return x = REPL (\_ -> return x)
{-# INLINE (>>=) #-}
m >>= f = REPL $ \ref -> do
x <- unREPL m ref
unREPL (f x) ref
instance MonadIO REPL where
liftIO = io
instance MonadBase IO REPL where
liftBase = liftIO
instance MonadBaseControl IO REPL where
type StM REPL a = a
liftBaseWith f = REPL $ \ref ->
f $ \m -> unREPL m ref
restoreM x = return x
instance M.FreshM REPL where
liftSupply f = modifyRW $ \ RW { .. } ->
let (a,s') = f (M.meSupply eModuleEnv)
in (RW { eModuleEnv = eModuleEnv { M.meSupply = s' }, .. },a)
data REPLException
= ParseError ParseError
| FileNotFound FilePath
| DirectoryNotFound FilePath
| NoPatError [Error]
| NoIncludeError [IncludeError]
| EvalError EvalError
| ModuleSystemError NameDisp M.ModuleError
| EvalPolyError T.Schema
| TypeNotTestable T.Type
| EvalInParamModule [M.Name]
| SBVError String
deriving (Show,Typeable)
instance X.Exception REPLException
instance PP REPLException where
ppPrec _ re = case re of
ParseError e -> ppError e
FileNotFound path -> sep [ text "File"
, text ("`" ++ path ++ "'")
, text"not found"
]
DirectoryNotFound path -> sep [ text "Directory"
, text ("`" ++ path ++ "'")
, text"not found or not a directory"
]
NoPatError es -> vcat (map pp es)
NoIncludeError es -> vcat (map ppIncludeError es)
ModuleSystemError ns me -> fixNameDisp ns (pp me)
EvalError e -> pp e
EvalPolyError s -> text "Cannot evaluate polymorphic value."
$$ text "Type:" <+> pp s
TypeNotTestable t -> text "The expression is not of a testable type."
$$ text "Type:" <+> pp t
EvalInParamModule xs ->
text "Expression depends on definitions from a parameterized module:"
$$ nest 2 (vcat (map pp xs))
SBVError s -> text "SBV error:" $$ text s
raise :: REPLException -> REPL a
raise exn = io (X.throwIO exn)
catch :: REPL a -> (REPLException -> REPL a) -> REPL a
catch m k = REPL (\ ref -> rethrowEvalError (unREPL m ref) `X.catch` \ e -> unREPL (k e) ref)
rethrowEvalError :: IO a -> IO a
rethrowEvalError m = run `X.catch` rethrow
where
run = do
a <- m
return $! a
rethrow :: EvalError -> IO a
rethrow exn = X.throwIO (EvalError exn)
io :: IO a -> REPL a
io m = REPL (\ _ -> m)
getRW :: REPL RW
getRW = REPL readIORef
modifyRW :: (RW -> (RW,a)) -> REPL a
modifyRW f = REPL (\ ref -> atomicModifyIORef ref f)
modifyRW_ :: (RW -> RW) -> REPL ()
modifyRW_ f = REPL (\ ref -> modifyIORef ref f)
getPrompt :: REPL String
getPrompt = mkPrompt `fmap` getRW
clearLoadedMod :: REPL ()
clearLoadedMod = do modifyRW_ (\rw -> rw { eLoadedMod = upd <$> eLoadedMod rw })
updateREPLTitle
where upd x = x { lName = Nothing }
setLoadedMod :: LoadedModule -> REPL ()
setLoadedMod n = do
modifyRW_ (\ rw -> rw { eLoadedMod = Just n })
updateREPLTitle
getLoadedMod :: REPL (Maybe LoadedModule)
getLoadedMod = eLoadedMod `fmap` getRW
setEditPath :: FilePath -> REPL ()
setEditPath p = modifyRW_ $ \rw -> rw { eEditFile = Just p }
getEditPath :: REPL (Maybe FilePath)
getEditPath = eEditFile <$> getRW
setSearchPath :: [FilePath] -> REPL ()
setSearchPath path = do
me <- getModuleEnv
setModuleEnv $ me { M.meSearchPath = path }
prependSearchPath :: [FilePath] -> REPL ()
prependSearchPath path = do
me <- getModuleEnv
setModuleEnv $ me { M.meSearchPath = path ++ M.meSearchPath me }
shouldContinue :: REPL Bool
shouldContinue = eContinue `fmap` getRW
stop :: REPL ()
stop = modifyRW_ (\ rw -> rw { eContinue = False })
unlessBatch :: REPL () -> REPL ()
unlessBatch body = do
rw <- getRW
unless (eIsBatch rw) body
asBatch :: REPL a -> REPL a
asBatch body = do
wasBatch <- eIsBatch `fmap` getRW
modifyRW_ $ (\ rw -> rw { eIsBatch = True })
a <- body
modifyRW_ $ (\ rw -> rw { eIsBatch = wasBatch })
return a
disableLet :: REPL ()
disableLet = modifyRW_ (\ rw -> rw { eLetEnabled = False })
enableLet :: REPL ()
enableLet = modifyRW_ (\ rw -> rw { eLetEnabled = True })
getLetEnabled :: REPL Bool
getLetEnabled = fmap eLetEnabled getRW
validEvalContext :: T.FreeVars a => a -> REPL ()
validEvalContext a =
do me <- eModuleEnv <$> getRW
let ds = T.freeVars a
badVals = foldr badName Set.empty (T.valDeps ds)
bad = foldr badName badVals (T.tyDeps ds)
badName nm bs =
case M.nameInfo nm of
M.Declared m _
| M.isLoadedParamMod m (M.meLoadedModules me) -> Set.insert nm bs
_ -> bs
unless (Set.null bad) $
raise (EvalInParamModule (Set.toList bad))
updateREPLTitle :: REPL ()
updateREPLTitle = unlessBatch $ do
rw <- getRW
eUpdateTitle rw
setUpdateREPLTitle :: REPL () -> REPL ()
setUpdateREPLTitle m = modifyRW_ (\rw -> rw { eUpdateTitle = m })
setPutStr :: (String -> IO ()) -> REPL ()
setPutStr fn = modifyRW_ $ \rw -> rw { eLogger = funLogger fn }
getPutStr :: REPL (String -> IO ())
getPutStr =
do rw <- getRW
return (logPutStr (eLogger rw))
getLogger :: REPL Logger
getLogger = eLogger <$> getRW
rPutStr :: String -> REPL ()
rPutStr str = do
f <- getPutStr
io (f str)
rPutStrLn :: String -> REPL ()
rPutStrLn str = rPutStr $ str ++ "\n"
rPrint :: Show a => a -> REPL ()
rPrint x = rPutStrLn (show x)
getFocusedEnv :: REPL (M.IfaceParams,M.IfaceDecls,M.NamingEnv,NameDisp)
getFocusedEnv = do
me <- getModuleEnv
let (dyDecls,dyNames,dyDisp) = M.dynamicEnv me
let (fParams,fDecls,fNames,fDisp) = M.focusedEnv me
return ( fParams
, dyDecls `mappend` fDecls
, dyNames `M.shadowing` fNames
, dyDisp `mappend` fDisp)
getVars :: REPL (Map.Map M.Name M.IfaceDecl)
getVars = do
(_,decls,_,_) <- getFocusedEnv
return (M.ifDecls decls)
getTSyns :: REPL (Map.Map M.Name T.TySyn)
getTSyns = do
(_,decls,_,_) <- getFocusedEnv
return (M.ifTySyns decls)
getNewtypes :: REPL (Map.Map M.Name T.Newtype)
getNewtypes = do
(_,decls,_,_) <- getFocusedEnv
return (M.ifNewtypes decls)
getExprNames :: REPL [String]
getExprNames =
do (_,_, fNames, _) <- getFocusedEnv
return (map (show . pp) (Map.keys (M.neExprs fNames)))
getTypeNames :: REPL [String]
getTypeNames =
do (_,_, fNames, _) <- getFocusedEnv
return (map (show . pp) (Map.keys (M.neTypes fNames)))
getPropertyNames :: REPL ([M.Name],NameDisp)
getPropertyNames =
do (_,decls,_,names) <- getFocusedEnv
let xs = M.ifDecls decls
ps = sortBy (comparing (from . M.nameLoc))
$ [ x | (x,d) <- Map.toList xs, T.PragmaProperty `elem` M.ifDeclPragmas d ]
return (ps, names)
getModNames :: REPL [I.ModName]
getModNames =
do me <- getModuleEnv
return (map T.mName (M.loadedModules me))
getModuleEnv :: REPL M.ModuleEnv
getModuleEnv = eModuleEnv `fmap` getRW
setModuleEnv :: M.ModuleEnv -> REPL ()
setModuleEnv me = modifyRW_ (\rw -> rw { eModuleEnv = me })
getDynEnv :: REPL M.DynamicEnv
getDynEnv = (M.meDynEnv . eModuleEnv) `fmap` getRW
setDynEnv :: M.DynamicEnv -> REPL ()
setDynEnv denv = do
me <- getModuleEnv
setModuleEnv (me { M.meDynEnv = denv })
uniqify :: M.Name -> REPL M.Name
uniqify name =
case M.nameInfo name of
M.Declared ns s ->
M.liftSupply (M.mkDeclared ns s (M.nameIdent name) (M.nameFixity name) (M.nameLoc name))
M.Parameter ->
panic "[REPL] uniqify" ["tried to uniqify a parameter: " ++ pretty name]
freshName :: I.Ident -> M.NameSource -> REPL M.Name
freshName i sys =
M.liftSupply (M.mkDeclared I.interactiveName sys i Nothing emptyRange)
type UserEnv = Map.Map String EnvVal
data EnvVal
= EnvString String
| EnvProg String [String]
| EnvNum !Int
| EnvBool Bool
deriving (Show)
mkUserEnv :: OptionMap -> UserEnv
mkUserEnv opts = Map.fromList $ do
opt <- leaves opts
return (optName opt, optDefault opt)
setUser :: String -> String -> REPL ()
setUser name val = case lookupTrieExact name userOptions of
[opt] -> setUserOpt opt
[] -> rPutStrLn ("Unknown env value `" ++ name ++ "`")
_ -> rPutStrLn ("Ambiguous env value `" ++ name ++ "`")
where
setUserOpt opt = case optDefault opt of
EnvString _ -> doCheck (EnvString val)
EnvProg _ _ ->
case splitOptArgs val of
prog:args -> doCheck (EnvProg prog args)
[] -> rPutStrLn ("Failed to parse command for field, `" ++ name ++ "`")
EnvNum _ -> case reads val of
[(x,_)] -> doCheck (EnvNum x)
_ -> rPutStrLn ("Failed to parse number for field, `" ++ name ++ "`")
EnvBool _
| any (`isPrefixOf` val) ["enable", "on", "yes", "true"] ->
writeEnv (EnvBool True)
| any (`isPrefixOf` val) ["disable", "off", "no", "false"] ->
writeEnv (EnvBool False)
| otherwise ->
rPutStrLn ("Failed to parse boolean for field, `" ++ name ++ "`")
where
doCheck v = do (r,ws) <- io (optCheck opt v)
case r of
Just err -> rPutStrLn err
Nothing -> do mapM_ rPutStrLn ws
writeEnv v
writeEnv ev =
do optEff opt ev
modifyRW_ (\rw -> rw { eUserEnv = Map.insert (optName opt) ev (eUserEnv rw) })
splitOptArgs :: String -> [String]
splitOptArgs = unfoldr (parse "")
where
parse acc (c:cs) | isQuote c = quoted (c:acc) cs
| not (isSpace c) = parse (c:acc) cs
| otherwise = result acc cs
parse acc [] = result acc []
quoted acc (c:cs) | isQuote c = parse (c:acc) cs
| otherwise = quoted (c:acc) cs
quoted acc [] = result acc []
result [] [] = Nothing
result [] cs = parse [] (dropWhile isSpace cs)
result acc cs = Just (reverse acc, dropWhile isSpace cs)
isQuote :: Char -> Bool
isQuote c = c `elem` ("'\"" :: String)
tryGetUser :: String -> REPL (Maybe EnvVal)
tryGetUser name = do
rw <- getRW
return (Map.lookup name (eUserEnv rw))
getUser :: String -> REPL EnvVal
getUser name = do
mb <- tryGetUser name
case mb of
Just ev -> return ev
Nothing -> panic "[REPL] getUser" ["option `" ++ name ++ "` does not exist"]
getKnownUser :: IsEnvVal a => String -> REPL a
getKnownUser x = fromEnvVal <$> getUser x
class IsEnvVal a where
fromEnvVal :: EnvVal -> a
instance IsEnvVal Bool where
fromEnvVal x = case x of
EnvBool b -> b
_ -> badIsEnv "Bool"
instance IsEnvVal Int where
fromEnvVal x = case x of
EnvNum b -> b
_ -> badIsEnv "Num"
instance IsEnvVal (String,[String]) where
fromEnvVal x = case x of
EnvProg b bs -> (b,bs)
_ -> badIsEnv "Prog"
instance IsEnvVal String where
fromEnvVal x = case x of
EnvString b -> b
_ -> badIsEnv "String"
badIsEnv :: String -> a
badIsEnv x = panic "fromEnvVal" [ "[REPL] Expected a `" ++ x ++ "` value." ]
getUserShowProverStats :: REPL Bool
getUserShowProverStats = getKnownUser "prover-stats"
getUserProverValidate :: REPL Bool
getUserProverValidate = getKnownUser "prover-validate"
type OptionMap = Trie OptionDescr
mkOptionMap :: [OptionDescr] -> OptionMap
mkOptionMap = foldl insert emptyTrie
where
insert m d = insertTrie (optName d) d m
type Checker = EnvVal -> IO (Maybe String, [String])
noCheck :: Checker
noCheck _ = return (Nothing, [])
noWarns :: Maybe String -> IO (Maybe String, [String])
noWarns mb = return (mb, [])
data OptionDescr = OptionDescr
{ optName :: String
, optDefault :: EnvVal
, optCheck :: Checker
, optHelp :: String
, optEff :: EnvVal -> REPL ()
}
simpleOpt :: String -> EnvVal -> Checker -> String -> OptionDescr
simpleOpt optName optDefault optCheck optHelp =
OptionDescr { optEff = \ _ -> return (), .. }
userOptions :: OptionMap
userOptions = mkOptionMap
[ simpleOpt "base" (EnvNum 16) checkBase
"The base to display words at (2, 8, 10, or 16)."
, simpleOpt "debug" (EnvBool False) noCheck
"Enable debugging output."
, simpleOpt "ascii" (EnvBool False) noCheck
"Whether to display 7- or 8-bit words using ASCII notation."
, simpleOpt "infLength" (EnvNum 5) checkInfLength
"The number of elements to display for infinite sequences."
, simpleOpt "tests" (EnvNum 100) noCheck
"The number of random tests to try with ':check'."
, simpleOpt "satNum" (EnvString "1") checkSatNum
"The maximum number of :sat solutions to display (\"all\" for no limit)."
, simpleOpt "prover" (EnvString "z3") checkProver $
"The external SMT solver for ':prove' and ':sat'\n(" ++ proverListString ++ ")."
, simpleOpt "warnDefaulting" (EnvBool True) noCheck
"Choose whether to display warnings when defaulting."
, simpleOpt "warnShadowing" (EnvBool True) noCheck
"Choose whether to display warnings when shadowing symbols."
, simpleOpt "smtfile" (EnvString "-") noCheck
"The file to use for SMT-Lib scripts (for debugging or offline proving).\nUse \"-\" for stdout."
, OptionDescr "mono-binds" (EnvBool True) noCheck
"Whether or not to generalize bindings in a 'where' clause." $
\case EnvBool b -> do me <- getModuleEnv
setModuleEnv me { M.meMonoBinds = b }
_ -> return ()
, OptionDescr "tc-solver" (EnvProg "z3" [ "-smt2", "-in" ])
noCheck
"The solver that will be used by the type checker." $
\case EnvProg prog args -> do me <- getModuleEnv
let cfg = M.meSolverConfig me
setModuleEnv me { M.meSolverConfig =
cfg { T.solverPath = prog
, T.solverArgs = args } }
_ -> return ()
, OptionDescr "tc-debug" (EnvNum 0)
noCheck
"Enable type-checker debugging output." $
\case EnvNum n -> do me <- getModuleEnv
let cfg = M.meSolverConfig me
setModuleEnv me { M.meSolverConfig = cfg{ T.solverVerbose = fromIntegral n } }
_ -> return ()
, OptionDescr "core-lint" (EnvBool False)
noCheck
"Enable sanity checking of type-checker." $
let setIt x = do me <- getModuleEnv
setModuleEnv me { M.meCoreLint = x }
in \case EnvBool True -> setIt M.CoreLint
EnvBool False -> setIt M.NoCoreLint
_ -> return ()
, simpleOpt "prover-stats" (EnvBool True) noCheck
"Enable prover timing statistics."
, simpleOpt "prover-validate" (EnvBool False) noCheck
"Validate :sat examples and :prove counter-examples for correctness."
, simpleOpt "show-examples" (EnvBool True) noCheck
"Print the (counter) example after :sat or :prove"
]
checkBase :: Checker
checkBase val = case val of
EnvNum n
| n `elem` [2,8,10,16] -> noWarns Nothing
| otherwise -> noWarns $ Just "base must be 2, 8, 10, or 16"
_ -> noWarns $ Just "unable to parse a value for base"
checkInfLength :: Checker
checkInfLength val = case val of
EnvNum n
| n >= 0 -> noWarns Nothing
| otherwise -> noWarns $ Just "the number of elements should be positive"
_ -> noWarns $ Just "unable to parse a value for infLength"
checkProver :: Checker
checkProver val = case val of
EnvString s
| s `notElem` proverNames ->
noWarns $ Just $ "Prover must be " ++ proverListString
| s `elem` ["offline", "any"] -> noWarns Nothing
| otherwise ->
do let prover = lookupProver s
available <- sbvCheckSolverInstallation prover
let ws = if available
then []
else ["Warning: " ++ s ++ " installation not found"]
return (Nothing, ws)
_ -> noWarns $ Just "unable to parse a value for prover"
proverListString :: String
proverListString = concatMap (++ ", ") (init proverNames) ++ "or " ++ last proverNames
checkSatNum :: Checker
checkSatNum val = case val of
EnvString "all" -> noWarns Nothing
EnvString s ->
case readMaybe s :: Maybe Int of
Just n | n >= 1 -> noWarns Nothing
_ -> noWarns $ Just "must be an integer > 0 or \"all\""
_ -> noWarns $ Just "unable to parse a value for satNum"
getUserSatNum :: REPL SatNum
getUserSatNum = do
s <- getKnownUser "satNum"
case s of
"all" -> return AllSat
_ | Just n <- readMaybe s -> return (SomeSat n)
_ -> panic "REPL.Monad.getUserSatNum"
[ "invalid satNum option" ]
whenDebug :: REPL () -> REPL ()
whenDebug m = do
b <- getKnownUser "debug"
when b m
smokeTest :: REPL [Smoke]
smokeTest = catMaybes <$> sequence tests
where
tests = [ z3exists ]
type SmokeTest = REPL (Maybe Smoke)
data Smoke
= Z3NotFound
deriving (Show, Eq)
instance PP Smoke where
ppPrec _ smoke =
case smoke of
Z3NotFound -> text . intercalate " " $ [
"[error] z3 is required to run Cryptol, but was not found in the"
, "system path. See the Cryptol README for more on how to install z3."
]
z3exists :: SmokeTest
z3exists = do
mPath <- io $ findExecutable "z3"
case mPath of
Nothing -> return (Just Z3NotFound)
Just _ -> return Nothing