tinyapp-0.1.0.0: src/TinyApp/Repl.hs
-- | Build REPL apps
module TinyApp.Repl where
import Control.Exception
import Control.Monad
import System.IO
-- | Signals whether the REPL should continue asking input from the user or exit.
data ContinueExit = Continue | Exit
deriving (Eq, Show)
-- | Defines a REPL application that is not allows to perform arbitrary IO while executing.
data Sandbox state = Sandbox
{ -- | Initial state
initialize :: state,
-- | The prompt to show. It can depend on the state
prompt :: state -> String,
-- | Process the user input given the current state
-- Returns the next state, the output and whether to continue or not the program
update :: String -> state -> (state, String, ContinueExit)
}
-- | Executes the REPL application.
runRepl :: Sandbox s -> IO ()
runRepl = Control.Monad.void . runRepl'
-- | Executes the REPL application returning its final state.
runRepl' :: forall s. Sandbox s -> IO s
runRepl' config =
let go :: s -> IO s
go state = do
System.IO.putStr (config.prompt state)
-- Since the prompt does not finish in a newline we force a flush right after
hFlush stdout
-- When Control-D an exception is thrown
input <- Control.Exception.catch @IOException (Just <$> System.IO.getLine) (\_ -> pure Nothing)
case input of
Nothing -> pure state
Just input' -> do
let (state', output, continue) = config.update input' state
System.IO.putStrLn output
case continue of
Continue ->
go state'
Exit ->
pure state'
in do
go config.initialize