libclang-bindings-0.1.0.0: test/Test/Util/FoldException.hs
-- | Artificial exceptions in folds
--
-- Intended for qualified import.
--
-- > import Test.Util.FoldException (FoldException(..))
-- > import Test.Util.FoldException qualified as FoldException
module Test.Util.FoldException (
-- * Definition
FoldException(..)
, descrAt
, descrTopLevel
, handleAt
, handleTopLevel
-- * Instrumentation
, Info(..)
, defaultInfo
-- ** Execution
, parse
-- * Model
, model
) where
import Control.Exception (Exception)
import Control.Exception qualified as Exception
import Control.Monad
import Control.Monad.Except
import Data.String
import Test.QuickCheck
import Test.Util.AST (AST (..))
import Test.Util.AST qualified as AST
import Test.Util.Clang qualified as Clang
import Test.Util.Input (TestInput)
import Clang.HighLevel.Types hiding (FoldException)
import Clang.LowLevel.Core
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
-- | Exception thrown by instrumented fold
--
-- The 'Int' parameter means that if we throw (and possibly catch) /multiple/
-- exceptions from inside a fold, that we end up with the correct one.
data FoldException = FoldException Int
deriving stock (Show)
deriving anyclass (Exception)
-- | Description generated by handler somewhere during folding
descrAt :: AST.Descr -> FoldException -> AST.Descr
descrAt (AST.Descr context) ex = AST.Descr $
show ex ++ " at " ++ context
-- | Description generated by top-level handler (outside the fold entirely)
descrTopLevel :: FoldException -> AST.Descr
descrTopLevel ex = AST.Descr $
show ex ++ " at top-level"
-- | Handler intended for use in folds
handleAt :: CXCursor -> FoldException -> IO (HandlerResult (Maybe (AST.Node AST.Descr)))
handleAt curr ex = do
node <- AST.descrAt curr
return $ HandlerResult $ Just $ AST.Node (descrAt node ex) $ AST.Siblings []
-- | Top-level handler
--
-- Since this handler is /outside/ the scope of the fold, we don't get a cursor,
-- and so cannot report an error location within the AST.
handleTopLevel :: FoldException -> IO (AST AST.Descr)
handleTopLevel ex = return $ AST . AST.Siblings $ [
AST.Node (descrTopLevel ex) $ AST.Siblings []
]
{-------------------------------------------------------------------------------
Info
-------------------------------------------------------------------------------}
-- | Local information about exception behaviour
--
-- This is the /local/ information at this node: does /this/ node catch or
-- /throw/ something. See also 'RecursiveInfo'.
data Info = Info {
exceptionHandler :: Bool
, throwInBody :: Maybe Int
, throwInSummarize :: Maybe Int
}
deriving stock (Show)
defaultInfo :: Info
defaultInfo = Info {
exceptionHandler = False
, throwInBody = Nothing
, throwInSummarize = Nothing
}
instance Arbitrary Info where
arbitrary =
pure Info
<*> arbitrary
<*> arbitrary
<*> arbitrary
shrink info = concat [
[ info{exceptionHandler = x} | x <- shrink exceptionHandler ]
, [ info{throwInBody = x} | x <- shrink throwInBody ]
, [ info{throwInSummarize = x} | x <- shrink throwInSummarize ]
]
where
Info{exceptionHandler, throwInBody, throwInSummarize} = info
instance AST.ShowComment Info where
showComment info = Just $ fromString $ "// " <> show info
{-------------------------------------------------------------------------------
Execution of 'Info'
-------------------------------------------------------------------------------}
fold :: (CXCursor -> IO Info) -> Fold IO (AST.Node AST.Descr)
fold infoForCursor = go
where
go :: Fold IO (AST.Node AST.Descr)
go = foldWithHandler handler $ \curr -> do
info <- infoForCursor curr
case throwInBody info of
Just n -> Exception.throwIO $ FoldException n
Nothing -> do
node <- AST.descrAt curr
foldRecurseWith go $ \children ->
case throwInSummarize info of
Just n -> Exception.throwIO $ FoldException n
Nothing -> return $ AST.Node node (AST.Siblings children)
handler :: CXCursor -> FoldException -> IO (HandlerResult (Maybe (AST.Node AST.Descr)))
handler curr e = do
info <- infoForCursor curr
if exceptionHandler info
then handleAt curr e
else return HandlerRethrow
parse :: (CXCursor -> IO Info) -> TestInput -> IO (AST AST.Descr)
parse infoForCursor input =
Exception.handle handleTopLevel $
AST . AST.Siblings <$> Clang.parseUsing (fold infoForCursor) input
{-------------------------------------------------------------------------------
Model
-------------------------------------------------------------------------------}
-- | Model that tells us what should happen in the presence of exceptions
model :: AST (AST.Descr, Info) -> AST AST.Descr
model = \(AST siblings) ->
either catchTopLevel id $
AST <$> runExcept (goSiblings siblings)
where
-- Model equivalent of 'handleTopLevel'
catchTopLevel :: FoldException -> AST AST.Descr
catchTopLevel ex = AST . AST.Siblings $ [
AST.Node (descrTopLevel ex) $ AST.Siblings []
]
goSiblings ::
AST.Siblings (AST.Descr, Info)
-> Except FoldException (AST.Siblings AST.Descr)
goSiblings (AST.Siblings siblings) = AST.Siblings <$> mapM goNode siblings
goNode ::
AST.Node (AST.Descr, Info)
-> Except FoldException (AST.Node AST.Descr)
goNode (AST.Node (descr, info) children) =
flip catchError catchAt $ do
-- The order here matters for /which/ exception we might throw:
-- first body, then children, and finally summarize
forM_ (throwInBody info) $ throwError . FoldException
children' <- goSiblings children
forM_ (throwInSummarize info) $ throwError . FoldException
return $ AST.Node descr children'
where
-- Model equivalent of 'handleAt'
catchAt :: FoldException -> Except FoldException (AST.Node AST.Descr)
catchAt ex
| exceptionHandler info
= return $ AST.Node (descrAt descr ex) $ AST.Siblings []
| otherwise
= throwError ex