ivo/app/Main.hs

173 lines
5.7 KiB
Haskell

module Main (main) where
import Command
import Flags
import MonadApp
import Ivo
import Control.Exception (IOException, catch)
import Control.Monad (when, zipWithM)
import Control.Monad.Except (throwError, liftEither)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Loops (whileJust_)
import Control.Monad.State (gets, modify')
import Control.Monad.Trans (lift)
import Data.HashMap.Strict qualified as HM
import Data.Maybe (isJust, fromJust)
import Data.Text qualified as T
import Data.Text.IO (readFile)
import Prelude hiding (readFile)
import System.Console.Haskeline (getInputLine)
main :: IO ()
main = do
action <- getAction
case action of
PrintHelp -> putStrLn usageMessage
PrintVersion -> putStrLn "Ivo 0.1.0.0"
Interpreter ProgramOpts { pInterpreterOpts, loadFiles, mainFile } -> do
-- read the file contents first so we can print errors right away
mMainContents <- mapM readArgFile mainFile
filesContents <- mapM readArgFile loadFiles
runAppM pInterpreterOpts do
-- then parse
mMain <- mapM (parseProgramHandleErrors $ fromJust mainFile) mMainContents
case mMain of
Just mainAST
| any (\(name, _, _) -> name == "main") mainAST -> pure ()
| otherwise -> throwError "File passed to `-c` does not contain a main function."
Nothing -> pure ()
files <- zipWithM parseProgramHandleErrors loadFiles filesContents
-- and only finally interpret
mapM_ loadFile files
maybe repl runProgram mMain
where
-- | When reading the file contents of a file passed as an argument,
-- we want to print usage information and exit if the file can't be opened.
readArgFile :: FilePath -> IO Text
readArgFile file = readFile file `catch` handleException
where
handleException :: IOException -> IO a
handleException _ = ioError $ userError $
"Could not open file: " ++ file ++ "\n" ++ usageMessage
repl :: AppM ()
repl = lift $ whileJust_ (fmap T.pack <$> lift (getInputLine ">> ")) \inputText ->
handleErrors do
input <- parseCommandOrTopLevel inputText
either runCommand runTopLevel input
parseCommandOrTopLevel :: Text -> AppM (Either Command TopLevelAST)
parseCommandOrTopLevel input = do
mCmd <- liftParseError $ parseCommand input
case mCmd of
Nothing -> Right <$> liftParseError (parseTopLevel input)
Just cmd -> pure $ Left cmd
parseProgramHandleErrors :: FilePath -> Text -> AppM ProgramAST
parseProgramHandleErrors filename = liftParseError . parse programParser filename
liftParseError :: Either ParseError a -> AppM a
liftParseError result = case result of
Left err -> throwError $ T.pack $ show err
Right x -> pure x
runProgram :: ProgramAST -> AppM ()
runProgram program = do
loadFile program
runDeclOrExpr (Right (Var "main"))
loadFile :: ProgramAST -> AppM ()
loadFile = mapM_ (\(name, ty, e) -> define name ty $ decl2check name e)
runTopLevel :: TopLevelAST -> AppM ()
runTopLevel = mapM_ runDeclOrExpr
runDeclOrExpr :: Either Declaration AST -> AppM ()
runDeclOrExpr (Left (name, ty, body)) = do
defs <- gets definitions
let expr = substitute defs $ decl2check name body
_ <- typecheckDecl ty name expr
define name ty expr
runDeclOrExpr (Right exprAST) = do
defs <- gets definitions
let expr = substitute defs $ ast2check exprAST
_ <- typecheckExpr expr
value <- execute expr
liftInput $ outputTextLn $ unparseEval value
pure ()
typecheckDecl :: Maybe Type -> Text -> CheckExpr -> AppM Scheme
typecheckDecl ty = typecheck ty . Just
typecheckExpr :: CheckExpr-> AppM Scheme
typecheckExpr = typecheck Nothing Nothing
typecheck :: Maybe Type -> Maybe Text -> CheckExpr -> AppM Scheme
typecheck tann decl expr = do
defs <- gets definitions
let type_ = maybe infer check tann $ substitute defs expr
case type_ of
Left err -> throwError $ "Typecheck error: " <> err
Right t -> do
printTypeB <- gets $ shouldPrintType isDecl . interpreterOpts
when printTypeB $ outputStderrLn $ prefix <> unparseScheme t
pure t
where
isDecl = isJust decl
prefix = case decl of
Just name -> name <> " : "
Nothing -> ": "
execute :: CheckExpr -> AppM EvalExpr
execute checkExpr = do
defs <- gets definitions
let expr = check2eval $ substitute defs checkExpr
traceOpts <- gets (traceOpts . interpreterOpts)
case traceOpts of
TraceOff -> do
let value = eval expr
pure value
TraceLocal -> do
let (value, trace) = evalTrace expr
mapM_ (outputStderrLn . unparseEval) trace
pure value
TraceGlobal -> do
let (value, trace) = evalTraceGlobal expr
mapM_ (outputStderrLn . unparseEval) trace
pure value
define :: Text -> Maybe Type -> CheckExpr -> AppM ()
define name ty expr = do
_ <- typecheckDecl ty name expr
modify' \appState ->
let expr' = substitute (definitions appState) expr
in appState { definitions = HM.insert name expr' $ definitions appState }
modifyInterpreterOpts :: (InterpreterOpts -> InterpreterOpts) -> AppM ()
modifyInterpreterOpts f =
modify' \app -> app { interpreterOpts = f (interpreterOpts app) }
runCommand :: Command -> AppM ()
runCommand (Trace traceOpts) =
modifyInterpreterOpts \opts -> opts { traceOpts }
runCommand (PrintType printTypeOpts) =
modifyInterpreterOpts \opts -> opts { printTypeOpts }
runCommand Clear = modify' \app -> app { definitions = HM.empty }
runCommand (Load filePath) = do
input <- safeReadFile
program <- liftParseError $ parse programParser filePath input
runProgram program
where
safeReadFile :: AppM Text
safeReadFile = liftEither =<< liftIO (
(Right <$> readFile filePath)
`catch` handleException)
handleException :: IOException -> IO (Either Text Text)
handleException = pure . Left . T.pack . show