/ghc-7.0.4/ghc/InteractiveUI.hs
Haskell | 1680 lines | 1295 code | 208 blank | 177 comment | 84 complexity | 255d7e3368114dd266f2cf21fb4cb77f MD5 | raw file
Possible License(s): BSD-3-Clause, BSD-2-Clause
Large files files are truncated, but you can click here to view the full file
- {-# OPTIONS -fno-cse #-}
- -- -fno-cse is needed for GLOBAL_VAR's to behave properly
- {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
- -----------------------------------------------------------------------------
- --
- -- GHC Interactive User Interface
- --
- -- (c) The GHC Team 2005-2006
- --
- -----------------------------------------------------------------------------
- module InteractiveUI ( interactiveUI, ghciWelcomeMsg ) where
- #include "HsVersions.h"
- import qualified GhciMonad
- import GhciMonad hiding (runStmt)
- import GhciTags
- import Debugger
- -- The GHC interface
- import qualified GHC hiding (resume, runStmt)
- import GHC ( LoadHowMuch(..), Target(..), TargetId(..),
- TyThing(..), Phase,
- BreakIndex, Resume, SingleStep,
- Ghc, handleSourceError )
- import PprTyThing
- import DynFlags
- import Packages
- -- import PackageConfig
- import UniqFM
- import HscTypes ( handleFlagWarnings )
- import HsImpExp
- import qualified RdrName ( getGRE_NameQualifier_maybes ) -- should this come via GHC?
- import RdrName (RdrName)
- import Outputable hiding (printForUser, printForUserPartWay)
- import Module -- for ModuleEnv
- import Name
- import SrcLoc
- -- Other random utilities
- import Digraph
- import BasicTypes hiding (isTopLevel)
- import Panic hiding (showException)
- import Config
- import StaticFlags
- import Linker
- import Util
- import NameSet
- import Maybes ( orElse, expectJust )
- import FastString
- import Encoding
- import Foreign.C
- #ifndef mingw32_HOST_OS
- import System.Posix hiding (getEnv)
- #else
- import qualified System.Win32
- #endif
- import System.Console.Haskeline as Haskeline
- import qualified System.Console.Haskeline.Encoding as Encoding
- import Control.Monad.Trans
- --import SystemExts
- import Exception hiding (catch, block, unblock)
- -- import Control.Concurrent
- import System.FilePath
- import qualified Data.ByteString.Char8 as BS
- import Data.List
- import Data.Maybe
- import System.Cmd
- import System.Environment
- import System.Exit ( exitWith, ExitCode(..) )
- import System.Directory
- import System.IO
- import System.IO.Error as IO
- import Data.Char
- import Data.Array
- import Control.Monad as Monad
- import Text.Printf
- import Foreign
- import GHC.Exts ( unsafeCoerce# )
- #if __GLASGOW_HASKELL__ >= 611
- import GHC.IO.Exception ( IOErrorType(InvalidArgument) )
- import GHC.IO.Handle ( hFlushAll )
- #else
- import GHC.IOBase ( IOErrorType(InvalidArgument) )
- #endif
- import GHC.TopHandler
- import Data.IORef ( IORef, readIORef, writeIORef )
- -----------------------------------------------------------------------------
- ghciWelcomeMsg :: String
- ghciWelcomeMsg = "GHCi, version " ++ cProjectVersion ++
- ": http://www.haskell.org/ghc/ :? for help"
- cmdName :: Command -> String
- cmdName (n,_,_) = n
- GLOBAL_VAR(macros_ref, [], [Command])
- builtin_commands :: [Command]
- builtin_commands = [
- -- Hugs users are accustomed to :e, so make sure it doesn't overlap
- ("?", keepGoing help, noCompletion),
- ("add", keepGoingPaths addModule, completeFilename),
- ("abandon", keepGoing abandonCmd, noCompletion),
- ("break", keepGoing breakCmd, completeIdentifier),
- ("back", keepGoing backCmd, noCompletion),
- ("browse", keepGoing' (browseCmd False), completeModule),
- ("browse!", keepGoing' (browseCmd True), completeModule),
- ("cd", keepGoing' changeDirectory, completeFilename),
- ("check", keepGoing' checkModule, completeHomeModule),
- ("continue", keepGoing continueCmd, noCompletion),
- ("cmd", keepGoing cmdCmd, completeExpression),
- ("ctags", keepGoing createCTagsWithLineNumbersCmd, completeFilename),
- ("ctags!", keepGoing createCTagsWithRegExesCmd, completeFilename),
- ("def", keepGoing (defineMacro False), completeExpression),
- ("def!", keepGoing (defineMacro True), completeExpression),
- ("delete", keepGoing deleteCmd, noCompletion),
- ("edit", keepGoing editFile, completeFilename),
- ("etags", keepGoing createETagsFileCmd, completeFilename),
- ("force", keepGoing forceCmd, completeExpression),
- ("forward", keepGoing forwardCmd, noCompletion),
- ("help", keepGoing help, noCompletion),
- ("history", keepGoing historyCmd, noCompletion),
- ("info", keepGoing' info, completeIdentifier),
- ("kind", keepGoing' kindOfType, completeIdentifier),
- ("load", keepGoingPaths loadModule_, completeHomeModuleOrFile),
- ("list", keepGoing' listCmd, noCompletion),
- ("module", keepGoing setContext, completeSetModule),
- ("main", keepGoing runMain, completeFilename),
- ("print", keepGoing printCmd, completeExpression),
- ("quit", quit, noCompletion),
- ("reload", keepGoing' reloadModule, noCompletion),
- ("run", keepGoing runRun, completeFilename),
- ("set", keepGoing setCmd, completeSetOptions),
- ("show", keepGoing showCmd, completeShowOptions),
- ("sprint", keepGoing sprintCmd, completeExpression),
- ("step", keepGoing stepCmd, completeIdentifier),
- ("steplocal", keepGoing stepLocalCmd, completeIdentifier),
- ("stepmodule",keepGoing stepModuleCmd, completeIdentifier),
- ("type", keepGoing' typeOfExpr, completeExpression),
- ("trace", keepGoing traceCmd, completeExpression),
- ("undef", keepGoing undefineMacro, completeMacro),
- ("unset", keepGoing unsetOptions, completeSetOptions)
- ]
- -- We initialize readline (in the interactiveUI function) to use
- -- word_break_chars as the default set of completion word break characters.
- -- This can be overridden for a particular command (for example, filename
- -- expansion shouldn't consider '/' to be a word break) by setting the third
- -- entry in the Command tuple above.
- --
- -- NOTE: in order for us to override the default correctly, any custom entry
- -- must be a SUBSET of word_break_chars.
- word_break_chars :: String
- word_break_chars = let symbols = "!#$%&*+/<=>?@\\^|-~"
- specials = "(),;[]`{}"
- spaces = " \t\n"
- in spaces ++ specials ++ symbols
- flagWordBreakChars :: String
- flagWordBreakChars = " \t\n"
- keepGoing :: (String -> GHCi ()) -> (String -> InputT GHCi Bool)
- keepGoing a str = keepGoing' (lift . a) str
- keepGoing' :: Monad m => (String -> m ()) -> String -> m Bool
- keepGoing' a str = a str >> return False
- keepGoingPaths :: ([FilePath] -> InputT GHCi ()) -> (String -> InputT GHCi Bool)
- keepGoingPaths a str
- = do case toArgs str of
- Left err -> Encoding.encode err >>= liftIO . BS.hPutStrLn stderr
- Right args -> a args
- return False
- shortHelpText :: String
- shortHelpText = "use :? for help.\n"
- helpText :: String
- helpText =
- " Commands available from the prompt:\n" ++
- "\n" ++
- " <statement> evaluate/run <statement>\n" ++
- " : repeat last command\n" ++
- " :{\\n ..lines.. \\n:}\\n multiline command\n" ++
- " :add [*]<module> ... add module(s) to the current target set\n" ++
- " :browse[!] [[*]<mod>] display the names defined by module <mod>\n" ++
- " (!: more details; *: all top-level names)\n" ++
- " :cd <dir> change directory to <dir>\n" ++
- " :cmd <expr> run the commands returned by <expr>::IO String\n" ++
- " :ctags[!] [<file>] create tags file for Vi (default: \"tags\")\n" ++
- " (!: use regex instead of line number)\n" ++
- " :def <cmd> <expr> define a command :<cmd>\n" ++
- " :edit <file> edit file\n" ++
- " :edit edit last module\n" ++
- " :etags [<file>] create tags file for Emacs (default: \"TAGS\")\n" ++
- " :help, :? display this list of commands\n" ++
- " :info [<name> ...] display information about the given names\n" ++
- " :kind <type> show the kind of <type>\n" ++
- " :load [*]<module> ... load module(s) and their dependents\n" ++
- " :main [<arguments> ...] run the main function with the given arguments\n" ++
- " :module [+/-] [*]<mod> ... set the context for expression evaluation\n" ++
- " :quit exit GHCi\n" ++
- " :reload reload the current module set\n" ++
- " :run function [<arguments> ...] run the function with the given arguments\n" ++
- " :type <expr> show the type of <expr>\n" ++
- " :undef <cmd> undefine user-defined command :<cmd>\n" ++
- " :!<command> run the shell command <command>\n" ++
- "\n" ++
- " -- Commands for debugging:\n" ++
- "\n" ++
- " :abandon at a breakpoint, abandon current computation\n" ++
- " :back go back in the history (after :trace)\n" ++
- " :break [<mod>] <l> [<col>] set a breakpoint at the specified location\n" ++
- " :break <name> set a breakpoint on the specified function\n" ++
- " :continue resume after a breakpoint\n" ++
- " :delete <number> delete the specified breakpoint\n" ++
- " :delete * delete all breakpoints\n" ++
- " :force <expr> print <expr>, forcing unevaluated parts\n" ++
- " :forward go forward in the history (after :back)\n" ++
- " :history [<n>] after :trace, show the execution history\n" ++
- " :list show the source code around current breakpoint\n" ++
- " :list identifier show the source code for <identifier>\n" ++
- " :list [<module>] <line> show the source code around line number <line>\n" ++
- " :print [<name> ...] prints a value without forcing its computation\n" ++
- " :sprint [<name> ...] simplifed version of :print\n" ++
- " :step single-step after stopping at a breakpoint\n"++
- " :step <expr> single-step into <expr>\n"++
- " :steplocal single-step within the current top-level binding\n"++
- " :stepmodule single-step restricted to the current module\n"++
- " :trace trace after stopping at a breakpoint\n"++
- " :trace <expr> evaluate <expr> with tracing on (see :history)\n"++
- "\n" ++
- " -- Commands for changing settings:\n" ++
- "\n" ++
- " :set <option> ... set options\n" ++
- " :set args <arg> ... set the arguments returned by System.getArgs\n" ++
- " :set prog <progname> set the value returned by System.getProgName\n" ++
- " :set prompt <prompt> set the prompt used in GHCi\n" ++
- " :set editor <cmd> set the command used for :edit\n" ++
- " :set stop [<n>] <cmd> set the command to run when a breakpoint is hit\n" ++
- " :unset <option> ... unset options\n" ++
- "\n" ++
- " Options for ':set' and ':unset':\n" ++
- "\n" ++
- " +r revert top-level expressions after each evaluation\n" ++
- " +s print timing/memory stats after each evaluation\n" ++
- " +t print type after evaluation\n" ++
- " -<flags> most GHC command line flags can also be set here\n" ++
- " (eg. -v2, -fglasgow-exts, etc.)\n" ++
- " for GHCi-specific flags, see User's Guide,\n"++
- " Flag reference, Interactive-mode options\n" ++
- "\n" ++
- " -- Commands for displaying information:\n" ++
- "\n" ++
- " :show bindings show the current bindings made at the prompt\n" ++
- " :show breaks show the active breakpoints\n" ++
- " :show context show the breakpoint context\n" ++
- " :show modules show the currently loaded modules\n" ++
- " :show packages show the currently active package flags\n" ++
- " :show languages show the currently active language flags\n" ++
- " :show <setting> show value of <setting>, which is one of\n" ++
- " [args, prog, prompt, editor, stop]\n" ++
- "\n"
- findEditor :: IO String
- findEditor = do
- getEnv "EDITOR"
- `IO.catch` \_ -> do
- #if mingw32_HOST_OS
- win <- System.Win32.getWindowsDirectory
- return (win </> "notepad.exe")
- #else
- return ""
- #endif
- foreign import ccall unsafe "rts_isProfiled" isProfiled :: IO CInt
- default_progname, default_prompt, default_stop :: String
- default_progname = "<interactive>"
- default_prompt = "%s> "
- default_stop = ""
- default_args :: [String]
- default_args = []
- interactiveUI :: [(FilePath, Maybe Phase)] -> Maybe [String]
- -> Ghc ()
- interactiveUI srcs maybe_exprs = do
- -- although GHCi compiles with -prof, it is not usable: the byte-code
- -- compiler and interpreter don't work with profiling. So we check for
- -- this up front and emit a helpful error message (#2197)
- i <- liftIO $ isProfiled
- when (i /= 0) $
- ghcError (InstallationError "GHCi cannot be used when compiled with -prof")
- -- HACK! If we happen to get into an infinite loop (eg the user
- -- types 'let x=x in x' at the prompt), then the thread will block
- -- on a blackhole, and become unreachable during GC. The GC will
- -- detect that it is unreachable and send it the NonTermination
- -- exception. However, since the thread is unreachable, everything
- -- it refers to might be finalized, including the standard Handles.
- -- This sounds like a bug, but we don't have a good solution right
- -- now.
- _ <- liftIO $ newStablePtr stdin
- _ <- liftIO $ newStablePtr stdout
- _ <- liftIO $ newStablePtr stderr
- -- Initialise buffering for the *interpreted* I/O system
- initInterpBuffering
- liftIO $ when (isNothing maybe_exprs) $ do
- -- Only for GHCi (not runghc and ghc -e):
- -- Turn buffering off for the compiled program's stdout/stderr
- turnOffBuffering
- -- Turn buffering off for GHCi's stdout
- hFlush stdout
- hSetBuffering stdout NoBuffering
- -- We don't want the cmd line to buffer any input that might be
- -- intended for the program, so unbuffer stdin.
- hSetBuffering stdin NoBuffering
- #if defined(mingw32_HOST_OS) && __GLASGOW_HASKELL__ >= 611
- -- On Unix, stdin will use the locale encoding. The IO library
- -- doesn't do this on Windows (yet), so for now we use UTF-8,
- -- for consistency with GHC 6.10 and to make the tests work.
- hSetEncoding stdin utf8
- #endif
- -- initial context is just the Prelude
- prel_mod <- GHC.lookupModule (GHC.mkModuleName "Prelude") Nothing
- GHC.setContext [] [(prel_mod, Nothing)]
- default_editor <- liftIO $ findEditor
- startGHCi (runGHCi srcs maybe_exprs)
- GHCiState{ progname = default_progname,
- args = default_args,
- prompt = default_prompt,
- stop = default_stop,
- editor = default_editor,
- -- session = session,
- options = [],
- prelude = prel_mod,
- break_ctr = 0,
- breaks = [],
- tickarrays = emptyModuleEnv,
- last_command = Nothing,
- cmdqueue = [],
- remembered_ctx = [],
- ghc_e = isJust maybe_exprs
- }
- return ()
- withGhcAppData :: (FilePath -> IO a) -> IO a -> IO a
- withGhcAppData right left = do
- either_dir <- IO.try (getAppUserDataDirectory "ghc")
- case either_dir of
- Right dir ->
- do createDirectoryIfMissing False dir `catchIO` \_ -> return ()
- right dir
- _ -> left
- runGHCi :: [(FilePath, Maybe Phase)] -> Maybe [String] -> GHCi ()
- runGHCi paths maybe_exprs = do
- let
- read_dot_files = not opt_IgnoreDotGhci
- current_dir = return (Just ".ghci")
- app_user_dir = liftIO $ withGhcAppData
- (\dir -> return (Just (dir </> "ghci.conf")))
- (return Nothing)
- home_dir = do
- either_dir <- liftIO $ IO.try (getEnv "HOME")
- case either_dir of
- Right home -> return (Just (home </> ".ghci"))
- _ -> return Nothing
- canonicalizePath' :: FilePath -> IO (Maybe FilePath)
- canonicalizePath' fp = liftM Just (canonicalizePath fp)
- `catchIO` \_ -> return Nothing
- sourceConfigFile :: FilePath -> GHCi ()
- sourceConfigFile file = do
- exists <- liftIO $ doesFileExist file
- when exists $ do
- dir_ok <- liftIO $ checkPerms (getDirectory file)
- file_ok <- liftIO $ checkPerms file
- when (dir_ok && file_ok) $ do
- either_hdl <- liftIO $ IO.try (openFile file ReadMode)
- case either_hdl of
- Left _e -> return ()
- -- NOTE: this assumes that runInputT won't affect the terminal;
- -- can we assume this will always be the case?
- -- This would be a good place for runFileInputT.
- Right hdl ->
- do runInputTWithPrefs defaultPrefs defaultSettings $
- runCommands $ fileLoop hdl
- liftIO (hClose hdl `IO.catch` \_ -> return ())
- where
- getDirectory f = case takeDirectory f of "" -> "."; d -> d
- when (read_dot_files) $ do
- mcfgs0 <- sequence [ current_dir, app_user_dir, home_dir ]
- mcfgs <- liftIO $ mapM canonicalizePath' (catMaybes mcfgs0)
- mapM_ sourceConfigFile $ nub $ catMaybes mcfgs
- -- nub, because we don't want to read .ghci twice if the
- -- CWD is $HOME.
- -- Perform a :load for files given on the GHCi command line
- -- When in -e mode, if the load fails then we want to stop
- -- immediately rather than going on to evaluate the expression.
- when (not (null paths)) $ do
- ok <- ghciHandle (\e -> do showException e; return Failed) $
- -- TODO: this is a hack.
- runInputTWithPrefs defaultPrefs defaultSettings $ do
- let (filePaths, phases) = unzip paths
- filePaths' <- mapM (Encoding.decode . BS.pack) filePaths
- loadModule (zip filePaths' phases)
- when (isJust maybe_exprs && failed ok) $
- liftIO (exitWith (ExitFailure 1))
- -- if verbosity is greater than 0, or we are connected to a
- -- terminal, display the prompt in the interactive loop.
- is_tty <- liftIO (hIsTerminalDevice stdin)
- dflags <- getDynFlags
- let show_prompt = verbosity dflags > 0 || is_tty
- case maybe_exprs of
- Nothing ->
- do
- -- enter the interactive loop
- runGHCiInput $ runCommands $ nextInputLine show_prompt is_tty
- Just exprs -> do
- -- just evaluate the expression we were given
- enqueueCommands exprs
- let handle e = do st <- getGHCiState
- -- flush the interpreter's stdout/stderr on exit (#3890)
- flushInterpBuffers
- -- Jump through some hoops to get the
- -- current progname in the exception text:
- -- <progname>: <exception>
- liftIO $ withProgName (progname st)
- -- this used to be topHandlerFastExit, see #2228
- $ topHandler e
- runInputTWithPrefs defaultPrefs defaultSettings $ do
- runCommands' handle (return Nothing)
- -- and finally, exit
- liftIO $ when (verbosity dflags > 0) $ putStrLn "Leaving GHCi."
- runGHCiInput :: InputT GHCi a -> GHCi a
- runGHCiInput f = do
- histFile <- liftIO $ withGhcAppData (\dir -> return (Just (dir </> "ghci_history")))
- (return Nothing)
- let settings = setComplete ghciCompleteWord
- $ defaultSettings {historyFile = histFile}
- runInputT settings f
- nextInputLine :: Bool -> Bool -> InputT GHCi (Maybe String)
- nextInputLine show_prompt is_tty
- | is_tty = do
- prompt <- if show_prompt then lift mkPrompt else return ""
- getInputLine prompt
- | otherwise = do
- when show_prompt $ lift mkPrompt >>= liftIO . putStr
- fileLoop stdin
- -- NOTE: We only read .ghci files if they are owned by the current user,
- -- and aren't world writable. Otherwise, we could be accidentally
- -- running code planted by a malicious third party.
- -- Furthermore, We only read ./.ghci if . is owned by the current user
- -- and isn't writable by anyone else. I think this is sufficient: we
- -- don't need to check .. and ../.. etc. because "." always refers to
- -- the same directory while a process is running.
- checkPerms :: String -> IO Bool
- #ifdef mingw32_HOST_OS
- checkPerms _ =
- return True
- #else
- checkPerms name =
- handleIO (\_ -> return False) $ do
- st <- getFileStatus name
- me <- getRealUserID
- if fileOwner st /= me then do
- putStrLn $ "WARNING: " ++ name ++ " is owned by someone else, IGNORING!"
- return False
- else do
- let mode = System.Posix.fileMode st
- if (groupWriteMode == (mode `intersectFileModes` groupWriteMode))
- || (otherWriteMode == (mode `intersectFileModes` otherWriteMode))
- then do
- putStrLn $ "*** WARNING: " ++ name ++
- " is writable by someone else, IGNORING!"
- return False
- else return True
- #endif
- fileLoop :: MonadIO m => Handle -> InputT m (Maybe String)
- fileLoop hdl = do
- l <- liftIO $ IO.try $ hGetLine hdl
- case l of
- Left e | isEOFError e -> return Nothing
- | InvalidArgument <- etype -> return Nothing
- | otherwise -> liftIO $ ioError e
- where etype = ioeGetErrorType e
- -- treat InvalidArgument in the same way as EOF:
- -- this can happen if the user closed stdin, or
- -- perhaps did getContents which closes stdin at
- -- EOF.
- Right l -> return (Just l)
- mkPrompt :: GHCi String
- mkPrompt = do
- (toplevs,exports) <- GHC.getContext
- resumes <- GHC.getResumeContext
- -- st <- getGHCiState
- context_bit <-
- case resumes of
- [] -> return empty
- r:_ -> do
- let ix = GHC.resumeHistoryIx r
- if ix == 0
- then return (brackets (ppr (GHC.resumeSpan r)) <> space)
- else do
- let hist = GHC.resumeHistory r !! (ix-1)
- span <- GHC.getHistorySpan hist
- return (brackets (ppr (negate ix) <> char ':'
- <+> ppr span) <> space)
- let
- dots | _:rs <- resumes, not (null rs) = text "... "
- | otherwise = empty
- modules_bit =
- -- ToDo: maybe...
- -- let (btoplevs, bexports) = fromMaybe ([],[]) (remembered_ctx st) in
- -- hsep (map (\m -> text "!*" <> ppr (GHC.moduleName m)) btoplevs) <+>
- -- hsep (map (\m -> char '!' <> ppr (GHC.moduleName m)) bexports) <+>
- hsep (map (\m -> char '*' <> ppr (GHC.moduleName m)) toplevs) <+>
- hsep (map (ppr . GHC.moduleName) (nub (map fst exports)))
- deflt_prompt = dots <> context_bit <> modules_bit
- f ('%':'s':xs) = deflt_prompt <> f xs
- f ('%':'%':xs) = char '%' <> f xs
- f (x:xs) = char x <> f xs
- f [] = empty
- --
- st <- getGHCiState
- return (showSDoc (f (prompt st)))
- queryQueue :: GHCi (Maybe String)
- queryQueue = do
- st <- getGHCiState
- case cmdqueue st of
- [] -> return Nothing
- c:cs -> do setGHCiState st{ cmdqueue = cs }
- return (Just c)
- runCommands :: InputT GHCi (Maybe String) -> InputT GHCi ()
- runCommands = runCommands' handler
- runCommands' :: (SomeException -> GHCi Bool) -- Exception handler
- -> InputT GHCi (Maybe String) -> InputT GHCi ()
- runCommands' eh getCmd = do
- b <- ghandle (\e -> case fromException e of
- Just UserInterrupt -> return False
- _ -> case fromException e of
- Just ghc_e ->
- do liftIO (print (ghc_e :: GhcException))
- return True
- _other ->
- liftIO (Exception.throwIO e))
- (runOneCommand eh getCmd)
- if b then return () else runCommands' eh getCmd
- runOneCommand :: (SomeException -> GHCi Bool) -> InputT GHCi (Maybe String)
- -> InputT GHCi Bool
- runOneCommand eh getCmd = do
- mb_cmd <- noSpace (lift queryQueue)
- mb_cmd <- maybe (noSpace getCmd) (return . Just) mb_cmd
- case mb_cmd of
- Nothing -> return True
- Just c -> ghciHandle (lift . eh) $
- handleSourceError printErrorAndKeepGoing
- (doCommand c)
- where
- printErrorAndKeepGoing err = do
- GHC.printExceptionAndWarnings err
- return False
- noSpace q = q >>= maybe (return Nothing)
- (\c->case removeSpaces c of
- "" -> noSpace q
- ":{" -> multiLineCmd q
- c -> return (Just c) )
- multiLineCmd q = do
- st <- lift getGHCiState
- let p = prompt st
- lift $ setGHCiState st{ prompt = "%s| " }
- mb_cmd <- collectCommand q ""
- lift $ getGHCiState >>= \st->setGHCiState st{ prompt = p }
- return mb_cmd
- -- we can't use removeSpaces for the sublines here, so
- -- multiline commands are somewhat more brittle against
- -- fileformat errors (such as \r in dos input on unix),
- -- we get rid of any extra spaces for the ":}" test;
- -- we also avoid silent failure if ":}" is not found;
- -- and since there is no (?) valid occurrence of \r (as
- -- opposed to its String representation, "\r") inside a
- -- ghci command, we replace any such with ' ' (argh:-(
- collectCommand q c = q >>=
- maybe (liftIO (ioError collectError))
- (\l->if removeSpaces l == ":}"
- then return (Just $ removeSpaces c)
- else collectCommand q (c ++ "\n" ++ map normSpace l))
- where normSpace '\r' = ' '
- normSpace c = c
- -- QUESTION: is userError the one to use here?
- collectError = userError "unterminated multiline command :{ .. :}"
- doCommand (':' : cmd) = specialCommand cmd
- doCommand stmt = do _ <- timeIt $ lift $ runStmt stmt GHC.RunToCompletion
- return False
- enqueueCommands :: [String] -> GHCi ()
- enqueueCommands cmds = do
- st <- getGHCiState
- setGHCiState st{ cmdqueue = cmds ++ cmdqueue st }
- runStmt :: String -> SingleStep -> GHCi Bool
- runStmt stmt step
- | null (filter (not.isSpace) stmt)
- = return False
- | "import " `isPrefixOf` stmt
- = do newContextCmd (Import stmt); return False
- | otherwise
- = do
- #if __GLASGOW_HASKELL__ >= 611
- -- In the new IO library, read handles buffer data even if the Handle
- -- is set to NoBuffering. This causes problems for GHCi where there
- -- are really two stdin Handles. So we flush any bufferred data in
- -- GHCi's stdin Handle here (only relevant if stdin is attached to
- -- a file, otherwise the read buffer can't be flushed).
- _ <- liftIO $ IO.try $ hFlushAll stdin
- #endif
- result <- withFlattenedDynflags $ GhciMonad.runStmt stmt step
- afterRunStmt (const True) result
- --afterRunStmt :: GHC.RunResult -> GHCi Bool
- -- False <=> the statement failed to compile
- afterRunStmt :: (SrcSpan -> Bool) -> GHC.RunResult -> GHCi Bool
- afterRunStmt _ (GHC.RunException e) = throw e
- afterRunStmt step_here run_result = do
- resumes <- GHC.getResumeContext
- case run_result of
- GHC.RunOk names -> do
- show_types <- isOptionSet ShowType
- when show_types $ printTypeOfNames names
- GHC.RunBreak _ names mb_info
- | isNothing mb_info ||
- step_here (GHC.resumeSpan $ head resumes) -> do
- mb_id_loc <- toBreakIdAndLocation mb_info
- let breakCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
- if (null breakCmd)
- then printStoppedAtBreakInfo (head resumes) names
- else enqueueCommands [breakCmd]
- -- run the command set with ":set stop <cmd>"
- st <- getGHCiState
- enqueueCommands [stop st]
- return ()
- | otherwise -> resume step_here GHC.SingleStep >>=
- afterRunStmt step_here >> return ()
- _ -> return ()
- flushInterpBuffers
- liftIO installSignalHandlers
- b <- isOptionSet RevertCAFs
- when b revertCAFs
- return (case run_result of GHC.RunOk _ -> True; _ -> False)
- toBreakIdAndLocation ::
- Maybe GHC.BreakInfo -> GHCi (Maybe (Int, BreakLocation))
- toBreakIdAndLocation Nothing = return Nothing
- toBreakIdAndLocation (Just info) = do
- let mod = GHC.breakInfo_module info
- nm = GHC.breakInfo_number info
- st <- getGHCiState
- return $ listToMaybe [ id_loc | id_loc@(_,loc) <- breaks st,
- breakModule loc == mod,
- breakTick loc == nm ]
- printStoppedAtBreakInfo :: Resume -> [Name] -> GHCi ()
- printStoppedAtBreakInfo resume names = do
- printForUser $ ptext (sLit "Stopped at") <+>
- ppr (GHC.resumeSpan resume)
- -- printTypeOfNames session names
- let namesSorted = sortBy compareNames names
- tythings <- catMaybes `liftM` mapM GHC.lookupName namesSorted
- docs <- pprTypeAndContents [id | AnId id <- tythings]
- printForUserPartWay docs
- printTypeOfNames :: [Name] -> GHCi ()
- printTypeOfNames names
- = mapM_ (printTypeOfName ) $ sortBy compareNames names
- compareNames :: Name -> Name -> Ordering
- n1 `compareNames` n2 = compareWith n1 `compare` compareWith n2
- where compareWith n = (getOccString n, getSrcSpan n)
- printTypeOfName :: Name -> GHCi ()
- printTypeOfName n
- = do maybe_tything <- GHC.lookupName n
- case maybe_tything of
- Nothing -> return ()
- Just thing -> printTyThing thing
- data MaybeCommand = GotCommand Command | BadCommand | NoLastCommand
- specialCommand :: String -> InputT GHCi Bool
- specialCommand ('!':str) = lift $ shellEscape (dropWhile isSpace str)
- specialCommand str = do
- let (cmd,rest) = break isSpace str
- maybe_cmd <- lift $ lookupCommand cmd
- case maybe_cmd of
- GotCommand (_,f,_) -> f (dropWhile isSpace rest)
- BadCommand ->
- do liftIO $ hPutStr stdout ("unknown command ':" ++ cmd ++ "'\n"
- ++ shortHelpText)
- return False
- NoLastCommand ->
- do liftIO $ hPutStr stdout ("there is no last command to perform\n"
- ++ shortHelpText)
- return False
- lookupCommand :: String -> GHCi (MaybeCommand)
- lookupCommand "" = do
- st <- getGHCiState
- case last_command st of
- Just c -> return $ GotCommand c
- Nothing -> return NoLastCommand
- lookupCommand str = do
- mc <- liftIO $ lookupCommand' str
- st <- getGHCiState
- setGHCiState st{ last_command = mc }
- return $ case mc of
- Just c -> GotCommand c
- Nothing -> BadCommand
- lookupCommand' :: String -> IO (Maybe Command)
- lookupCommand' ":" = return Nothing
- lookupCommand' str' = do
- macros <- readIORef macros_ref
- let{ (str, cmds) = case str' of
- ':' : rest -> (rest, builtin_commands)
- _ -> (str', macros ++ builtin_commands) }
- -- look for exact match first, then the first prefix match
- return $ case [ c | c <- cmds, str == cmdName c ] of
- c:_ -> Just c
- [] -> case [ c | c@(s,_,_) <- cmds, str `isPrefixOf` s ] of
- [] -> Nothing
- c:_ -> Just c
- getCurrentBreakSpan :: GHCi (Maybe SrcSpan)
- getCurrentBreakSpan = do
- resumes <- GHC.getResumeContext
- case resumes of
- [] -> return Nothing
- (r:_) -> do
- let ix = GHC.resumeHistoryIx r
- if ix == 0
- then return (Just (GHC.resumeSpan r))
- else do
- let hist = GHC.resumeHistory r !! (ix-1)
- span <- GHC.getHistorySpan hist
- return (Just span)
- getCurrentBreakModule :: GHCi (Maybe Module)
- getCurrentBreakModule = do
- resumes <- GHC.getResumeContext
- case resumes of
- [] -> return Nothing
- (r:_) -> do
- let ix = GHC.resumeHistoryIx r
- if ix == 0
- then return (GHC.breakInfo_module `liftM` GHC.resumeBreakInfo r)
- else do
- let hist = GHC.resumeHistory r !! (ix-1)
- return $ Just $ GHC.getHistoryModule hist
- -----------------------------------------------------------------------------
- -- Commands
- noArgs :: GHCi () -> String -> GHCi ()
- noArgs m "" = m
- noArgs _ _ = liftIO $ putStrLn "This command takes no arguments"
- help :: String -> GHCi ()
- help _ = liftIO (putStr helpText)
- info :: String -> InputT GHCi ()
- info "" = ghcError (CmdLineError "syntax: ':i <thing-you-want-info-about>'")
- info s = handleSourceError GHC.printExceptionAndWarnings $
- withFlattenedDynflags $ do
- { let names = words s
- ; dflags <- getDynFlags
- ; let pefas = dopt Opt_PrintExplicitForalls dflags
- ; mapM_ (infoThing pefas) names }
- where
- infoThing pefas str = do
- names <- GHC.parseName str
- mb_stuffs <- mapM GHC.getInfo names
- let filtered = filterOutChildren (\(t,_f,_i) -> t) (catMaybes mb_stuffs)
- unqual <- GHC.getPrintUnqual
- liftIO $ putStrLn $ showSDocForUser unqual $
- vcat (intersperse (text "") $
- map (pprInfo pefas) filtered)
- -- Filter out names whose parent is also there Good
- -- example is '[]', which is both a type and data
- -- constructor in the same type
- filterOutChildren :: (a -> TyThing) -> [a] -> [a]
- filterOutChildren get_thing xs
- = filterOut has_parent xs
- where
- all_names = mkNameSet (map (getName . get_thing) xs)
- has_parent x = case pprTyThingParent_maybe (get_thing x) of
- Just p -> getName p `elemNameSet` all_names
- Nothing -> False
- pprInfo :: PrintExplicitForalls -> (TyThing, Fixity, [GHC.Instance]) -> SDoc
- pprInfo pefas (thing, fixity, insts)
- = pprTyThingInContextLoc pefas thing
- $$ show_fixity fixity
- $$ vcat (map GHC.pprInstance insts)
- where
- show_fixity fix
- | fix == GHC.defaultFixity = empty
- | otherwise = ppr fix <+> ppr (GHC.getName thing)
- runMain :: String -> GHCi ()
- runMain s = case toArgs s of
- Left err -> liftIO (hPutStrLn stderr err)
- Right args ->
- withFlattenedDynflags $ do
- dflags <- getDynFlags
- case mainFunIs dflags of
- Nothing -> doWithArgs args "main"
- Just f -> doWithArgs args f
- runRun :: String -> GHCi ()
- runRun s = case toCmdArgs s of
- Left err -> liftIO (hPutStrLn stderr err)
- Right (cmd, args) -> doWithArgs args cmd
- doWithArgs :: [String] -> String -> GHCi ()
- doWithArgs args cmd = enqueueCommands ["System.Environment.withArgs " ++
- show args ++ " (" ++ cmd ++ ")"]
- addModule :: [FilePath] -> InputT GHCi ()
- addModule files = do
- lift revertCAFs -- always revert CAFs on load/add.
- files <- mapM expandPath files
- targets <- mapM (\m -> GHC.guessTarget m Nothing) files
- -- remove old targets with the same id; e.g. for :add *M
- mapM_ GHC.removeTarget [ tid | Target tid _ _ <- targets ]
- mapM_ GHC.addTarget targets
- prev_context <- GHC.getContext
- ok <- trySuccess $ GHC.load LoadAllTargets
- afterLoad ok False prev_context
- changeDirectory :: String -> InputT GHCi ()
- changeDirectory "" = do
- -- :cd on its own changes to the user's home directory
- either_dir <- liftIO $ IO.try getHomeDirectory
- case either_dir of
- Left _e -> return ()
- Right dir -> changeDirectory dir
- changeDirectory dir = do
- graph <- GHC.getModuleGraph
- when (not (null graph)) $
- do liftIO $ putStrLn "Warning: changing directory causes all loaded modules to be unloaded,"
- liftIO $ putStrLn "because the search path has changed."
- prev_context <- GHC.getContext
- GHC.setTargets []
- _ <- GHC.load LoadAllTargets
- lift $ setContextAfterLoad prev_context False []
- GHC.workingDirectoryChanged
- dir <- expandPath dir
- liftIO $ setCurrentDirectory dir
- trySuccess :: GHC.GhcMonad m => m SuccessFlag -> m SuccessFlag
- trySuccess act =
- handleSourceError (\e -> do GHC.printExceptionAndWarnings e
- return Failed) $ do
- act
- editFile :: String -> GHCi ()
- editFile str =
- do file <- if null str then chooseEditFile else return str
- st <- getGHCiState
- let cmd = editor st
- when (null cmd)
- $ ghcError (CmdLineError "editor not set, use :set editor")
- _ <- liftIO $ system (cmd ++ ' ':file)
- return ()
- -- The user didn't specify a file so we pick one for them.
- -- Our strategy is to pick the first module that failed to load,
- -- or otherwise the first target.
- --
- -- XXX: Can we figure out what happened if the depndecy analysis fails
- -- (e.g., because the porgrammeer mistyped the name of a module)?
- -- XXX: Can we figure out the location of an error to pass to the editor?
- -- XXX: if we could figure out the list of errors that occured during the
- -- last load/reaload, then we could start the editor focused on the first
- -- of those.
- chooseEditFile :: GHCi String
- chooseEditFile =
- do let hasFailed x = fmap not $ GHC.isLoaded $ GHC.ms_mod_name x
- graph <- GHC.getModuleGraph
- failed_graph <- filterM hasFailed graph
- let order g = flattenSCCs $ GHC.topSortModuleGraph True g Nothing
- pick xs = case xs of
- x : _ -> GHC.ml_hs_file (GHC.ms_location x)
- _ -> Nothing
- case pick (order failed_graph) of
- Just file -> return file
- Nothing ->
- do targets <- GHC.getTargets
- case msum (map fromTarget targets) of
- Just file -> return file
- Nothing -> ghcError (CmdLineError "No files to edit.")
-
- where fromTarget (GHC.Target (GHC.TargetFile f _) _ _) = Just f
- fromTarget _ = Nothing -- when would we get a module target?
- defineMacro :: Bool{-overwrite-} -> String -> GHCi ()
- defineMacro _ (':':_) =
- liftIO $ putStrLn "macro name cannot start with a colon"
- defineMacro overwrite s = do
- let (macro_name, definition) = break isSpace s
- macros <- liftIO (readIORef macros_ref)
- let defined = map cmdName macros
- if (null macro_name)
- then if null defined
- then liftIO $ putStrLn "no macros defined"
- else liftIO $ putStr ("the following macros are defined:\n" ++
- unlines defined)
- else do
- if (not overwrite && macro_name `elem` defined)
- then ghcError (CmdLineError
- ("macro '" ++ macro_name ++ "' is already defined"))
- else do
- let filtered = [ cmd | cmd <- macros, cmdName cmd /= macro_name ]
- -- give the expression a type signature, so we can be sure we're getting
- -- something of the right type.
- let new_expr = '(' : definition ++ ") :: String -> IO String"
- -- compile the expression
- handleSourceError (\e -> GHC.printExceptionAndWarnings e) $
- withFlattenedDynflags $ do
- hv <- GHC.compileExpr new_expr
- liftIO (writeIORef macros_ref --
- (filtered ++ [(macro_name, lift . runMacro hv, noCompletion)]))
- runMacro :: GHC.HValue{-String -> IO String-} -> String -> GHCi Bool
- runMacro fun s = do
- str <- liftIO ((unsafeCoerce# fun :: String -> IO String) s)
- -- make sure we force any exceptions in the result, while we are still
- -- inside the exception handler for commands:
- seqList str (return ())
- enqueueCommands (lines str)
- return False
- undefineMacro :: String -> GHCi ()
- undefineMacro str = mapM_ undef (words str)
- where undef macro_name = do
- cmds <- liftIO (readIORef macros_ref)
- if (macro_name `notElem` map cmdName cmds)
- then ghcError (CmdLineError
- ("macro '" ++ macro_name ++ "' is not defined"))
- else do
- liftIO (writeIORef macros_ref (filter ((/= macro_name) . cmdName) cmds))
- cmdCmd :: String -> GHCi ()
- cmdCmd str = do
- let expr = '(' : str ++ ") :: IO String"
- handleSourceError (\e -> GHC.printExceptionAndWarnings e) $
- withFlattenedDynflags $ do
- hv <- GHC.compileExpr expr
- cmds <- liftIO $ (unsafeCoerce# hv :: IO String)
- enqueueCommands (lines cmds)
- return ()
- loadModuleName :: GHC.GhcMonad m => ImportDecl RdrName -> m Module
- loadModuleName = flip GHC.findModule Nothing . unLoc . ideclName
- loadModule :: [(FilePath, Maybe Phase)] -> InputT GHCi SuccessFlag
- loadModule fs = timeIt (loadModule' fs)
- loadModule_ :: [FilePath] -> InputT GHCi ()
- loadModule_ fs = loadModule (zip fs (repeat Nothing)) >> return ()
- loadModule' :: [(FilePath, Maybe Phase)] -> InputT GHCi SuccessFlag
- loadModule' files = do
- prev_context <- GHC.getContext
- -- unload first
- _ <- GHC.abandonAll
- lift discardActiveBreakPoints
- GHC.setTargets []
- _ <- GHC.load LoadAllTargets
- let (filenames, phases) = unzip files
- exp_filenames <- mapM expandPath filenames
- let files' = zip exp_filenames phases
- targets <- mapM (uncurry GHC.guessTarget) files'
- -- NOTE: we used to do the dependency anal first, so that if it
- -- fails we didn't throw away the current set of modules. This would
- -- require some re-working of the GHC interface, so we'll leave it
- -- as a ToDo for now.
- GHC.setTargets targets
- doLoad False prev_context LoadAllTargets
- checkModule :: String -> InputT GHCi ()
- checkModule m = do
- let modl = GHC.mkModuleName m
- prev_context <- GHC.getContext
- ok <- handleSourceError (\e -> GHC.printExceptionAndWarnings e >> return False) $ do
- r <- GHC.typecheckModule =<< GHC.parseModule =<< GHC.getModSummary modl
- liftIO $ putStrLn $ showSDoc $
- case GHC.moduleInfo r of
- cm | Just scope <- GHC.modInfoTopLevelScope cm ->
- let
- (local,global) = ASSERT( all isExternalName scope )
- partition ((== modl) . GHC.moduleName . GHC.nameModule) scope
- in
- (text "global names: " <+> ppr global) $$
- (text "local names: " <+> ppr local)
- _ -> empty
- return True
- afterLoad (successIf ok) False prev_context
- reloadModule :: String -> InputT GHCi ()
- reloadModule m = do
- prev_context <- GHC.getContext
- _ <- doLoad True prev_context $
- if null m then LoadAllTargets
- else LoadUpTo (GHC.mkModuleName m)
- return ()
- doLoad :: Bool -> ([Module],[(Module, Maybe (ImportDecl RdrName))]) -> LoadHowMuch -> InputT GHCi SuccessFlag
- doLoad retain_context prev_context howmuch = do
- -- turn off breakpoints before we load: we can't turn them off later, because
- -- the ModBreaks will have gone away.
- lift discardActiveBreakPoints
- ok <- trySuccess $ GHC.load howmuch
- afterLoad ok retain_context prev_context
- return ok
- afterLoad :: SuccessFlag -> Bool -> ([Module],[(Module, Maybe (ImportDecl RdrName))]) -> InputT GHCi ()
- afterLoad ok retain_context prev_context = do
- lift revertCAFs -- always revert CAFs on load.
- lift discardTickArrays
- loaded_mod_summaries <- getLoadedModules
- let loaded_mods = map GHC.ms_mod loaded_mod_summaries
- loaded_mod_names = map GHC.moduleName loaded_mods
- modulesLoadedMsg ok loaded_mod_names
- withFlattenedDynflags $ lift $ setContextAfterLoad prev_context retain_context loaded_mod_summaries
- setContextAfterLoad :: ([Module],[(Module, Maybe (ImportDecl RdrName))]) -> Bool -> [GHC.ModSummary] -> GHCi ()
- setContextAfterLoad prev keep_ctxt [] = do
- prel_mod <- getPrelude
- setContextKeepingPackageModules prev keep_ctxt ([], [(prel_mod, Nothing)])
- setContextAfterLoad prev keep_ctxt ms = do
- -- load a target if one is available, otherwise load the topmost module.
- targets <- GHC.getTargets
- case [ m | Just m <- map (findTarget ms) targets ] of
- [] ->
- let graph' = flattenSCCs (GHC.topSortModuleGraph True ms Nothing) in
- load_this (last graph')
- (m:_) ->
- load_this m
- where
- findTarget ms t
- = case filter (`matches` t) ms of
- [] -> Nothing
- (m:_) -> Just m
- summary `matches` Target (TargetModule m) _ _
- = GHC.ms_mod_name summary == m
- summary `matches` Target (TargetFile f _) _ _
- | Just f' <- GHC.ml_hs_file (GHC.ms_location summary) = f == f'
- _ `matches` _
- = False
- load_this summary | m <- GHC.ms_mod summary = do
- b <- GHC.moduleIsInterpreted m
- if b then setContextKeepingPackageModules prev keep_ctxt ([m], [])
- else do
- prel_mod <- getPrelude
- setContextKeepingPackageModules prev keep_ctxt ([],[(prel_mod,Nothing),(m,Nothing)])
- -- | Keep any package modules (except Prelude) when changing the context.
- setContextKeepingPackageModules
- :: ([Module],[(Module, Maybe (ImportDecl RdrName))]) -- previous context
- -> Bool -- re-execute :module commands
- -> ([Module],[(Module, Maybe (ImportDecl RdrName))]) -- new context
- -> GHCi ()
- setContextKeepingPackageModules prev_context keep_ctxt (as,bs) = do
- let (_,bs0) = prev_context
- prel_mod <- getPrelude
- -- filter everything, not just lefts
- let pkg_modules = filter ((\p -> not (isHomeModule p) && p /= prel_mod) . fst) bs0
- let bs1 = if null as then nubBy sameFst ((prel_mod,Nothing) : bs) else bs
- GHC.setContext as (nubBy sameFst (bs1 ++ pkg_modules))
- if keep_ctxt
- then do
- st <- getGHCiState
- mapM_ (playCtxtCmd False) (remembered_ctx st)
- else do
- st <- getGHCiState
- setGHCiState st{ remembered_ctx = [] }
- isHomeModule :: Module -> Bool
- isHomeModule mod = GHC.modulePackageId mod == mainPackageId
- sameFst :: (Module, Maybe (ImportDecl RdrName)) -> (Module, Maybe (ImportDecl RdrName)) -> Bool
- sameFst x y = fst x == fst y
- modulesLoadedMsg :: SuccessFlag -> [ModuleName] -> InputT GHCi ()
- modulesLoadedMsg ok mods = do
- dflags <- getDynFlags
- when (verbosity dflags > 0) $ do
- let mod_commas
- | null mods = text "none."
- | otherwise = hsep (
- punctuate comma (map ppr mods)) <> text "."
- case ok of
- Failed ->
- liftIO $ putStrLn $ showSDoc (text "Failed, modules loaded: " <> mod_commas)
- Succeeded ->
- liftIO $ putStrLn $ showSDoc (text "Ok, modules loaded: " <> mod_commas)
- typeOfExpr :: String -> InputT GHCi ()
- typeOfExpr str
- = handleSourceError (\e -> GHC.printExceptionAndWarnings e)
- $ withFlattenedDynflags
- $ do
- ty <- GHC.exprType str
- dflags <- getDynFlags
- let pefas = dopt Opt_PrintExplicitForalls dflags
- printForUser $ sep [text str, nest 2 (dcolon <+> pprTypeForUser pefas ty)]
- kindOfType :: String -> InputT GHCi ()
- kindOfType str
- = handleSourceError (\e -> GHC.printExceptionAndWarnings e)
- $ withFlattenedDynflags
- $ do
- ty <- GHC.typeKind str
- printForUser $ text str <+> dcolon <+> ppr ty
- quit :: String -> InputT GHCi Bool
- quit _ = return True
- shellEscape :: String -> GHCi Bool
- shellEscape str = liftIO (system str >> return False)
- withFlattenedDynflags :: GHC.GhcMonad m => m a -> m a
- withFlattenedDynflags m
- = do dflags <- GHC.getSessionDynFlags
- gbracket (GHC.setSessionDynFlags dflags)
- (\_ -> GHC.setSessionDynFlags dflags)
- (\_ -> m)
- -----------------------------------------------------------------------------
- -- Browsing a module's contents
- browseCmd :: Bool -> String -> InputT GHCi ()
- browseCmd bang m =
- case words m of
- ['*':s] | looksLikeModuleName s -> do
- m <- lift $ wantInterpretedModule s
- browseModule bang m False
- [s] | looksLikeModuleName s -> do
- m <- lift $ lookupModule s
- browseModule bang m True
- [] -> do
- (as,bs) <- GHC.getContext
- -- Guess which module the user wants to browse. Pick
- -- modules that are interpreted first. The most
- -- recently-added module occurs last, it seems.
- case (as,bs) of
- (as@(_:_), _) -> browseModule bang (last as) True
- ([], bs@(_:_)) -> browseModule bang (fst (last bs)) True
- ([], []) -> ghcError (CmdLineError ":browse: no current module")
- _ -> ghcError (CmdLineError "syntax: :browse <module>")
- -- without bang, show items in context of their parents and omit children
- -- with bang, show class methods and data constructors separately, and
- -- indicate import modules, to aid qualifying unqualified names
- -- with sorted, sort items alphabetically
- browseModule :: Bool -> Module -> Bool -> InputT GHCi ()
- browseModule bang modl exports_only = withFlattenedDynflags $ do
- -- :browse! reports qualifiers wrt current context
- current_unqual <- GHC.getPrintUnqual
- -- Temporarily set the context to the module we're interested in,
- -- just so we can get an appropriate PrintUnqualified
- (as,bs) <- GHC.getContext
- prel_mod <- lift getPrelude
- if exports_only then GHC.setContext [] [(prel_mod,Nothing), (modl,Nothing)]
- else GHC.setContext [modl] []
- target_unqual <- GHC.getPrintUnqual
- GHC.setContext as bs
- let unqual = if bang then current_unqual else target_unqual
- mb_mod_info <- GHC.getModuleInfo modl
- case mb_mod_info of
- Nothing -> ghcError (CmdLineError ("unknown module: " ++
- GHC.moduleNameString (GHC.moduleName modl)))
- Just mod_info -> do
- dflags <- getDynFlags
- let names
- | exports_only = GHC.modInfoExports mod_info
- | otherwise = GHC.modInfoTopLevelScope mod_info
- `orElse` []
- …
Large files files are truncated, but you can click here to view the full file