module Main where import Control.Monad.Trans.Reader import Control.Monad.Error import Data.List import System.Directory import System.Environment import System.Console.GetOpt import System.Console.Haskeline import Exp.Lex import Exp.Par import Exp.Print import Exp.Abs hiding (NoArg) import Exp.Layout import Exp.ErrM import MTTtoCTT import Concrete import qualified MTT as A import qualified CTT as C import qualified Eval as E type Interpreter a = InputT IO a -- Flag handling data Flag = Debug deriving (Eq,Show) options :: [OptDescr Flag] options = [ Option "d" ["debug"] (NoArg Debug) "Run in debugging mode" ] parseOpts :: [String] -> IO ([Flag],[String]) parseOpts argv = case getOpt Permute options argv of (o,n,[]) -> return (o,n) (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options)) where header = "Usage: cubical [OPTION...] [FILES...]" defaultPrompt :: String defaultPrompt = "> " lexer :: String -> [Token] lexer = resolveLayout True . myLexer showTree :: (Show a, Print a) => a -> IO () showTree tree = do putStrLn $ "\n[Abstract Syntax]\n\n" ++ show tree putStrLn $ "\n[Linearized tree]\n\n" ++ printTree tree main :: IO () main = do args <- getArgs (flags,files) <- parseOpts args runInputT defaultSettings $ runInterpreter (Debug `elem` flags) files -- (not ok,loaded,already loaded defs) -> to load -> (newnotok, newloaded, newdefs) imports :: ([String],[String],[Def]) -> String -> Interpreter ([String],[String],[Def]) imports st@(notok,loaded,defs) f | f `elem` notok = do outputStrLn $ "Looping imports in " ++ f return ([],[],[]) | f `elem` loaded = return st | otherwise = do b <- lift $ doesFileExist f if not b then do outputStrLn ("The file " ++ f ++ " does not exist") return ([],[],[]) else do s <- lift $ readFile f let ts = lexer s case pModule ts of Bad s -> do outputStrLn $ "Parse Failed in file " ++ show f ++ "\n" ++ show s return ([],[],[]) Ok mod@(Module _ imps defs') -> do let imps' = [ unIdent s ++ ".cub" | Import s <- imps ] (notok1,loaded1,def1) <- foldM imports (f:notok,loaded,defs) imps' outputStrLn $ "Parsed file " ++ show f ++ " successfully!" return (notok,f:loaded1,def1 ++ defs') -- The Bool is intended to be whether or not to run in debug mode runInterpreter :: Bool -> [FilePath] -> Interpreter () runInterpreter b fs = case fs of [f] -> do -- parse and type-check files (_,_,defs) <- imports ([],[],[]) f -- Compute all constructors let cs = concat [ [ unIdent n | Sum n _ <- lbls] | DefData _ _ lbls <- defs ] let res = runResolver (local (insertConstrs cs) (resolveDefs defs)) case res of Left err -> do outputStrLn $ "Resolver failed: " ++ err loop [] A.tEmpty Right adefs -> case A.runDefs A.tEmpty adefs of Left err -> do outputStrLn $ "Type checking failed: " ++ err loop [] A.tEmpty Right tenv -> do outputStrLn "File loaded." loop cs tenv _ -> do outputStrLn $ "Exactly one file expected: " ++ show fs loop [] A.tEmpty where loop :: [String] -> A.TEnv -> Interpreter () loop cs tenv@(A.TEnv _ rho _) = do input <- getInputLine defaultPrompt case input of Nothing -> outputStrLn help >> loop cs tenv Just ":q" -> return () Just ":r" -> runInterpreter b fs Just (':':'l':' ':str) -> runInterpreter b (words str) Just (':':'c':'d':' ':str) -> lift (setCurrentDirectory str) >> loop cs tenv Just ":h" -> outputStrLn help >> loop cs tenv Just str -> let ts = lexer str in case pExp ts of Bad err -> outputStrLn ("Parse error: " ++ err) >> loop cs tenv Ok exp -> case runResolver (local (const (Env cs)) (resolveExp exp)) of Left err -> outputStrLn ("Resolver failed: " ++ err) >> loop cs tenv Right body -> case A.runInfer tenv body of Left err -> outputStrLn ("Could not type-check: " ++ err) >> loop cs tenv Right _ -> case translate (A.defs rho body) of Left err -> outputStrLn ("Could not translate to internal syntax: " ++ err) >> loop cs tenv Right t -> let value = E.eval C.Empty t in outputStrLn ("EVAL: " ++ show value) >> loop cs tenv help :: String help = "\nAvailable commands:\n" ++ " infer type and evaluate statement\n" ++ " :q quit\n" ++ " :l loads filename (and resets environment before)\n" ++ " :cd change directory to path\n" ++ " :r reload\n" ++ " :h display this message\n"