Learning Haskell – REPL
Because I am doing a lot of library work and I prefer text-based interfaces I always enjoy having a REPL (Read Evaluate Print Loop) next to my libraries for easy testing.
Here's my first Haskell REPL. This is actually quite nice, I think.
module Cli where
import System.IO
( hSetBuffering
, hSetEcho
, hGetChar
, hGetLine
, hFlush
, stdin
, stdout
, BufferMode(NoBuffering,LineBuffering)
)
import Control.Exception
( Exception
, throwIO
, try
)
import System.Console.ANSI
( getCursorPosition
, setCursorPosition
, clearScreen
)
data Command = One | Two String | Clear | Cancel deriving (Eq, Show)
data UnknownCommandException = UnknownCommand Char deriving Show
instance Exception UnknownCommandException
moveLeft :: IO ()
moveLeft = do
position <- getCursorPosition
case position of
Just (x, y) ->
if y > 0
then setCursorPosition x (y - 1)
else return()
_ -> return ()
getCommand :: String -> IO Command
getCommand prompt = do
hSetBuffering stdin NoBuffering
hSetEcho stdin False
putStr $ prompt <> ": "
hFlush stdout
input <- hGetChar stdin
case input of
'o' -> return One
't' -> readTwo
'0' -> return Clear
c | c == 'c' || c == 'q' -> return Cancel
_ -> throwIO (UnknownCommand input)
readTwo :: IO Command
readTwo = do
moveLeft
putStr "Enter text to reverse: "
hFlush stdout
hSetBuffering stdin LineBuffering
hSetEcho stdin True
input <- hGetLine stdin
return $ Two input
handleCommand :: Command -> IO Bool
handleCommand Cancel = putChar '\n' >> return False
handleCommand (Two str) = do
putStrLn $ reverse str
return True
handleCommand Clear = do
clearScreen
setCursorPosition 0 0
return True
handleCommand cmd = do
putStrLn $ show cmd
return True
handleUnknownCommandException :: IO Bool -> IO Bool
handleUnknownCommandException ioAction = do
result <- try $ ioAction :: IO (Either UnknownCommandException Bool)
case result of
Left err ->
print (err :: UnknownCommandException)
>> return True
Right continue ->
return continue
loop :: IO Bool -> IO ()
loop ioAction =
ioAction >>= \continue ->
if continue then loop ioAction
else return ()
repl :: IO ()
repl = loop $ handleUnknownCommandException $ getCommand "$" >>= handleCommand
This is not necessarily the way you would want to do it. I wanted getCommand to throw an exception and I wanted a clear separation between the elements that form the REPL. I have just started learning Haskell so suggestions are very welcome.