emacs-module-0.2: src/Emacs/Module/Monad.hs
----------------------------------------------------------------------------
-- |
-- Module : Emacs.Module.Monad
-- Copyright : (c) Sergey Vinokurov 2022
-- License : Apache-2.0 (see LICENSE)
-- Maintainer : serg.foo@gmail.com
----------------------------------------------------------------------------
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}
module Emacs.Module.Monad
( module Emacs.Module.Monad.Class
, EmacsM
, runEmacsM
) where
import Control.Exception
import Control.Exception qualified as Exception
import Control.Monad.Base
import Control.Monad.Catch qualified as Catch
import Control.Monad.Fix (MonadFix)
import Control.Monad.Interleave
import Control.Monad.Primitive hiding (unsafeInterleave)
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Data.ByteString qualified as BS
import Data.ByteString.Short (ShortByteString)
import Data.ByteString.Unsafe qualified as BSU
import Data.Coerce
import Data.Emacs.Module.Doc qualified as Doc
import Data.Int
import Data.Kind
import Data.Proxy
import Data.Text (Text)
import Data.Void
import Foreign.C.Types
import Foreign.Ptr
import GHC.ForeignPtr
import GHC.Stack (callStack)
import Prettyprinter
import Data.Emacs.Module.Args
import Data.Emacs.Module.Env.Functions
import Data.Emacs.Module.Env.ProcessInput qualified as ProcessInput
import Data.Emacs.Module.GetRawValue
import Data.Emacs.Module.NonNullPtr
import Data.Emacs.Module.Raw.Env qualified as Env
import Data.Emacs.Module.Raw.Env.Internal (Env, RawFunctionType)
import Data.Emacs.Module.Raw.Env.Internal qualified as Env
import Data.Emacs.Module.Raw.Value
import Data.Emacs.Module.SymbolName.Internal
import Data.Emacs.Module.Value.Internal
import Emacs.Module.Assert
import Emacs.Module.Errors
import Emacs.Module.Monad.Class
import Emacs.Module.Monad.Common as Common
import Foreign.Ptr.Builder as PtrBuilder
data Environment = Environment
{ eEnv :: Env
, eNonLocalState :: {-# UNPACK #-} !NonLocalState
, eArgsCache :: BuilderCache (RawValue 'Unknown)
}
-- | Concrete monad for interacting with Emacs. It provides:
--
-- 1. Ability to call Emacs C functions and automatically rethrows any
-- errors (non-local exits) from elisp as Haskell exceptions.
-- 2. Tracks ownership of any produced Emacs values and communicates
-- that to Emacs, so that GC on Emacs side will not make any
-- values in Haskell invalid (funnily enough, this can happen!).
--
-- Parameter 's' serves to make ownership-tracking capabilities possible.
-- It's use is the same as in 'Control.Monad.ST' monad. That is, it creates
-- local threads so that no produced Emacs values can leave past 'runEmacsM'.
newtype EmacsM (s :: k) (a :: Type) = EmacsM { unEmacsM :: ReaderT Environment IO a }
deriving
( Functor
, Applicative
, Monad
, Catch.MonadThrow
, Catch.MonadCatch
, Catch.MonadMask
, MonadFix
, PrimMonad
)
instance MonadInterleave (EmacsM s) where
{-# INLINE unsafeInterleave #-}
unsafeInterleave (EmacsM action) = EmacsM $ do
env <- ask
liftBase $ unsafeInterleave $ runReaderT action env
instance MonadIO (EmacsM s) where
{-# INLINE liftIO #-}
liftIO = EmacsM . lift
instance MonadBase IO (EmacsM s) where
{-# INLINE liftBase #-}
liftBase = EmacsM . lift
instance MonadBaseControl IO (EmacsM s) where
type StM (EmacsM s) a = StM (ReaderT Environment IO) a
{-# INLINE liftBaseWith #-}
liftBaseWith f = EmacsM (liftBaseWith (\runInBase -> f (runInBase . unEmacsM)))
{-# INLINE restoreM #-}
restoreM = EmacsM . restoreM
-- | Execute emacs interaction session using an environment supplied by Emacs.
runEmacsM
:: WithCallStack
=> Env
-> (forall s. EmacsM s a)
-> IO a
runEmacsM eEnv (EmacsM action) =
withNonLocalState $ \eNonLocalState ->
withBuilderCache 8 $ \eArgsCache ->
runReaderT action Environment { eEnv, eNonLocalState, eArgsCache }
{-# INLINE withEnv #-}
withEnv :: (Env -> IO a) -> EmacsM s a
withEnv f = EmacsM $ do
Environment{eEnv} <- ask
liftBase (f eEnv)
{-# INLINE withEnvCache #-}
withEnvCache :: (Env -> BuilderCache (RawValue b) -> IO a) -> EmacsM s a
withEnvCache f = EmacsM $ do
Environment{eEnv, eArgsCache} <- ask
liftBase $ f eEnv (coerceBuilderCache eArgsCache)
handleResult :: EmacsRes EmacsSignal EmacsThrow a -> IO a
handleResult = \case
EmacsSuccess x -> pure x
EmacsExitSignal e -> throwIO e
EmacsExitThrow e -> throwIO e
handleResultNoThrow :: EmacsRes EmacsSignal Void a -> IO a
handleResultNoThrow = \case
EmacsSuccess x -> pure x
EmacsExitSignal e -> throwIO e
EmacsExitThrow e -> absurd e
instance MonadEmacs EmacsM Value where
{-# INLINE makeGlobalRef #-}
makeGlobalRef :: WithCallStack => Value s -> EmacsM s (RawValue 'Pinned)
makeGlobalRef x = withEnv $ \env ->
liftBase $ Env.makeGlobalRef env $ getRawValue x
{-# INLINE freeGlobalRef #-}
freeGlobalRef :: WithCallStack => RawValue 'Pinned -> EmacsM s ()
freeGlobalRef x = withEnv $ \env ->
liftBase $ Env.freeGlobalRef env x
nonLocalExitCheck
:: WithCallStack
=> EmacsM s (FuncallExit ())
nonLocalExitCheck = withEnv $ \env ->
Env.nonLocalExitCheck env >>= Common.unpackEnumFuncallExit
nonLocalExitGet
:: WithCallStack
=> EmacsM s (FuncallExit (Value s, Value s))
nonLocalExitGet = EmacsM $ do
Environment{eEnv, eNonLocalState} <- ask
liftBase $ do
res <- Common.nonLocalExitGet eEnv eNonLocalState
pure $ coerce res
nonLocalExitSignal
:: (WithCallStack, Foldable f)
=> Value s -- ^ Error symbol
-> f (Value s) -- ^ Error data, will be converted to a list as Emacs API expects.
-> EmacsM s ()
nonLocalExitSignal sym errData = withEnvCache $ \env cache ->
Exception.throwIO =<< Common.nonLocalExitSignal cache env callStack (coerce sym) errData'
where
errData' =
foldMap (coerce (PtrBuilder.storable :: RawValue 'Regular -> PtrBuilder.Builder (RawValue 'Regular))) errData
nonLocalExitThrow
:: WithCallStack
=> Value s -- ^ Tag
-> Value s -- ^ Data
-> EmacsM s ()
nonLocalExitThrow tag errData = withEnv $ \env -> do
Env.nonLocalExitThrow env tag' errData'
Exception.throwIO EmacsThrow
{ emacsThrowTag = tag'
, emacsThrowValue = errData'
, emacsThrowOrigin = callStack
}
where
tag' = getRawValue tag
errData' = getRawValue errData
nonLocalExitClear :: WithCallStack => EmacsM s ()
nonLocalExitClear = withEnv Env.nonLocalExitClear
{-# INLINE makeFunction #-}
makeFunction
:: forall req opt rest s. (WithCallStack, EmacsInvocation req opt rest, GetArities req opt rest)
=> (forall s'. EmacsFunction req opt rest EmacsM Value s')
-> Doc.Doc
-> EmacsM s (Value s)
makeFunction emacsFun doc = withEnv $ \env -> do
impl' <- liftBase $ Env.exportToEmacs impl
Doc.useDocAsCString doc $ \doc' -> do
func <- Env.makeFunction env minArity maxArity impl' doc' (castFunPtrToPtr (Env.unRawFunction impl'))
Env.setFunctionFinalizer env func Env.freeHaskellFunPtrWrapped
pure $ Value func
where
(minArity, maxArity) = arities (Proxy @req) (Proxy @opt) (Proxy @rest)
impl :: RawFunctionType 'Unknown ()
impl envPtr nargs argsPtr _extraPtr = do
let env = Env.fromPtr envPtr
Exception.handle (reportAnyErrorToEmacs env) $
Exception.handle (reportEmacsSignalToEmacs env) $
Exception.handle (reportEmacsThrowToEmacs env) $
runEmacsM env $ do
res <- coerce (supplyEmacsArgs (fromIntegral nargs) argsPtr (pure . Value) emacsFun)
-- Force since value may contain exceptions.
liftIO $ evaluate res
{-# INLINE funcall #-}
funcall
:: (WithCallStack, Foldable f)
=> Value s
-> f (Value s)
-> EmacsM s (Value s)
funcall func args = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
coerce . handleResult
=<< Common.checkNonLocalExitFull (coerceBuilderCache eArgsCache) eEnv eNonLocalState
=<< (withPtrLenNonNull (coerceBuilderCache eArgsCache) (foldMap (PtrBuilder.storable . getRawValue) args) $ \n args' ->
Env.funcall eEnv (getRawValue func) (fromIntegral n) args')
{-# INLINE funcallPrimitive #-}
funcallPrimitive
:: (WithCallStack, Foldable f)
=> Value s
-> f (Value s)
-> EmacsM s (Value s)
funcallPrimitive func args = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
coerce . handleResult
=<< Common.checkNonLocalExitFull (coerceBuilderCache eArgsCache) eEnv eNonLocalState
=<< (withPtrLenNonNull (coerceBuilderCache eArgsCache) (foldMap (PtrBuilder.storable . getRawValue) args) $ \n args' ->
Env.funcallPrimitive eEnv (getRawValue func) (fromIntegral n) args')
{-# INLINE funcallPrimitiveUnchecked #-}
funcallPrimitiveUnchecked
:: (WithCallStack, Foldable f)
=> Value s
-> f (Value s)
-> EmacsM s (Value s)
funcallPrimitiveUnchecked func args =
withEnvCache $ \env cache ->
withPtrLenNonNull cache (foldMap (PtrBuilder.storable . getRawValue) args) $ \n args' ->
coerce $ Env.funcallPrimitive @IO env (getRawValue func) (fromIntegral n) args'
intern
:: WithCallStack
=> SymbolName
-> EmacsM s (Value s)
intern sym = withEnv $ \env ->
coerce $ reifySymbolUnknown env sym
typeOf
:: WithCallStack
=> Value s -> EmacsM s (Value s)
typeOf x = withEnv $ \env ->
coerce $ Env.typeOf @IO env (getRawValue x)
{-# INLINE isNotNil #-}
isNotNil :: WithCallStack => Value s -> EmacsM s Bool
isNotNil x = withEnv $ \env ->
Env.isTruthy <$> Env.isNotNil env (getRawValue x)
eq :: Value s -> Value s -> EmacsM s Bool
eq x y = withEnv $ \env ->
Env.isTruthy <$> Env.eq env (getRawValue x) (getRawValue y)
extractWideInteger :: WithCallStack => Value s -> EmacsM s Int64
extractWideInteger x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase
$ handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "ExtractInteger" . fromIntegral
=<< Env.extractInteger eEnv (getRawValue x)
makeWideInteger :: WithCallStack => Int64 -> EmacsM s (Value s)
makeWideInteger x = withEnv $ \env ->
coerce $ Env.makeInteger @IO env (fromIntegral x)
extractDouble :: WithCallStack => Value s -> EmacsM s Double
extractDouble x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase
$ handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "ExtractFloat" . (\(CDouble y) -> y)
=<< Env.extractFloat eEnv (getRawValue x)
makeDouble :: WithCallStack => Double -> EmacsM s (Value s)
makeDouble x = withEnv $ \env ->
coerce $ Env.makeFloat @IO env (CDouble x)
extractText :: WithCallStack => Value s -> EmacsM s Text
extractText x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase
$ handleResultNoThrow
=<< Common.extractText (coerceBuilderCache eArgsCache) eEnv eNonLocalState (getRawValue x)
extractShortByteString :: WithCallStack => Value s -> EmacsM s ShortByteString
extractShortByteString x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase
$ handleResultNoThrow
=<< Common.extractShortByteString (coerceBuilderCache eArgsCache) eEnv eNonLocalState (getRawValue x)
makeString :: WithCallStack => BS.ByteString -> EmacsM s (Value s)
makeString x = withEnv $ \env ->
BSU.unsafeUseAsCStringLen x $ \(pStr, len) ->
coerce $ Env.makeString @IO env pStr (fromIntegral len)
extractUserPtr :: WithCallStack => Value s -> EmacsM s (Ptr a)
extractUserPtr x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase
$ handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "GetUserPtr"
=<< Env.getUserPtr eEnv (getRawValue x)
makeUserPtr
:: WithCallStack
=> FinalizerPtr a
-> Ptr a
-> EmacsM s (Value s)
makeUserPtr fin ptr = withEnv $ \env ->
coerce $ Env.makeUserPtr @IO env fin ptr
assignUserPtr :: WithCallStack => Value s -> Ptr a -> EmacsM s ()
assignUserPtr dest ptr = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
-- callWithResultMayFailSignalWaitSideEffect (SetUserPtr (getRawValue dest) ptr)
liftBase $
handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "SetUserPtr"
=<< Env.setUserPtr eEnv (getRawValue dest) ptr
extractUserPtrFinaliser
:: WithCallStack => Value s -> EmacsM s (FinalizerPtr a)
extractUserPtrFinaliser x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "GetUserPtrFinaliser"
=<< Env.getUserFinaliser eEnv (getRawValue x)
assignUserPtrFinaliser
:: WithCallStack => Value s -> FinalizerPtr a -> EmacsM s ()
assignUserPtrFinaliser x fin = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "SetUserPtrFinaliser"
=<< Env.setUserFinaliser eEnv (getRawValue x) fin
vecGet :: WithCallStack => Value s -> Int -> EmacsM s (Value s)
vecGet vec n = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
coerce . handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "VecGet"
=<< Env.vecGet eEnv (getRawValue vec) (fromIntegral n)
unsafeVecGet :: WithCallStack => Value s -> Int -> EmacsM s (Value s)
unsafeVecGet vec n = EmacsM $ do
Environment{eEnv} <- ask
liftBase $
coerce $
Env.vecGet @IO eEnv (getRawValue vec) (fromIntegral n)
vecSet
:: WithCallStack
=> Value s -- ^ Vector
-> Int -- ^ Index
-> Value s -- ^ New value
-> EmacsM s ()
vecSet vec n x = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "VecSet"
=<< Env.vecSet eEnv (getRawValue vec) (fromIntegral n) (getRawValue x)
vecSize :: WithCallStack => Value s -> EmacsM s Int
vecSize vec = EmacsM $ do
Environment{eEnv, eNonLocalState, eArgsCache} <- ask
liftBase $
handleResultNoThrow
=<< checkNonLocalExitSignal (coerceBuilderCache eArgsCache) eEnv eNonLocalState "VecSize" . fromIntegral
=<< Env.vecSize eEnv (getRawValue vec)
processInput :: WithCallStack => EmacsM s ProcessInput.Result
processInput =
withEnv $ \env -> do
Env.EnumProcessInputResult (CInt x) <- Env.processInput env
case ProcessInput.resultFromNum x of
Nothing ->
throwIO $ mkEmacsInternalError $
"Unknown value of enum emacs_process_input_result" <+> pretty x
Just y -> pure y