ivo/app/Main.hs

173 lines
5.7 KiB
Haskell
Raw Normal View History

module Main (main) where
import Command
import Flags
import MonadApp
2021-03-26 12:31:55 -07:00
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
2021-03-26 14:55:23 -07:00
_ <- 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