linenoise-0.3.2: src/Linenoise/Repl.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- | Helper functions and transformer to write your own REPLs.
module Linenoise.Repl
( ReplDirective (..)
, ReplT (..)
, replM
, runReplT
) where
import Control.Applicative (Alternative)
import Control.Monad (MonadPlus)
import Control.Monad.Catch (MonadCatch, MonadThrow)
import Control.Monad.Fix (MonadFix)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.IO.Unlift (MonadUnliftIO (..), wrappedWithRunInIO)
import Control.Monad.Reader (MonadReader, ReaderT (..), ask)
import Control.Monad.State.Strict (MonadState (..))
import Control.Monad.Trans (MonadTrans (..))
import Control.Monad.Zip (MonadZip)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Text (Text)
import Linenoise.Unlift (InputResult (..))
import qualified Linenoise.Unlift as Unlift
-- | Basic monad transformer with mutable state that can be used with all "Linenoise.Unlift" functions.
-- You do not have to use this, but it's here to cover most of what you would need without having
-- to roll your own newtype.
newtype ReplT r s m a = ReplT { unReplT :: ReaderT r (ReaderT (IORef s) m) a }
deriving (Functor, Applicative, Monad, MonadIO,
Alternative, MonadPlus, MonadFix, MonadZip,
MonadFail, MonadThrow, MonadCatch,
MonadReader r)
askRef :: Applicative m => ReplT r s m (IORef s)
askRef = ReplT (ReaderT (const (ReaderT pure)))
refReplT :: ReplT r s m a -> r -> IORef s -> m a
refReplT n r = runReaderT (runReaderT (unReplT n) r)
instance MonadTrans (ReplT r s) where
lift = ReplT . lift . lift
instance MonadUnliftIO m => MonadUnliftIO (ReplT r s m) where
withRunInIO run = do
r <- ask
ref <-askRef
wrappedWithRunInIO lift (\n -> refReplT n r ref) run
instance MonadIO m => MonadState s (ReplT r s m) where
get = ReplT (ReaderT (const (ReaderT (liftIO . readIORef))))
put s = ReplT (ReaderT (const (ReaderT (\ref -> liftIO (writeIORef ref s)))))
-- | Run a ReplT.
runReplT :: MonadIO m => ReplT r s m a -> r -> s -> m (a, s)
runReplT n r s = do
ref <- liftIO (newIORef s)
res <- refReplT n r ref
final <- liftIO (readIORef ref)
pure (res, final)
-- | Directive to control voluntary REPL termination.
data ReplDirective
= ReplQuit
| ReplContinue
deriving (Eq, Show)
-- | Run a simple REPL.
replM
:: MonadUnliftIO m
=> ReplDirective -- ^ Directive on interrupt
-> Text -- ^ Prompt
-> (Text -> m ReplDirective) -- ^ Action
-> (Text -> m [Text]) -- ^ Completion
-> m ()
replM onInterrupt prompt action comp = loop where
loop = do
Unlift.setCompletion comp
res <- Unlift.getInputLine prompt
directive <- case res of
InterruptResult -> pure onInterrupt
EofResult -> pure ReplQuit
LineResult line -> do
directive <- action line
Unlift.addHistory line
pure directive
case directive of
ReplContinue -> loop
ReplQuit -> pure ()