Read Eval Print Loop Repeat
Galileo alone had risked asserting the truth about our planet, and this made him a great man… His was a genuine career as I understand it. Yevgeny Yevtushenko
Repl.hs defines the code we use for our REPL loop. Our strategy will be to have the user enter text, parse, interpret, then display the result, then allow the user to enter another line of text. If an exception is thrown, we will catch and display the exception, then return to our normal mode.
type Repl a = InputT IO a mainLoop :: IO () mainLoop = runInputT defaultSettings repl
Here we define out
Repl type using
InputT from the mtl library to wrap
mainLoop will run the REPL with default settings, and is the top-level function exported by Repl.hs. that will be loaded into Main.hs and used to create the executable.
repl :: Repl () repl = do minput <- getInputLine "Repl> " case minput of Nothing -> outputStrLn "Goodbye." Just input -> liftIO (process input) >> repl --Just input -> (liftIO $ processToAST input) >> repl
repl is a recursive function which will get a line of texted wrapped in the
Maybe context, if
Nothing then the function terminates, if
Just input then we evaluate the text using the helper function
process. Commented out is an alternative processing line, which will display the abstract syntax tree, very useful for debugging.
process :: String -> IO () process str = do res <- safeExec $ evalText $ T.pack str either putStrLn return res processToAST :: String -> IO () processToAST str = print $ runParseTest $ T.pack str
process takes a
String input, packs it into
Text, evaluates it, then handles errors using
safeExec. Recall that
safeExec return type
IO (Either String a). This catches thrown exceptions and allows for the REPL to display the error, then allow the user to subsequently enter a fixed expression.
The REPL here is pretty basic, nothing fancy, just connect the user to the underlying interpreter and let them play! It takes input, runs the evaluator, then displays either the computed
LispVal or the
[ Understanding Check ]
Every input is evaluated independently, that is, with a fresh
EnvCtx. Can you figure out a way to thread the
EnvCtx, so variables bound in one inputted expression can be used in subsequent inputs?
REPL keywords like “:quit” “:help” “:clear” or “:ast” can be entered to allow for the REPL to do different tasks, like show the AST, information about commands, or quit out. Write a quick
Parsec parser to extract these keywords.