{-# LANGUAGE CPP #-}
-- | When you've caught all the exceptions that can be handled safely,
--   this is what you're left with.
--
-- > runEitherIO . fromIO ≡ id
module UnexceptionalIO (
	UnexceptionalIO,
	UIO,
	fromIO,
	runUnexceptionalIO,
	runEitherIO,
	-- * Unsafe entry points
#ifdef __GLASGOW_HASKELL__
	fromIO',
#endif
	unsafeFromIO,
	-- * Utilities
	syncIO
) where

import Control.Applicative (Applicative(..))
import Control.Monad (liftM, ap, (<=<))
import Control.Monad.Fix (MonadFix(..))
#ifdef __GLASGOW_HASKELL__
import Data.Dynamic (Dynamic)
import System.Exit (ExitCode)
import qualified Control.Exception as Ex

type SomeException = Ex.SomeException

throwIO :: (Ex.Exception e) => e -> IO a
throwIO = Ex.throwIO
#else
-- Haskell98 import 'IO' instead
import System.IO.Error (IOError, ioError, try)

type SomeException = IOError

throwIO :: SomeException -> IO a
throwIO = ioError
#endif

-- | IO without any non-error, synchronous exceptions
newtype UnexceptionalIO a = UnexceptionalIO (IO a)

-- | or, you may prefer a short name
type UIO = UnexceptionalIO

instance Functor UnexceptionalIO where
	fmap = liftM

instance Applicative UnexceptionalIO where
	pure = return
	(<*>) = ap

instance Monad UnexceptionalIO where
	return = UnexceptionalIO . return
	(UnexceptionalIO x) >>= f = UnexceptionalIO (x >>= runUnexceptionalIO . f)

	fail s = error $ "UnexceptionalIO cannot fail (" ++ s ++ ")"

instance MonadFix UnexceptionalIO where
	mfix f = UnexceptionalIO (mfix $ runUnexceptionalIO . f)

-- | Catch any non-error, synchronous exceptions in an 'IO' action
fromIO :: IO a -> UnexceptionalIO (Either SomeException a)
fromIO = unsafeFromIO . syncIO

-- | Re-embed 'UnexceptionalIO' into 'IO'
runUnexceptionalIO :: UnexceptionalIO a -> IO a
runUnexceptionalIO (UnexceptionalIO io) = io

-- | Re-embed 'UnexceptionalIO' and possible exception back into 'IO'
#ifdef __GLASGOW_HASKELL__
runEitherIO :: (Ex.Exception e) => UnexceptionalIO (Either e a) -> IO a
#else
runEitherIO :: UnexceptionalIO (Either SomeException a) -> IO a
#endif
runEitherIO = either throwIO return <=< runUnexceptionalIO

#ifdef __GLASGOW_HASKELL__
-- | You promise that 'e' covers all non-error, synchronous exceptions
--   thrown by this 'IO' action
--
-- This function is partial if you lie
fromIO' :: (Ex.Exception e) => IO a -> UnexceptionalIO (Either e a)
fromIO' =
	(return . either (Left . maybePartial . Ex.fromException) Right) <=< fromIO
	where
	maybePartial (Just x) = x
	maybePartial Nothing = error "UnexceptionalIO.fromIO' exception of unspecified type"
#endif

-- | You promise there are no exceptions thrown by this 'IO' action
unsafeFromIO :: IO a -> UnexceptionalIO a
unsafeFromIO = UnexceptionalIO

-- | Catch all exceptions, except for asynchronous exceptions found in @base@
syncIO :: IO a -> IO (Either SomeException a)
#ifdef __GLASGOW_HASKELL__
syncIO a = Ex.catches (fmap Right a) [
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.ArithException)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.ArrayException)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.AssertionFailed)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.AsyncException)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.BlockedIndefinitelyOnMVar)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.BlockedIndefinitelyOnSTM)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.Deadlock)),
		Ex.Handler (\e -> Ex.throwIO (e :: Dynamic)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.ErrorCall)),
		Ex.Handler (\e -> Ex.throwIO (e :: ExitCode)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.NestedAtomically)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.NoMethodError)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.NonTermination)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.PatternMatchFail)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.RecConError)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.RecSelError)),
		Ex.Handler (\e -> Ex.throwIO (e :: Ex.RecUpdError)),
		Ex.Handler (return . Left)
	]
#else
syncIO = try
#endif