{-# LANGUAGE DataKinds #-} {-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} module Main (main) where import Prelude () import Prelude.Compat import Data.Monoid ((<>)) import Data.Version (showVersion) import Control.Applicative (many) import Control.Monad import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Class import Control.Monad.Trans.Except (ExceptT(..), runExceptT) import Control.Monad.Trans.State.Strict (StateT, evalStateT) import Control.Monad.Trans.Reader (ReaderT, runReaderT) import qualified Language.PureScript as P import Language.PureScript.Interactive import qualified Options.Applicative as Opts import qualified Paths_purescript as Paths import System.Console.Haskeline import System.Exit import System.FilePath.Glob (glob) -- | Command line options data PSCiOptions = PSCiOptions { psciMultiLineMode :: Bool , psciInputFile :: [FilePath] , psciInputNodeFlags :: [String] } multiLineMode :: Opts.Parser Bool multiLineMode = Opts.switch $ Opts.long "multi-line-mode" <> Opts.short 'm' <> Opts.help "Run in multi-line mode (use ^D to terminate commands)" inputFile :: Opts.Parser FilePath inputFile = Opts.strArgument $ Opts.metavar "FILE" <> Opts.help "Optional .purs files to load on start" nodeFlagsFlag :: Opts.Parser [String] nodeFlagsFlag = Opts.option parser $ Opts.long "node-opts" <> Opts.metavar "NODE_OPTS" <> Opts.value [] <> Opts.help "Flags to pass to node, separated by spaces" where parser = words <$> Opts.str psciOptions :: Opts.Parser PSCiOptions psciOptions = PSCiOptions <$> multiLineMode <*> many inputFile <*> nodeFlagsFlag version :: Opts.Parser (a -> a) version = Opts.abortOption (Opts.InfoMsg (showVersion Paths.version)) $ Opts.long "version" <> Opts.help "Show the version number" <> Opts.hidden getOpt :: IO PSCiOptions getOpt = Opts.execParser opts where opts = Opts.info (version <*> Opts.helper <*> psciOptions) infoModList infoModList = Opts.fullDesc <> headerInfo <> footerInfo headerInfo = Opts.header "psci - Interactive mode for PureScript" footerInfo = Opts.footer $ "psci " ++ showVersion Paths.version -- | Parses the input and returns either a command, or an error as a 'String'. getCommand :: forall m. MonadException m => Bool -> InputT m (Either String (Maybe Command)) getCommand singleLineMode = handleInterrupt (return (Right Nothing)) $ do firstLine <- withInterrupt $ getInputLine "> " case firstLine of Nothing -> return (Right (Just QuitPSCi)) -- Ctrl-D when input is empty Just "" -> return (Right Nothing) Just s | singleLineMode || head s == ':' -> return . fmap Just $ parseCommand s Just s -> fmap Just . parseCommand <$> go [s] where go :: [String] -> InputT m String go ls = maybe (return . unlines $ reverse ls) (go . (:ls)) =<< getInputLine " " -- | Get command line options and drop into the REPL main :: IO () main = getOpt >>= loop where loop :: PSCiOptions -> IO () loop PSCiOptions{..} = do inputFiles <- concat <$> traverse glob psciInputFile e <- runExceptT $ do modules <- ExceptT (loadAllModules inputFiles) unless (supportModuleIsDefined (map snd modules)) . liftIO $ do putStrLn supportModuleMessage exitFailure (externs, env) <- ExceptT . runMake . make $ modules return (modules, externs, env) case e of Left errs -> putStrLn (P.prettyPrintMultipleErrors P.defaultPPEOptions errs) >> exitFailure Right (modules, externs, env) -> do historyFilename <- getHistoryFilename let settings = defaultSettings { historyFile = Just historyFilename } initialState = PSCiState [] [] (zip (map snd modules) externs) config = PSCiConfig inputFiles psciInputNodeFlags env runner = flip runReaderT config . flip evalStateT initialState . runInputT (setComplete completion settings) putStrLn prologueMessage runner go where go :: InputT (StateT PSCiState (ReaderT PSCiConfig IO)) () go = do c <- getCommand (not psciMultiLineMode) case c of Left err -> outputStrLn err >> go Right Nothing -> go Right (Just QuitPSCi) -> outputStrLn quitMessage Right (Just c') -> do handleInterrupt (outputStrLn "Interrupted.") (withInterrupt (lift (handleCommand c'))) go