{-# LANGUAGE Safe #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
module Cryptol.Eval.Monad
(
Eval(..)
, runEval
, EvalOpts(..)
, getEvalOpts
, PPOpts(..)
, io
, delay
, delayFill
, ready
, blackhole
, EvalError(..)
, evalPanic
, typeCannotBeDemoted
, divideByZero
, negativeExponent
, logNegative
, wordTooWide
, cryUserError
, cryLoopError
, invalidIndex
) where
import Control.DeepSeq
import Control.Monad
import Control.Monad.Fix
import Control.Monad.IO.Class
import Data.IORef
import Data.Typeable (Typeable)
import qualified Control.Exception as X
import Cryptol.Utils.Panic
import Cryptol.Utils.PP
import Cryptol.Utils.Logger(Logger)
import Cryptol.TypeCheck.AST(Type)
ready :: a -> Eval a
ready a = Ready a
data PPOpts = PPOpts
{ useAscii :: Bool
, useBase :: Int
, useInfLength :: Int
}
data EvalOpts = EvalOpts
{ evalLogger :: Logger
, evalPPOpts :: PPOpts
}
data Eval a
= Ready !a
| Thunk !(EvalOpts -> IO a)
data ThunkState a
= Unforced
| BlackHole
| Forced !a
getEvalOpts :: Eval EvalOpts
getEvalOpts = Thunk return
{-# INLINE delay #-}
delay :: Maybe String
-> Eval a
-> Eval (Eval a)
delay _ (Ready a) = Ready (Ready a)
delay msg (Thunk x) = Thunk $ \opts -> do
let msg' = maybe "" ("while evaluating "++) msg
let retry = cryLoopError msg'
r <- newIORef Unforced
return $ unDelay retry r (x opts)
{-# INLINE delayFill #-}
delayFill :: Eval a
-> Eval a
-> Eval (Eval a)
delayFill (Ready x) _ = Ready (Ready x)
delayFill (Thunk x) retry = Thunk $ \opts -> do
r <- newIORef Unforced
return $ unDelay retry r (x opts)
blackhole :: String
-> Eval (Eval a, Eval a -> Eval ())
blackhole msg = do
r <- io $ newIORef (fail msg)
let get = join (io $ readIORef r)
let set = io . writeIORef r
return (get, set)
unDelay :: Eval a -> IORef (ThunkState a) -> IO a -> Eval a
unDelay retry r x = do
rval <- io $ readIORef r
case rval of
Forced val -> return val
BlackHole ->
retry
Unforced -> io $ do
writeIORef r BlackHole
val <- x
writeIORef r (Forced val)
return val
runEval :: EvalOpts -> Eval a -> IO a
runEval _ (Ready a) = return a
runEval opts (Thunk x) = x opts
{-# INLINE evalBind #-}
evalBind :: Eval a -> (a -> Eval b) -> Eval b
evalBind (Ready a) f = f a
evalBind (Thunk x) f = Thunk $ \opts -> x opts >>= runEval opts . f
instance Functor Eval where
fmap f (Ready x) = Ready (f x)
fmap f (Thunk m) = Thunk $ \opts -> f <$> m opts
{-# INLINE fmap #-}
instance Applicative Eval where
pure = return
(<*>) = ap
{-# INLINE pure #-}
{-# INLINE (<*>) #-}
instance Monad Eval where
return = Ready
fail x = Thunk (\_ -> fail x)
(>>=) = evalBind
{-# INLINE return #-}
{-# INLINE (>>=) #-}
instance MonadIO Eval where
liftIO = io
instance NFData a => NFData (Eval a) where
rnf (Ready a) = rnf a
rnf (Thunk _) = ()
instance MonadFix Eval where
mfix f = Thunk $ \opts -> mfix (\x -> runEval opts (f x))
io :: IO a -> Eval a
io m = Thunk (\_ -> m)
{-# INLINE io #-}
evalPanic :: HasCallStack => String -> [String] -> a
evalPanic cxt = panic ("[Eval] " ++ cxt)
data EvalError
= InvalidIndex Integer
| TypeCannotBeDemoted Type
| DivideByZero
| NegativeExponent
| LogNegative
| WordTooWide Integer
| UserError String
| LoopError String
deriving (Typeable,Show)
instance PP EvalError where
ppPrec _ e = case e of
InvalidIndex i -> text "invalid sequence index:" <+> integer i
TypeCannotBeDemoted t -> text "type cannot be demoted:" <+> pp t
DivideByZero -> text "division by 0"
NegativeExponent -> text "negative exponent"
LogNegative -> text "logarithm of negative"
WordTooWide w ->
text "word too wide for memory:" <+> integer w <+> text "bits"
UserError x -> text "Run-time error:" <+> text x
LoopError x -> text "<<loop>>" <+> text x
instance X.Exception EvalError
typeCannotBeDemoted :: Type -> a
typeCannotBeDemoted t = X.throw (TypeCannotBeDemoted t)
divideByZero :: Eval a
divideByZero = io (X.throwIO DivideByZero)
negativeExponent :: Eval a
negativeExponent = io (X.throwIO NegativeExponent)
logNegative :: Eval a
logNegative = io (X.throwIO LogNegative)
wordTooWide :: Integer -> a
wordTooWide w = X.throw (WordTooWide w)
cryUserError :: String -> Eval a
cryUserError msg = io (X.throwIO (UserError msg))
cryLoopError :: String -> Eval a
cryLoopError msg = io (X.throwIO (LoopError msg))
invalidIndex :: Integer -> Eval a
invalidIndex i = io (X.throwIO (InvalidIndex i))