The REPL

The driver for this simply invokes all of the compiler in a loop feeding the resulting artifacts to the next iteration. We will use the haskeline library to give us readline interactions for the small REPL.

  1. module Main where
  2. import Parser
  3. import Control.Monad.Trans
  4. import System.Console.Haskeline
  5. process :: String -> IO ()
  6. process line = do
  7. let res = parseToplevel line
  8. case res of
  9. Left err -> print err
  10. Right ex -> mapM_ print ex
  11. main :: IO ()
  12. main = runInputT defaultSettings loop
  13. where
  14. loop = do
  15. minput <- getInputLine "ready> "
  16. case minput of
  17. Nothing -> outputStrLn "Goodbye."
  18. Just input -> (liftIO $ process input) >> loop

In under 100 lines of code, we fully defined our minimal language, including a lexer, parser, and AST builder. With this done, the executable will validate Kaleidoscope code, print out the Haskell representation of the AST, and tell us the position information for any syntax errors. For example, here is a sample interaction:

  1. ready> def foo(x y) x+foo(y, 4.0);
  2. Function "foo" [Var "x",Var "y"] (BinOp Plus (Var "x") (Call "foo" [Var "y",Float 4.0]))
  3. ready> def foo(x y) x+y; y;
  4. Function "foo" [Var "x",Var "y"] (BinOp Plus (Var "x") (Var "y"))
  5. Var "y"
  6. ready> def foo(x y) x+y );
  7. "<stdin>" (line 1, column 18):
  8. unexpected ")"
  9. expecting float, natural, "extern", "def", identifier, "(" or ";"
  10. ready> extern sin(a);
  11. Extern "sin" [Var "a"]
  12. ready> ^D
  13. Goodbye.

There is a lot of room for extension here. You can define new AST nodes, extend the language in many ways, etc. In the next installment, we will describe how to generate LLVM Intermediate Representation (IR) from the AST.