ghci-haskeline (empty) → 0.1
raw patch · 7 files changed
+3474/−0 lines, 7 filesdep +Win32dep +arraydep +basesetup-changed
Dependencies added: Win32, array, base, bytestring, directory, filepath, ghc, ghc-paths, haskeline, mtl, process, unix
Files
- GhciHaskeline/GhciMonad.hs +413/−0
- GhciHaskeline/GhciTags.hs +140/−0
- GhciHaskeline/InteractiveUI.hs +2226/−0
- LICENSE +31/−0
- Main.hs +608/−0
- Setup.hs +6/−0
- ghci-haskeline.cabal +50/−0
+ GhciHaskeline/GhciMonad.hs view
@@ -0,0 +1,413 @@+{-# OPTIONS -fno-cse #-}+-- -fno-cse is needed for GLOBAL_VAR's to behave properly++-----------------------------------------------------------------------------+--+-- Monadery code used in InteractiveUI+--+-- (c) The GHC Team 2005-2006+--+-----------------------------------------------------------------------------++module GhciHaskeline.GhciMonad where+++import qualified GHC+import Outputable hiding (printForUser, printForUserPartWay)+import qualified Outputable+import qualified Pretty+import Panic hiding (showException)+import Util+import DynFlags+import HscTypes hiding (liftIO)+import SrcLoc+import Module+import ObjLink+import Linker+import StaticFlags+import qualified MonadUtils as MonadUtils+import qualified ErrUtils as ErrUtils++import Exception+import Data.Maybe+import Numeric+import Data.Array+import Data.Char+import Data.Int ( Int64 )+import Data.IORef+import Data.List+import System.CPUTime+import System.Directory+import System.Environment+import System.IO+import Control.Monad as Monad+import GHC.Exts++import System.Console.Haskeline (CompletionFunc, InputT)+import qualified System.Console.Haskeline as Haskeline+import System.Console.Haskeline.Encoding+import Control.Monad.Trans as Trans+import qualified Data.ByteString as B++-- copied from HsVersions.h++#define GLOBAL_VAR(name,value,ty) \+{-# NOINLINE name #-}; \+name :: IORef (ty); \+name = Util.global (value);++++-----------------------------------------------------------------------------+-- GHCi monad++type Command = (String, String -> InputT GHCi Bool, CompletionFunc GHCi)++data GHCiState = GHCiState+ { + progname :: String,+ args :: [String],+ prompt :: String,+ editor :: String,+ stop :: String,+ options :: [GHCiOption],+ prelude :: GHC.Module,+ break_ctr :: !Int,+ breaks :: ![(Int, BreakLocation)],+ tickarrays :: ModuleEnv TickArray,+ -- tickarrays caches the TickArray for loaded modules,+ -- so that we don't rebuild it each time the user sets+ -- a breakpoint.+ -- ":" at the GHCi prompt repeats the last command, so we+ -- remember is here:+ last_command :: Maybe Command,+ cmdqueue :: [String],+ remembered_ctx :: [(CtxtCmd, [String], [String])],+ -- we remember the :module commands between :loads, so that+ -- on a :reload we can replay them. See bugs #2049,+ -- \#1873, #1360. Previously we tried to remember modules that+ -- were supposed to be in the context but currently had errors,+ -- but this was complicated. Just replaying the :module commands+ -- seems to be the right thing.+ virtual_path :: FilePath,+ ghc_e :: Bool -- True if this is 'ghc -e' (or runghc)+ }++data CtxtCmd+ = SetContext+ | AddModules+ | RemModules++type TickArray = Array Int [(BreakIndex,SrcSpan)]++data GHCiOption + = ShowTiming -- show time/allocs after evaluation+ | ShowType -- show the type of expressions+ | RevertCAFs -- revert CAFs after every evaluation+ deriving Eq++data BreakLocation+ = BreakLocation+ { breakModule :: !GHC.Module+ , breakLoc :: !SrcSpan+ , breakTick :: {-# UNPACK #-} !Int+ , onBreakCmd :: String+ } ++instance Eq BreakLocation where+ loc1 == loc2 = breakModule loc1 == breakModule loc2 &&+ breakTick loc1 == breakTick loc2++prettyLocations :: [(Int, BreakLocation)] -> SDoc+prettyLocations [] = text "No active breakpoints." +prettyLocations locs = vcat $ map (\(i, loc) -> brackets (int i) <+> ppr loc) $ reverse $ locs++instance Outputable BreakLocation where+ ppr loc = (ppr $ breakModule loc) <+> ppr (breakLoc loc) <+>+ if null (onBreakCmd loc)+ then empty+ else doubleQuotes (text (onBreakCmd loc))++recordBreak :: BreakLocation -> GHCi (Bool{- was already present -}, Int)+recordBreak brkLoc = do+ st <- getGHCiState+ let oldActiveBreaks = breaks st + -- don't store the same break point twice+ case [ nm | (nm, loc) <- oldActiveBreaks, loc == brkLoc ] of+ (nm:_) -> return (True, nm)+ [] -> do+ let oldCounter = break_ctr st+ newCounter = oldCounter + 1+ setGHCiState $ st { break_ctr = newCounter,+ breaks = (oldCounter, brkLoc) : oldActiveBreaks+ }+ return (False, oldCounter)++newtype GHCi a = GHCi { unGHCi :: IORef GHCiState -> Ghc a }++reflectGHCi :: (Session, IORef GHCiState) -> GHCi a -> IO a+reflectGHCi (s, gs) m = unGhc (unGHCi m gs) s++reifyGHCi :: ((Session, IORef GHCiState) -> IO a) -> GHCi a+reifyGHCi f = GHCi f'+ where+ -- f' :: IORef GHCiState -> Ghc a+ f' gs = reifyGhc (f'' gs)+ -- f'' :: IORef GHCiState -> Session -> IO a+ f'' gs s = f (s, gs)++startGHCi :: GHCi a -> GHCiState -> Ghc a+startGHCi g state = do ref <- liftIO $ newIORef state; unGHCi g ref++instance Monad GHCi where+ (GHCi m) >>= k = GHCi $ \s -> m s >>= \a -> unGHCi (k a) s+ return a = GHCi $ \_ -> return a++instance Functor GHCi where+ fmap f m = m >>= return . f++ghciHandleGhcException :: (GhcException -> GHCi a) -> GHCi a -> GHCi a+ghciHandleGhcException = handleGhcException++getGHCiState :: GHCi GHCiState+getGHCiState = GHCi $ \r -> liftIO $ readIORef r+setGHCiState :: GHCiState -> GHCi ()+setGHCiState s = GHCi $ \r -> liftIO $ writeIORef r s++liftGhc :: Ghc a -> GHCi a+liftGhc m = GHCi $ \_ -> m++instance MonadUtils.MonadIO GHCi where+ liftIO = liftGhc . MonadUtils.liftIO++instance Trans.MonadIO Ghc where+ liftIO = MonadUtils.liftIO++instance GhcMonad GHCi where+ setSession s' = liftGhc $ setSession s'+ getSession = liftGhc $ getSession++instance GhcMonad (InputT GHCi) where+ setSession = lift . setSession+ getSession = lift getSession++instance MonadUtils.MonadIO (InputT GHCi) where+ liftIO = Trans.liftIO++instance WarnLogMonad (InputT GHCi) where+ setWarnings = lift . setWarnings+ getWarnings = lift getWarnings++instance ExceptionMonad GHCi where+ gcatch m h = GHCi $ \r -> unGHCi m r `gcatch` (\e -> unGHCi (h e) r)+ gblock (GHCi m) = GHCi $ \r -> gblock (m r)+ gunblock (GHCi m) = GHCi $ \r -> gunblock (m r)++instance WarnLogMonad GHCi where+ setWarnings warns = liftGhc $ setWarnings warns+ getWarnings = liftGhc $ getWarnings++instance MonadIO GHCi where+ liftIO = io++instance Haskeline.MonadException GHCi where+ catch = gcatch+ block = gblock+ unblock = gunblock++instance ExceptionMonad (InputT GHCi) where+ gcatch = Haskeline.catch+ gblock = Haskeline.block+ gunblock = Haskeline.unblock++-- for convenience...+getPrelude :: GHCi Module+getPrelude = getGHCiState >>= return . prelude++getDynFlags :: GhcMonad m => m DynFlags+getDynFlags = do+ GHC.getSessionDynFlags++setDynFlags :: DynFlags -> GHCi [PackageId]+setDynFlags dflags = do + GHC.setSessionDynFlags dflags++isOptionSet :: GHCiOption -> GHCi Bool+isOptionSet opt+ = do st <- getGHCiState+ return (opt `elem` options st)++setOption :: GHCiOption -> GHCi ()+setOption opt+ = do st <- getGHCiState+ setGHCiState (st{ options = opt : filter (/= opt) (options st) })++unsetOption :: GHCiOption -> GHCi ()+unsetOption opt+ = do st <- getGHCiState+ setGHCiState (st{ options = filter (/= opt) (options st) })++io :: IO a -> GHCi a+io = MonadUtils.liftIO++printForUser :: SDoc -> GHCi ()+printForUser doc = do+ unqual <- GHC.getPrintUnqual+ io $ Outputable.printForUser stdout unqual doc++printForUser' :: SDoc -> InputT GHCi ()+printForUser' doc = do+ unqual <- GHC.getPrintUnqual+ Haskeline.outputStrLn $ showSDocForUser unqual doc++printForUserPartWay :: SDoc -> GHCi ()+printForUserPartWay doc = do+ unqual <- GHC.getPrintUnqual+ io $ Outputable.printForUserPartWay stdout opt_PprUserLength unqual doc++-- We set log_action to write encoded output.+-- This fails whenever GHC tries to mention an (already encoded) filename,+-- but I don't know how to work around that.+setLogAction :: InputT GHCi ()+setLogAction = do+ encoder <- getEncoder+ dflags <- GHC.getSessionDynFlags+ GHC.setSessionDynFlags dflags {log_action = logAction encoder}+ return ()+ where+ logAction encoder severity srcSpan style msg = case severity of+ GHC.SevInfo -> printEncErrs encoder (msg style)+ GHC.SevFatal -> printEncErrs encoder (msg style)+ _ -> do+ hPutChar stderr '\n'+ printEncErrs encoder (ErrUtils.mkLocMessage srcSpan msg style)+ printEncErrs encoder doc = do+ str <- encoder (Pretty.showDocWith Pretty.PageMode doc)+ B.hPutStrLn stderr str+ hFlush stderr+++withVirtualPath :: GHCi a -> GHCi a+withVirtualPath m = do+ ghci_wd <- liftIO getCurrentDirectory -- Store the cwd of GHCi+ st <- getGHCiState+ liftIO $ setCurrentDirectory (virtual_path st)+ result <- m -- Evaluate in the virtual wd..+ vwd <- liftIO getCurrentDirectory+ setGHCiState (st{ virtual_path = vwd}) -- Update the virtual path+ liftIO $ setCurrentDirectory ghci_wd -- ..and restore GHCi wd+ return result++runStmt :: String -> GHC.SingleStep -> GHCi GHC.RunResult+runStmt expr step = withVirtualPath$ do+ st <- getGHCiState+ reifyGHCi $ \x ->+ withProgName (progname st) $+ withArgs (args st) $+ reflectGHCi x $ do+ GHC.handleSourceError (\e -> do GHC.printExceptionAndWarnings e+ return GHC.RunFailed) $ do+ GHC.runStmt expr step++resume :: GHC.SingleStep -> GHCi GHC.RunResult+resume step = withVirtualPath$ do+ GHC.resume step+++-- --------------------------------------------------------------------------+-- timing & statistics++timeIt :: InputT GHCi a -> InputT GHCi a+timeIt action+ = do b <- lift $ isOptionSet ShowTiming+ if not b + then action + else do allocs1 <- liftIO $ getAllocations+ time1 <- liftIO $ getCPUTime+ a <- action+ allocs2 <- liftIO $ getAllocations+ time2 <- liftIO $ getCPUTime+ liftIO $ printTimes (fromIntegral (allocs2 - allocs1)) + (time2 - time1)+ return a++foreign import ccall unsafe "getAllocations" getAllocations :: IO Int64+ -- defined in ghc/rts/Stats.c++printTimes :: Integer -> Integer -> IO ()+printTimes allocs psecs+ = do let secs = (fromIntegral psecs / (10^(12::Integer))) :: Float+ secs_str = showFFloat (Just 2) secs+ putStrLn (showSDoc (+ parens (text (secs_str "") <+> text "secs" <> comma <+> + text (show allocs) <+> text "bytes")))++-----------------------------------------------------------------------------+-- reverting CAFs+ +revertCAFs :: GHCi ()+revertCAFs = do+ io $ rts_revertCAFs+ s <- getGHCiState+ when (not (ghc_e s)) $ io turnOffBuffering+ -- Have to turn off buffering again, because we just + -- reverted stdout, stderr & stdin to their defaults.++foreign import ccall "revertCAFs" rts_revertCAFs :: IO () + -- Make it "safe", just in case++-----------------------------------------------------------------------------+-- To flush buffers for the *interpreted* computation we need+-- to refer to *its* stdout/stderr handles++GLOBAL_VAR(stdin_ptr, error "no stdin_ptr", Ptr ())+GLOBAL_VAR(stdout_ptr, error "no stdout_ptr", Ptr ())+GLOBAL_VAR(stderr_ptr, error "no stderr_ptr", Ptr ())++-- After various attempts, I believe this is the least bad way to do+-- what we want. We know look up the address of the static stdin,+-- stdout, and stderr closures in the loaded base package, and each+-- time we need to refer to them we cast the pointer to a Handle.+-- This avoids any problems with the CAF having been reverted, because+-- we'll always get the current value.+--+-- The previous attempt that didn't work was to compile an expression+-- like "hSetBuffering stdout NoBuffering" into an expression of type+-- IO () and run this expression each time we needed it, but the+-- problem is that evaluating the expression might cache the contents+-- of the Handle rather than referring to it from its static address+-- each time. There's no safe workaround for this.++initInterpBuffering :: Ghc ()+initInterpBuffering = do -- make sure these are linked+ dflags <- GHC.getSessionDynFlags+ liftIO $ do+ initDynLinker dflags++ -- ToDo: we should really look up these names properly, but+ -- it's a fiddle and not all the bits are exposed via the GHC+ -- interface.+ mb_stdin_ptr <- ObjLink.lookupSymbol "base_GHCziHandle_stdin_closure"+ mb_stdout_ptr <- ObjLink.lookupSymbol "base_GHCziHandle_stdout_closure"+ mb_stderr_ptr <- ObjLink.lookupSymbol "base_GHCziHandle_stderr_closure"++ let f ref (Just ptr) = writeIORef ref ptr+ f _ Nothing = panic "interactiveUI:setBuffering2"+ zipWithM f [stdin_ptr,stdout_ptr,stderr_ptr]+ [mb_stdin_ptr,mb_stdout_ptr,mb_stderr_ptr]+ return ()++flushInterpBuffers :: GHCi ()+flushInterpBuffers+ = io $ do getHandle stdout_ptr >>= hFlush+ getHandle stderr_ptr >>= hFlush++turnOffBuffering :: IO ()+turnOffBuffering+ = do hdls <- mapM getHandle [stdin_ptr,stdout_ptr,stderr_ptr]+ mapM_ (\h -> hSetBuffering h NoBuffering) hdls++getHandle :: IORef (Ptr ()) -> IO Handle+getHandle ref = do+ (Ptr addr) <- readIORef ref+ case addrToHValue# addr of (# hval #) -> return (unsafeCoerce# hval)
+ GhciHaskeline/GhciTags.hs view
@@ -0,0 +1,140 @@+-----------------------------------------------------------------------------+--+-- GHCi's :ctags and :etags commands+--+-- (c) The GHC Team 2005-2007+--+-----------------------------------------------------------------------------++module GhciHaskeline.GhciTags (createCTagsFileCmd, createETagsFileCmd) where++import GHC+import GhciHaskeline.GhciMonad+import Outputable+import Util++-- ToDo: figure out whether we need these, and put something appropriate+-- into the GHC API instead+import Name (nameOccName)+import OccName (pprOccName)+import MonadUtils++import Data.Maybe+import Panic+import Data.List+import Control.Monad+import System.IO+import System.IO.Error as IO++-----------------------------------------------------------------------------+-- create tags file for currently loaded modules.++createETagsFileCmd, createCTagsFileCmd :: String -> GHCi ()++createCTagsFileCmd "" = ghciCreateTagsFile CTags "tags"+createCTagsFileCmd file = ghciCreateTagsFile CTags file++createETagsFileCmd "" = ghciCreateTagsFile ETags "TAGS"+createETagsFileCmd file = ghciCreateTagsFile ETags file++data TagsKind = ETags | CTags++ghciCreateTagsFile :: TagsKind -> FilePath -> GHCi ()+ghciCreateTagsFile kind file = do+ createTagsFile kind file++-- ToDo: +-- - remove restriction that all modules must be interpreted+-- (problem: we don't know source locations for entities unless+-- we compiled the module.+--+-- - extract createTagsFile so it can be used from the command-line+-- (probably need to fix first problem before this is useful).+--+createTagsFile :: TagsKind -> FilePath -> GHCi ()+createTagsFile tagskind tagFile = do+ graph <- GHC.getModuleGraph+ let ms = map GHC.ms_mod graph+ tagModule m = do + is_interpreted <- GHC.moduleIsInterpreted m+ -- should we just skip these?+ when (not is_interpreted) $+ ghcError (CmdLineError ("module '" + ++ GHC.moduleNameString (GHC.moduleName m)+ ++ "' is not interpreted"))+ mbModInfo <- GHC.getModuleInfo m+ unqual <-+ case mbModInfo of+ Just minf -> do+ mb_print_unqual <- GHC.mkPrintUnqualifiedForModule minf+ return (fromMaybe GHC.alwaysQualify mb_print_unqual)+ Nothing ->+ return GHC.alwaysQualify+ case mbModInfo of + Just modInfo -> return $! listTags unqual modInfo + _ -> return []++ mtags <- mapM tagModule ms+ either_res <- liftIO $ collateAndWriteTags tagskind tagFile $ concat mtags+ case either_res of+ Left e -> liftIO $ hPutStrLn stderr $ ioeGetErrorString e+ Right _ -> return ()++listTags :: PrintUnqualified -> GHC.ModuleInfo -> [TagInfo]+listTags unqual modInfo =+ [ tagInfo unqual name loc + | name <- GHC.modInfoExports modInfo+ , let loc = srcSpanStart (nameSrcSpan name)+ , isGoodSrcLoc loc+ ]++type TagInfo = (String -- tag name+ ,String -- file name+ ,Int -- line number+ ,Int -- column number+ )++-- get tag info, for later translation into Vim or Emacs style+tagInfo :: PrintUnqualified -> Name -> SrcLoc -> TagInfo+tagInfo unqual name loc+ = ( showSDocForUser unqual $ pprOccName (nameOccName name)+ , showSDocForUser unqual $ ftext (srcLocFile loc)+ , srcLocLine loc+ , srcLocCol loc+ )++collateAndWriteTags :: TagsKind -> FilePath -> [TagInfo] -> IO (Either IOError ())+collateAndWriteTags CTags file tagInfos = do -- ctags style, Vim et al+ let tags = unlines $ sortLe (<=) $ nub $ map showTag tagInfos+ IO.try (writeFile file tags)+collateAndWriteTags ETags file tagInfos = do -- etags style, Emacs/XEmacs+ let byFile op (_,f1,_,_) (_,f2,_,_) = f1 `op` f2+ groups = groupBy (byFile (==)) $ sortLe (byFile (<=)) tagInfos+ tagGroups <- mapM tagFileGroup groups + IO.try (writeFile file $ concat tagGroups)+ where+ tagFileGroup [] = ghcError (CmdLineError "empty tag file group??")+ tagFileGroup group@((_,fileName,_,_):_) = do+ file <- readFile fileName -- need to get additional info from sources..+ let byLine (_,_,l1,_) (_,_,l2,_) = l1 <= l2+ sortedGroup = sortLe byLine group+ tags = unlines $ perFile sortedGroup 1 0 $ lines file+ return $ "\x0c\n" ++ fileName ++ "," ++ show (length tags) ++ "\n" ++ tags+ perFile (tagInfo@(_tag, _file, lNo, _colNo):tags) count pos lines@(line:lines')+ | lNo > count = perFile (tagInfo:tags) (count+1) (pos+length line) lines'+ | lNo == count = showETag tagInfo line pos : perFile tags count pos lines+ perFile _ _ _ _ = []++-- simple ctags format, for Vim et al+showTag :: TagInfo -> String+showTag (tag, file, lineNo, _colNo)+ = tag ++ "\t" ++ file ++ "\t" ++ show lineNo++-- etags format, for Emacs/XEmacs+showETag :: TagInfo -> String -> Int -> String+showETag (tag, _file, lineNo, colNo) line charPos+ = take colNo line ++ tag+ ++ "\x7f" ++ tag+ ++ "\x01" ++ show lineNo+ ++ "," ++ show charPos+
+ GhciHaskeline/InteractiveUI.hs view
@@ -0,0 +1,2226 @@+{-# OPTIONS -fno-cse #-}+-- -fno-cse is needed for GLOBAL_VAR's to behave properly++-----------------------------------------------------------------------------+--+-- GHC Interactive User Interface+--+-- (c) The GHC Team 2005-2006+--+-----------------------------------------------------------------------------++module GhciHaskeline.InteractiveUI ( interactiveUI, ghciWelcomeMsg ) where++import qualified GhciHaskeline.GhciMonad as GhciMonad+import GhciHaskeline.GhciMonad hiding (runStmt)+import GhciHaskeline.GhciTags+import Debugger++-- The GHC interface+import qualified GHC hiding (resume, runStmt)+import GHC ( LoadHowMuch(..), Target(..), TargetId(..),+ Module, ModuleName, TyThing(..), Phase,+ BreakIndex, SrcSpan, Resume, SingleStep,+ Ghc, handleSourceError )+import PprTyThing+import DynFlags++import Packages+import PackageConfig+import UniqFM++import HscTypes ( implicitTyThings )+import qualified RdrName ( getGRE_NameQualifier_maybes ) -- should this come via GHC?+import Outputable hiding (printForUser, printForUserPartWay)+import Module -- for ModuleEnv+import Name+import SrcLoc++-- Other random utilities+import ErrUtils+import CmdLineParser+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++#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 qualified Exception+-- 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# )+import GHC.IOBase ( IOErrorType(InvalidArgument) )+import GHC.TopHandler++import Data.IORef ( IORef, readIORef, writeIORef )++-- copied from HsVersions.h++#define GLOBAL_VAR(name,value,ty) \+{-# NOINLINE name #-}; \+name :: IORef (ty); \+name = Util.global (value);++#define ASSERT(e) if False then undefined else++-----------------------------------------------------------------------------++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 createCTagsFileCmd, completeFilename),+ ("def", keepGoing (defineMacro False), completeExpression),+ ("def!", keepGoing (defineMacro True), completeExpression),+ ("delete", keepGoing deleteCmd, noCompletion),+ ("e", keepGoing editFile, completeFilename),+ ("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, completeModule),+ ("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, noCompletion),+ ("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" +++ " :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++interactiveUI :: [(FilePath, Maybe Phase)] -> Maybe [String]+ -> Ghc ()+interactiveUI srcs maybe_exprs = do+ -- 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++ -- initial context is just the Prelude+ prel_mod <- GHC.findModule (GHC.mkModuleName "Prelude") Nothing+ GHC.setContext [] [prel_mod]++ default_editor <- liftIO $ findEditor++ cwd <- liftIO $ getCurrentDirectory++ startGHCi (runGHCi srcs maybe_exprs)+ GHCiState{ progname = "<interactive>",+ args = [],+ prompt = "%s> ",+ stop = "",+ editor = default_editor,+-- session = session,+ options = [],+ prelude = prel_mod,+ break_ctr = 0,+ breaks = [],+ tickarrays = emptyModuleEnv,+ last_command = Nothing,+ cmdqueue = [],+ remembered_ctx = [],+ virtual_path = cwd,+ 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 -> 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 = io $ withGhcAppData + (\dir -> return (Just (dir </> "ghci.conf")))+ (return Nothing)++ home_dir = do+ either_dir <- io $ IO.try (getEnv "HOME")+ case either_dir of+ Right home -> return (Just (home </> ".ghci"))+ _ -> return Nothing++ sourceConfigFile :: FilePath -> GHCi ()+ sourceConfigFile file = do+ exists <- io $ doesFileExist file+ when exists $ do+ dir_ok <- io $ checkPerms (getDirectory file)+ file_ok <- io $ checkPerms file+ when (dir_ok && file_ok) $ do+ either_hdl <- io $ 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 -> runInputTWithPrefs defaultPrefs defaultSettings $ do+ setLogAction+ runCommands $ fileLoop hdl+ where+ getDirectory f = case takeDirectory f of "" -> "."; d -> d++ when (read_dot_files) $ do+ cfgs0 <- sequence [ current_dir, app_user_dir, home_dir ]+ cfgs <- io $ mapM canonicalizePath (catMaybes cfgs0)+ mapM_ sourceConfigFile (nub cfgs)+ -- 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) $+ io (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 <- io (hIsTerminalDevice stdin)+ dflags <- getDynFlags+ let show_prompt = verbosity dflags > 0 || is_tty++ case maybe_exprs of+ Nothing ->+ do+ -- enter the interactive loop+ runGHCiInput $ runCommands $ haskelineLoop show_prompt+ Just exprs -> do+ -- just evaluate the expression we were given+ enqueueCommands exprs+ let handle e = do st <- getGHCiState+ -- Jump through some hoops to get the+ -- current progname in the exception text:+ -- <progname>: <exception>+ io $ withProgName (progname st)+ -- this used to be topHandlerFastExit, see #2228+ $ topHandler e+ runInputTWithPrefs defaultPrefs defaultSettings $ do+ setLogAction+ runCommands' handle (return Nothing)++ -- and finally, exit+ io $ do when (verbosity dflags > 0) $ putStrLn "Leaving GHCi."++runGHCiInput :: InputT GHCi a -> GHCi a+runGHCiInput f = do+ histFile <- io $ withGhcAppData (\dir -> return (Just (dir </> "ghci_history")))+ (return Nothing)+ let settings = setComplete ghciCompleteWord + $ defaultSettings {historyFile = histFile}+ runInputT settings $ do+ setLogAction+ f++-- TODO really bad name+haskelineLoop :: Bool -> InputT GHCi (Maybe String)+haskelineLoop show_prompt = do+ prompt <- if show_prompt then lift mkPrompt else return ""+ l <- getInputLine prompt+ return l+++-- 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 = 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 (BS.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 -> fmap Just (Encoding.decode 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) 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 <- handleGhcException (\e -> case e of+ Interrupted -> return False+ _other -> liftIO (print e) >> return True)+ (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++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", mod] <- words stmt = keepGoing' setContext ('+':mod)+ | otherwise+ = do result <- 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+ printForUser $ ptext (sLit "Stopped at") <+> + ppr (GHC.resumeSpan $ head resumes)+-- printTypeOfNames session names+ let namesSorted = sortBy compareNames names+ tythings <- catMaybes `liftM` + mapM GHC.lookupName namesSorted+ docs <- pprTypeAndContents [id | AnId id <- tythings]+ printForUserPartWay docs+ maybe (return ()) runBreakCmd mb_info+ -- run the command set with ":set stop <cmd>"+ st <- getGHCiState+ enqueueCommands [stop st]+ return ()+ | otherwise -> resume GHC.SingleStep >>=+ afterRunStmt step_here >> return ()+ _ -> return ()++ flushInterpBuffers+ io installSignalHandlers+ b <- isOptionSet RevertCAFs+ when b revertCAFs++ return (case run_result of GHC.RunOk _ -> True; _ -> False)++runBreakCmd :: GHC.BreakInfo -> GHCi ()+runBreakCmd info = do+ let mod = GHC.breakInfo_module info+ nm = GHC.breakInfo_number info+ st <- getGHCiState+ case [ loc | (_,loc) <- breaks st,+ breakModule loc == mod, breakTick loc == nm ] of+ [] -> return ()+ loc:_ | null cmd -> return ()+ | otherwise -> do enqueueCommands [cmd]; return ()+ where cmd = onBreakCmd loc++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++-- TODO: EVER SINGLE hPutStr needs to be changed...++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 <- io $ 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' str = do+ macros <- readIORef macros_ref+ let cmds = builtin_commands ++ macros+ -- 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 _ _ = io $ putStrLn "This command takes no arguments"++help :: String -> GHCi ()+help _ = io (putStr helpText)++info :: String -> InputT GHCi ()+info "" = ghcError (CmdLineError "syntax: ':i <thing-you-want-info-about>'")+info s = handleSourceError GHC.printExceptionAndWarnings $ 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+ outputStrLn $ 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 + = [x | x <- xs, not (getName (get_thing x) `elemNameSet` implicits)]+ where+ implicits = mkNameSet [getName t | x <- xs, t <- implicitTyThings (get_thing x)]++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 -> io (hPutStrLn stderr err)+ Right args ->+ 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 -> io (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)) $+ outputStr $ "Warning: changing directory causes all loaded modules to be unloaded,\nbecause the search path has changed.\n"+ 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")+ io $ 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 overwrite s = do+ let (macro_name, definition) = break isSpace s+ macros <- io (readIORef macros_ref)+ let defined = map cmdName macros+ if (null macro_name) + then if null defined+ then io $ putStrLn "no macros defined"+ else io $ 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) $ do+ hv <- GHC.compileExpr new_expr+ io (writeIORef macros_ref --+ (filtered ++ [(macro_name, lift . runMacro hv, noCompletion)]))++runMacro :: GHC.HValue{-String -> IO String-} -> String -> GHCi Bool+runMacro fun s = do+ str <- io ((unsafeCoerce# fun :: String -> IO String) s)+ enqueueCommands (lines str)+ return False++undefineMacro :: String -> GHCi ()+undefineMacro str = mapM_ undef (words str) + where undef macro_name = do+ cmds <- io (readIORef macros_ref)+ if (macro_name `notElem` map cmdName cmds) + then ghcError (CmdLineError + ("macro '" ++ macro_name ++ "' is not defined"))+ else do+ io (writeIORef macros_ref (filter ((/= macro_name) . cmdName) cmds))++cmdCmd :: String -> GHCi ()+cmdCmd str = do+ let expr = '(' : str ++ ") :: IO String"+ handleSourceError (\e -> GHC.printExceptionAndWarnings e) $ do+ hv <- GHC.compileExpr expr+ cmds <- io $ (unsafeCoerce# hv :: IO String)+ enqueueCommands (lines cmds)+ return ()++loadModule :: [(FilePath, Maybe Phase)] -> InputT GHCi SuccessFlag+loadModule fs = timeIt (loadModule' fs)++loadModule_ :: [FilePath] -> InputT GHCi ()+loadModule_ fs = do 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+ outputStrLn (showSDoc (+ case GHC.moduleInfo r of+ cm | Just scope <- GHC.modInfoTopLevelScope cm ->+ let+ (local,global) = 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]) -> 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]) -> 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++ lift $ setContextAfterLoad prev_context retain_context loaded_mod_summaries+++setContextAfterLoad :: ([Module],[Module]) -> Bool -> [GHC.ModSummary] -> GHCi ()+setContextAfterLoad prev keep_ctxt [] = do+ prel_mod <- getPrelude+ setContextKeepingPackageModules prev keep_ctxt ([], [prel_mod])+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,m])++-- | Keep any package modules (except Prelude) when changing the context.+setContextKeepingPackageModules+ :: ([Module],[Module]) -- previous context+ -> Bool -- re-execute :module commands+ -> ([Module],[Module]) -- new context+ -> GHCi ()+setContextKeepingPackageModules prev_context keep_ctxt (as,bs) = do+ let (_,bs0) = prev_context+ prel_mod <- getPrelude+ let pkg_modules = filter (\p -> not (isHomeModule p) && p /= prel_mod) bs0+ let bs1 = if null as then nub (prel_mod : bs) else bs+ GHC.setContext as (nub (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++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 ->+ outputStrLn (showSDoc (text "Failed, modules loaded: " <> mod_commas))+ Succeeded ->+ outputStrLn (showSDoc (text "Ok, modules loaded: " <> mod_commas))+++typeOfExpr :: String -> InputT GHCi ()+typeOfExpr str + = handleSourceError (\e -> GHC.printExceptionAndWarnings e) $ do+ ty <- GHC.exprType str+ dflags <- getDynFlags+ let pefas = dopt Opt_PrintExplicitForalls dflags+ printForUser' $ text str <+> dcolon+ <+> pprTypeForUser pefas ty++kindOfType :: String -> InputT GHCi ()+kindOfType str + = handleSourceError (\e -> GHC.printExceptionAndWarnings e) $ 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 = io (system str >> return False)++-----------------------------------------------------------------------------+-- 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 (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 = 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,modl]+ 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` []++ -- sort alphabetically name, but putting+ -- locally-defined identifiers first.+ -- We would like to improve this; see #1799.+ sorted_names = loc_sort local ++ occ_sort external+ where + (local,external) = partition ((==modl) . nameModule) names+ occ_sort = sortBy (compare `on` nameOccName) + -- try to sort by src location. If the first name in+ -- our list has a good source location, then they all should.+ loc_sort names+ | n:_ <- names, isGoodSrcSpan (nameSrcSpan n)+ = sortBy (compare `on` nameSrcSpan) names+ | otherwise+ = occ_sort names++ mb_things <- mapM GHC.lookupName sorted_names+ let filtered_things = filterOutChildren (\t -> t) (catMaybes mb_things)++ rdr_env <- GHC.getGRE++ let pefas = dopt Opt_PrintExplicitForalls dflags+ things | bang = catMaybes mb_things+ | otherwise = filtered_things+ pretty | bang = pprTyThing+ | otherwise = pprTyThingInContext++ labels [] = text "-- not currently imported"+ labels l = text $ intercalate "\n" $ map qualifier l+ qualifier = maybe "-- defined locally" + (("-- imported via "++) . intercalate ", " + . map GHC.moduleNameString)+ importInfo = RdrName.getGRE_NameQualifier_maybes rdr_env+ modNames = map (importInfo . GHC.getName) things+ + -- annotate groups of imports with their import modules+ -- the default ordering is somewhat arbitrary, so we group + -- by header and sort groups; the names themselves should+ -- really come in order of source appearance.. (trac #1799)+ annotate mts = concatMap (\(m,ts)->labels m:ts)+ $ sortBy cmpQualifiers $ group mts+ where cmpQualifiers = + compare `on` (map (fmap (map moduleNameFS)) . fst)+ group [] = []+ group mts@((m,_):_) = (m,map snd g) : group ng+ where (g,ng) = partition ((==m).fst) mts++ let prettyThings = map (pretty pefas) things+ prettyThings' | bang = annotate $ zip modNames prettyThings+ | otherwise = prettyThings+ outputStrLn $ showSDocForUser unqual (vcat prettyThings')+ -- ToDo: modInfoInstances currently throws an exception for+ -- package modules. When it works, we can do this:+ -- $$ vcat (map GHC.pprInstance (GHC.modInfoInstances mod_info))++-----------------------------------------------------------------------------+-- Setting the module context++setContext :: String -> GHCi ()+setContext str+ | all sensible strs = do+ playCtxtCmd True (cmd, as, bs)+ st <- getGHCiState+ setGHCiState st{ remembered_ctx = remembered_ctx st ++ [(cmd,as,bs)] }+ | otherwise = ghcError (CmdLineError "syntax: :module [+/-] [*]M1 ... [*]Mn")+ where+ (cmd, strs, as, bs) =+ case str of + '+':stuff -> rest AddModules stuff+ '-':stuff -> rest RemModules stuff+ stuff -> rest SetContext stuff++ rest cmd stuff = (cmd, strs, as, bs)+ where strs = words stuff+ (as,bs) = partitionWith starred strs++ sensible ('*':m) = looksLikeModuleName m+ sensible m = looksLikeModuleName m++ starred ('*':m) = Left m+ starred m = Right m++playCtxtCmd :: Bool -> (CtxtCmd, [String], [String]) -> GHCi ()+playCtxtCmd fail (cmd, as, bs)+ = do+ (as',bs') <- do_checks fail+ (prev_as,prev_bs) <- GHC.getContext+ (new_as, new_bs) <-+ case cmd of+ SetContext -> do+ prel_mod <- getPrelude+ let bs'' = if null as && prel_mod `notElem` bs' then prel_mod:bs'+ else bs'+ return (as',bs'')+ AddModules -> do+ let as_to_add = as' \\ (prev_as ++ prev_bs)+ bs_to_add = bs' \\ (prev_as ++ prev_bs)+ return (prev_as ++ as_to_add, prev_bs ++ bs_to_add)+ RemModules -> do+ let new_as = prev_as \\ (as' ++ bs')+ new_bs = prev_bs \\ (as' ++ bs')+ return (new_as, new_bs)+ GHC.setContext new_as new_bs+ where+ do_checks True = do+ as' <- mapM wantInterpretedModule as+ bs' <- mapM lookupModule bs+ return (as',bs')+ do_checks False = do+ as' <- mapM (trymaybe . wantInterpretedModule) as+ bs' <- mapM (trymaybe . lookupModule) bs+ return (catMaybes as', catMaybes bs')++ trymaybe m = do+ r <- ghciTry m+ case r of+ Left _ -> return Nothing+ Right a -> return (Just a)++----------------------------------------------------------------------------+-- Code for `:set'++-- set options in the interpreter. Syntax is exactly the same as the+-- ghc command line, except that certain options aren't available (-C,+-- -E etc.)+--+-- This is pretty fragile: most options won't work as expected. ToDo:+-- figure out which ones & disallow them.++setCmd :: String -> GHCi ()+setCmd ""+ = do st <- getGHCiState+ let opts = options st+ io $ putStrLn (showSDoc (+ text "options currently set: " <> + if null opts+ then text "none."+ else hsep (map (\o -> char '+' <> text (optToStr o)) opts)+ ))+ dflags <- getDynFlags+ io $ putStrLn (showSDoc (+ vcat (text "GHCi-specific dynamic flag settings:" + :map (flagSetting dflags) ghciFlags)+ ))+ io $ putStrLn (showSDoc (+ vcat (text "other dynamic, non-language, flag settings:" + :map (flagSetting dflags) nonLanguageDynFlags)+ ))+ where flagSetting dflags (str, f, _)+ | dopt f dflags = text " " <> text "-f" <> text str+ | otherwise = text " " <> text "-fno-" <> text str+ (ghciFlags,others) = partition (\(_, f, _) -> f `elem` flags)+ DynFlags.fFlags+ nonLanguageDynFlags = filterOut (\(_, f, _) -> f `elem` languageOptions)+ others+ flags = [Opt_PrintExplicitForalls+ ,Opt_PrintBindResult+ ,Opt_BreakOnException+ ,Opt_BreakOnError+ ,Opt_PrintEvldWithShow+ ] +setCmd str+ = case getCmd str of+ Right ("args", rest) ->+ case toArgs rest of+ Left err -> io (hPutStrLn stderr err)+ Right args -> setArgs args+ Right ("prog", rest) ->+ case toArgs rest of+ Right [prog] -> setProg prog+ _ -> io (hPutStrLn stderr "syntax: :set prog <progname>")+ Right ("prompt", rest) -> setPrompt $ dropWhile isSpace rest+ Right ("editor", rest) -> setEditor $ dropWhile isSpace rest+ Right ("stop", rest) -> setStop $ dropWhile isSpace rest+ _ -> case toArgs str of+ Left err -> io (hPutStrLn stderr err)+ Right wds -> setOptions wds++setArgs, setOptions :: [String] -> GHCi ()+setProg, setEditor, setStop, setPrompt :: String -> GHCi ()++setArgs args = do+ st <- getGHCiState+ setGHCiState st{ args = args }++setProg prog = do+ st <- getGHCiState+ setGHCiState st{ progname = prog }++setEditor cmd = do+ st <- getGHCiState+ setGHCiState st{ editor = cmd }++setStop str@(c:_) | isDigit c+ = do let (nm_str,rest) = break (not.isDigit) str+ nm = read nm_str+ st <- getGHCiState+ let old_breaks = breaks st+ if all ((/= nm) . fst) old_breaks+ then printForUser (text "Breakpoint" <+> ppr nm <+>+ text "does not exist")+ else do+ let new_breaks = map fn old_breaks+ fn (i,loc) | i == nm = (i,loc { onBreakCmd = dropWhile isSpace rest })+ | otherwise = (i,loc)+ setGHCiState st{ breaks = new_breaks }+setStop cmd = do+ st <- getGHCiState+ setGHCiState st{ stop = cmd }++setPrompt value = do+ st <- getGHCiState+ if null value+ then io $ hPutStrLn stderr $ "syntax: :set prompt <prompt>, currently \"" ++ prompt st ++ "\""+ else setGHCiState st{ prompt = remQuotes value }+ where+ remQuotes ('\"':xs) | not (null xs) && last xs == '\"' = init xs+ remQuotes x = x++setOptions wds =+ do -- first, deal with the GHCi opts (+s, +t, etc.)+ let (plus_opts, minus_opts) = partitionWith isPlus wds+ mapM_ setOpt plus_opts+ -- then, dynamic flags+ newDynFlags minus_opts++newDynFlags :: [String] -> GHCi ()+newDynFlags minus_opts = do+ dflags <- getDynFlags+ let pkg_flags = packageFlags dflags+ (dflags', leftovers, warns) <- io $ GHC.parseDynamicFlags dflags $ map noLoc minus_opts+ io $ handleFlagWarnings dflags' warns++ if (not (null leftovers))+ then ghcError $ errorsToGhcException leftovers+ else return ()++ new_pkgs <- setDynFlags dflags'++ -- if the package flags changed, we should reset the context+ -- and link the new packages.+ dflags <- getDynFlags+ when (packageFlags dflags /= pkg_flags) $ do+ io $ hPutStrLn stderr "package flags have changed, resetting and loading new packages..."+ GHC.setTargets []+ GHC.load LoadAllTargets+ io (linkPackages dflags new_pkgs)+ -- package flags changed, we can't re-use any of the old context+ setContextAfterLoad ([],[]) False []+ return ()+++unsetOptions :: String -> GHCi ()+unsetOptions str+ = do -- first, deal with the GHCi opts (+s, +t, etc.)+ let opts = words str+ (minus_opts, rest1) = partition isMinus opts+ (plus_opts, rest2) = partitionWith isPlus rest1++ if (not (null rest2)) + then io (putStrLn ("unknown option: '" ++ head rest2 ++ "'"))+ else do++ mapM_ unsetOpt plus_opts+ + let no_flag ('-':'f':rest) = return ("-fno-" ++ rest)+ no_flag f = ghcError (ProgramError ("don't know how to reverse " ++ f))++ no_flags <- mapM no_flag minus_opts+ newDynFlags no_flags++isMinus :: String -> Bool+isMinus ('-':_) = True+isMinus _ = False++isPlus :: String -> Either String String+isPlus ('+':opt) = Left opt+isPlus other = Right other++setOpt, unsetOpt :: String -> GHCi ()++setOpt str+ = case strToGHCiOpt str of+ Nothing -> io (putStrLn ("unknown option: '" ++ str ++ "'"))+ Just o -> setOption o++unsetOpt str+ = case strToGHCiOpt str of+ Nothing -> io (putStrLn ("unknown option: '" ++ str ++ "'"))+ Just o -> unsetOption o++strToGHCiOpt :: String -> (Maybe GHCiOption)+strToGHCiOpt "s" = Just ShowTiming+strToGHCiOpt "t" = Just ShowType+strToGHCiOpt "r" = Just RevertCAFs+strToGHCiOpt _ = Nothing++optToStr :: GHCiOption -> String+optToStr ShowTiming = "s"+optToStr ShowType = "t"+optToStr RevertCAFs = "r"++-- ---------------------------------------------------------------------------+-- code for `:show'++showCmd :: String -> GHCi ()+showCmd str = do+ st <- getGHCiState+ case words str of+ ["args"] -> io $ putStrLn (show (args st))+ ["prog"] -> io $ putStrLn (show (progname st))+ ["prompt"] -> io $ putStrLn (show (prompt st))+ ["editor"] -> io $ putStrLn (show (editor st))+ ["stop"] -> io $ putStrLn (show (stop st))+ ["modules" ] -> showModules+ ["bindings"] -> showBindings+ ["linker"] -> io showLinkerState+ ["breaks"] -> showBkptTable+ ["context"] -> showContext+ ["packages"] -> showPackages+ ["languages"] -> showLanguages+ _ -> ghcError (CmdLineError ("syntax: :show [ args | prog | prompt | editor | stop | modules | bindings\n"+++ " | breaks | context | packages | languages ]"))++showModules :: GHCi ()+showModules = do+ loaded_mods <- getLoadedModules+ -- we want *loaded* modules only, see #1734+ let show_one ms = do m <- GHC.showModule ms; io (putStrLn m)+ mapM_ show_one loaded_mods++getLoadedModules :: GHC.GhcMonad m => m [GHC.ModSummary]+getLoadedModules = do+ graph <- GHC.getModuleGraph+ filterM (GHC.isLoaded . GHC.ms_mod_name) graph++showBindings :: GHCi ()+showBindings = do+ bindings <- GHC.getBindings+ docs <- pprTypeAndContents+ [ id | AnId id <- sortBy compareTyThings bindings]+ printForUserPartWay docs++compareTyThings :: TyThing -> TyThing -> Ordering+t1 `compareTyThings` t2 = getName t1 `compareNames` getName t2++printTyThing :: TyThing -> GHCi ()+printTyThing tyth = do dflags <- getDynFlags+ let pefas = dopt Opt_PrintExplicitForalls dflags+ printForUser (pprTyThing pefas tyth)++showBkptTable :: GHCi ()+showBkptTable = do+ st <- getGHCiState+ printForUser $ prettyLocations (breaks st)++showContext :: GHCi ()+showContext = do+ resumes <- GHC.getResumeContext+ printForUser $ vcat (map pp_resume (reverse resumes))+ where+ pp_resume resume =+ ptext (sLit "--> ") <> text (GHC.resumeStmt resume)+ $$ nest 2 (ptext (sLit "Stopped at") <+> ppr (GHC.resumeSpan resume))++showPackages :: GHCi ()+showPackages = do+ pkg_flags <- fmap packageFlags getDynFlags+ io $ putStrLn $ showSDoc $ vcat $+ text ("active package flags:"++if null pkg_flags then " none" else "")+ : map showFlag pkg_flags+ pkg_ids <- fmap (preloadPackages . pkgState) getDynFlags+ io $ putStrLn $ showSDoc $ vcat $+ text "packages currently loaded:" + : map (nest 2 . text . packageIdString) + (sortBy (compare `on` packageIdFS) pkg_ids)+ where showFlag (ExposePackage p) = text $ " -package " ++ p+ showFlag (HidePackage p) = text $ " -hide-package " ++ p+ showFlag (IgnorePackage p) = text $ " -ignore-package " ++ p++showLanguages :: GHCi ()+showLanguages = do+ dflags <- getDynFlags+ io $ putStrLn $ showSDoc $ vcat $+ text "active language flags:" :+ [text (" -X" ++ str) | (str, f, _) <- DynFlags.xFlags, dopt f dflags]++-- -----------------------------------------------------------------------------+-- Completion++completeCmd, completeMacro, completeIdentifier, completeModule,+ completeHomeModule, completeSetOptions,+ completeHomeModuleOrFile, completeExpression+ :: CompletionFunc GHCi++ghciCompleteWord :: CompletionFunc GHCi+ghciCompleteWord line@(left,_) = case firstWord of+ ':':cmd | null rest -> completeCmd line+ | otherwise -> do+ completion <- lookupCompletion cmd+ completion line+ "import" -> completeModule line+ -- TODO: complete filename if inside quotations+ _ -> completeExpression line+ where+ (firstWord,rest) = break isSpace $ dropWhile isSpace $ reverse left+ lookupCompletion ('!':_) = return completeFilename+ lookupCompletion c = do+ maybe_cmd <- liftIO $ lookupCommand' c+ case maybe_cmd of+ Just (_,_,f) -> return f+ Nothing -> return completeFilename+++completeCmd = wrapCompleter " " $ \w -> do+ cmds <- liftIO $ readIORef macros_ref+ return (filter (w `isPrefixOf`) (map (':':) + (map cmdName (builtin_commands ++ cmds))))++completeMacro = wrapIdentCompleter $ \w -> do+ cmds <- liftIO $ readIORef macros_ref+ return (filter (w `isPrefixOf`) (map cmdName cmds))++completeIdentifier = wrapIdentCompleter $ \w -> do+ rdrs <- GHC.getRdrNamesInScope+ return (filter (w `isPrefixOf`) (map (showSDoc.ppr) rdrs))++completeModule = wrapIdentCompleter $ \w -> do+ dflags <- GHC.getSessionDynFlags+ let pkg_mods = allExposedModules dflags+ return (filter (w `isPrefixOf`) (map (showSDoc.ppr) pkg_mods))++completeHomeModule = wrapIdentCompleter listHomeModules++listHomeModules :: String -> GHCi [String]+listHomeModules w = do+ g <- GHC.getModuleGraph+ let home_mods = map GHC.ms_mod_name g+ return $ sort $ filter (w `isPrefixOf`) + $ map (showSDoc.ppr) home_mods++completeSetOptions = wrapCompleter flagWordBreakChars $ \w -> do+ return (filter (w `isPrefixOf`) options)+ where options = "args":"prog":allFlags++completeHomeModuleOrFile = completeWord Nothing filenameWordBreakChars+ $ unionComplete (fmap (map simpleCompletion) . listHomeModules)+ listFiles++unionComplete :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]+unionComplete f1 f2 line = do+ cs1 <- f1 line+ cs2 <- f2 line+ return (cs1 ++ cs2)++wrapCompleter :: String -> (String -> GHCi [String]) -> CompletionFunc GHCi+wrapCompleter breakChars fun = completeWord Nothing breakChars+ $ fmap (map simpleCompletion) . fmap sort . fun++wrapIdentCompleter :: (String -> GHCi [String]) -> CompletionFunc GHCi+wrapIdentCompleter = wrapCompleter word_break_chars++allExposedModules :: DynFlags -> [ModuleName]+allExposedModules dflags + = concat (map exposedModules (filter exposed (eltsUFM pkg_db)))+ where+ pkg_db = pkgIdMap (pkgState dflags)+++completeExpression = completeQuotedWord (Just '\\') "\"" listFiles+ completeIdentifier++-- ---------------------------------------------------------------------------+-- User code exception handling++-- This is the exception handler for exceptions generated by the+-- user's code and exceptions coming from children sessions; +-- it normally just prints out the exception. The+-- handler must be recursive, in case showing the exception causes+-- more exceptions to be raised.+--+-- Bugfix: if the user closed stdout or stderr, the flushing will fail,+-- raising another exception. We therefore don't put the recursive+-- handler arond the flushing operation, so if stderr is closed+-- GHCi will just die gracefully rather than going into an infinite loop.+handler :: SomeException -> GHCi Bool++handler exception = do+ flushInterpBuffers+ io installSignalHandlers+ ghciHandle handler (showException exception >> return False)++showException :: SomeException -> GHCi ()+showException se =+ io $ case fromException se of+ Just Interrupted -> putStrLn "Interrupted."+ -- omit the location for CmdLineError:+ Just (CmdLineError s) -> putStrLn s+ -- ditto:+ Just ph@(PhaseFailed {}) -> putStrLn (showGhcException ph "")+ Just other_ghc_ex -> print other_ghc_ex+ Nothing -> putStrLn ("*** Exception: " ++ show se)++-----------------------------------------------------------------------------+-- recursive exception handlers++-- Don't forget to unblock async exceptions in the handler, or if we're+-- in an exception loop (eg. let a = error a in a) the ^C exception+-- may never be delivered. Thanks to Marcin for pointing out the bug.++ghciHandle :: MonadException m => (SomeException -> m a) -> m a -> m a+ghciHandle h m = Haskeline.catch m $ \e -> unblock (h e)++ghciTry :: GHCi a -> GHCi (Either SomeException a)+ghciTry (GHCi m) = GHCi $ \s -> gtry (m s)++-- ----------------------------------------------------------------------------+-- Utils++-- TODO: won't work if home dir is encoded.+-- (changeDirectory may not work either in that case.)+expandPath :: MonadIO m => String -> InputT m String+expandPath path = do+ exp_path <- liftIO $ expandPathIO path+ enc <- fmap BS.unpack $ Encoding.encode exp_path+ return enc++expandPathIO :: String -> IO String+expandPathIO path = + case dropWhile isSpace path of+ ('~':d) -> do+ tilde <- getHomeDirectory -- will fail if HOME not defined+ return (tilde ++ '/':d)+ other -> + return other++wantInterpretedModule :: GHC.GhcMonad m => String -> m Module+wantInterpretedModule str = do+ modl <- lookupModule str+ dflags <- getDynFlags+ when (GHC.modulePackageId modl /= thisPackage dflags) $+ ghcError (CmdLineError ("module '" ++ str ++ "' is from another package;\nthis command requires an interpreted module"))+ is_interpreted <- GHC.moduleIsInterpreted modl+ when (not is_interpreted) $+ ghcError (CmdLineError ("module '" ++ str ++ "' is not interpreted; try \':add *" ++ str ++ "' first"))+ return modl++wantNameFromInterpretedModule :: GHC.GhcMonad m => (Name -> SDoc -> m ()) -> String+ -> (Name -> m ())+ -> m ()+wantNameFromInterpretedModule noCanDo str and_then =+ handleSourceError (GHC.printExceptionAndWarnings) $ do+ names <- GHC.parseName str+ case names of+ [] -> return ()+ (n:_) -> do+ let modl = GHC.nameModule n+ if not (GHC.isExternalName n)+ then noCanDo n $ ppr n <>+ text " is not defined in an interpreted module"+ else do+ is_interpreted <- GHC.moduleIsInterpreted modl+ if not is_interpreted+ then noCanDo n $ text "module " <> ppr modl <>+ text " is not interpreted"+ else and_then n++-- -----------------------------------------------------------------------------+-- commands for debugger++sprintCmd, printCmd, forceCmd :: String -> GHCi ()+sprintCmd = pprintCommand False False+printCmd = pprintCommand True False+forceCmd = pprintCommand False True++pprintCommand :: Bool -> Bool -> String -> GHCi ()+pprintCommand bind force str = do+ pprintClosureCommand bind force str++stepCmd :: String -> GHCi ()+stepCmd [] = doContinue (const True) GHC.SingleStep+stepCmd expression = do runStmt expression GHC.SingleStep; return ()++stepLocalCmd :: String -> GHCi ()+stepLocalCmd [] = do + mb_span <- getCurrentBreakSpan+ case mb_span of+ Nothing -> stepCmd []+ Just loc -> do+ Just mod <- getCurrentBreakModule+ current_toplevel_decl <- enclosingTickSpan mod loc+ doContinue (`isSubspanOf` current_toplevel_decl) GHC.SingleStep++stepLocalCmd expression = stepCmd expression++stepModuleCmd :: String -> GHCi ()+stepModuleCmd [] = do + mb_span <- getCurrentBreakSpan+ case mb_span of+ Nothing -> stepCmd []+ Just _ -> do+ Just span <- getCurrentBreakSpan+ let f some_span = srcSpanFileName_maybe span == srcSpanFileName_maybe some_span+ doContinue f GHC.SingleStep++stepModuleCmd expression = stepCmd expression++-- | Returns the span of the largest tick containing the srcspan given+enclosingTickSpan :: Module -> SrcSpan -> GHCi SrcSpan+enclosingTickSpan mod src = do+ ticks <- getTickArray mod+ let line = srcSpanStartLine src+ ASSERT (inRange (bounds ticks) line) do+ let enclosing_spans = [ span | (_,span) <- ticks ! line+ , srcSpanEnd span >= srcSpanEnd src]+ return . head . sortBy leftmost_largest $ enclosing_spans++traceCmd :: String -> GHCi ()+traceCmd [] = doContinue (const True) GHC.RunAndLogSteps+traceCmd expression = do runStmt expression GHC.RunAndLogSteps; return ()++continueCmd :: String -> GHCi ()+continueCmd = noArgs $ doContinue (const True) GHC.RunToCompletion++-- doContinue :: SingleStep -> GHCi ()+doContinue :: (SrcSpan -> Bool) -> SingleStep -> GHCi ()+doContinue pred step = do + runResult <- resume step+ afterRunStmt pred runResult+ return ()++abandonCmd :: String -> GHCi ()+abandonCmd = noArgs $ do+ b <- GHC.abandon -- the prompt will change to indicate the new context+ when (not b) $ io $ putStrLn "There is no computation running."+ return ()++deleteCmd :: String -> GHCi ()+deleteCmd argLine = do+ deleteSwitch $ words argLine+ where+ deleteSwitch :: [String] -> GHCi ()+ deleteSwitch [] = + io $ putStrLn "The delete command requires at least one argument."+ -- delete all break points+ deleteSwitch ("*":_rest) = discardActiveBreakPoints+ deleteSwitch idents = do+ mapM_ deleteOneBreak idents + where+ deleteOneBreak :: String -> GHCi ()+ deleteOneBreak str+ | all isDigit str = deleteBreak (read str)+ | otherwise = return ()++historyCmd :: String -> GHCi ()+historyCmd arg+ | null arg = history 20+ | all isDigit arg = history (read arg)+ | otherwise = io $ putStrLn "Syntax: :history [num]"+ where+ history num = do+ resumes <- GHC.getResumeContext+ case resumes of+ [] -> io $ putStrLn "Not stopped at a breakpoint"+ (r:_) -> do+ let hist = GHC.resumeHistory r+ (took,rest) = splitAt num hist+ case hist of+ [] -> io $ putStrLn $ + "Empty history. Perhaps you forgot to use :trace?"+ _ -> do+ spans <- mapM GHC.getHistorySpan took+ let nums = map (printf "-%-3d:") [(1::Int)..]+ names = map GHC.historyEnclosingDecl took+ printForUser (vcat(zipWith3 + (\x y z -> x <+> y <+> z) + (map text nums) + (map (bold . ppr) names)+ (map (parens . ppr) spans)))+ io $ putStrLn $ if null rest then "<end of history>" else "..."++bold :: SDoc -> SDoc+bold c | do_bold = text start_bold <> c <> text end_bold+ | otherwise = c++backCmd :: String -> GHCi ()+backCmd = noArgs $ do+ (names, _, span) <- GHC.back+ printForUser $ ptext (sLit "Logged breakpoint at") <+> ppr span+ printTypeOfNames names+ -- run the command set with ":set stop <cmd>"+ st <- getGHCiState+ enqueueCommands [stop st]++forwardCmd :: String -> GHCi ()+forwardCmd = noArgs $ do+ (names, ix, span) <- GHC.forward+ printForUser $ (if (ix == 0)+ then ptext (sLit "Stopped at")+ else ptext (sLit "Logged breakpoint at")) <+> ppr span+ printTypeOfNames names+ -- run the command set with ":set stop <cmd>"+ st <- getGHCiState+ enqueueCommands [stop st]++-- handle the "break" command+breakCmd :: String -> GHCi ()+breakCmd argLine = do+ breakSwitch $ words argLine++breakSwitch :: [String] -> GHCi ()+breakSwitch [] = do+ io $ putStrLn "The break command requires at least one argument."+breakSwitch (arg1:rest)+ | looksLikeModuleName arg1 && not (null rest) = do+ mod <- wantInterpretedModule arg1+ breakByModule mod rest+ | all isDigit arg1 = do+ (toplevel, _) <- GHC.getContext+ case toplevel of+ (mod : _) -> breakByModuleLine mod (read arg1) rest+ [] -> do + io $ putStrLn "Cannot find default module for breakpoint." + io $ putStrLn "Perhaps no modules are loaded for debugging?"+ | otherwise = do -- try parsing it as an identifier+ wantNameFromInterpretedModule noCanDo arg1 $ \name -> do+ let loc = GHC.srcSpanStart (GHC.nameSrcSpan name)+ if GHC.isGoodSrcLoc loc+ then findBreakAndSet (GHC.nameModule name) $ + findBreakByCoord (Just (GHC.srcLocFile loc))+ (GHC.srcLocLine loc, + GHC.srcLocCol loc)+ else noCanDo name $ text "can't find its location: " <> ppr loc+ where+ noCanDo n why = printForUser $+ text "cannot set breakpoint on " <> ppr n <> text ": " <> why++breakByModule :: Module -> [String] -> GHCi () +breakByModule mod (arg1:rest)+ | all isDigit arg1 = do -- looks like a line number+ breakByModuleLine mod (read arg1) rest+breakByModule _ _+ = breakSyntax++breakByModuleLine :: Module -> Int -> [String] -> GHCi ()+breakByModuleLine mod line args+ | [] <- args = findBreakAndSet mod $ findBreakByLine line+ | [col] <- args, all isDigit col =+ findBreakAndSet mod $ findBreakByCoord Nothing (line, read col)+ | otherwise = breakSyntax++breakSyntax :: a+breakSyntax = ghcError (CmdLineError "Syntax: :break [<mod>] <line> [<column>]")++findBreakAndSet :: Module -> (TickArray -> Maybe (Int, SrcSpan)) -> GHCi ()+findBreakAndSet mod lookupTickTree = do + tickArray <- getTickArray mod+ (breakArray, _) <- getModBreak mod+ case lookupTickTree tickArray of + Nothing -> io $ putStrLn $ "No breakpoints found at that location."+ Just (tick, span) -> do+ success <- io $ setBreakFlag True breakArray tick + if success + then do+ (alreadySet, nm) <- + recordBreak $ BreakLocation+ { breakModule = mod+ , breakLoc = span+ , breakTick = tick+ , onBreakCmd = ""+ }+ printForUser $+ text "Breakpoint " <> ppr nm <>+ if alreadySet + then text " was already set at " <> ppr span+ else text " activated at " <> ppr span+ else do+ printForUser $ text "Breakpoint could not be activated at" + <+> ppr span++-- When a line number is specified, the current policy for choosing+-- the best breakpoint is this:+-- - the leftmost complete subexpression on the specified line, or+-- - the leftmost subexpression starting on the specified line, or+-- - the rightmost subexpression enclosing the specified line+--+findBreakByLine :: Int -> TickArray -> Maybe (BreakIndex,SrcSpan)+findBreakByLine line arr+ | not (inRange (bounds arr) line) = Nothing+ | otherwise =+ listToMaybe (sortBy (leftmost_largest `on` snd) complete) `mplus`+ listToMaybe (sortBy (leftmost_smallest `on` snd) incomplete) `mplus`+ listToMaybe (sortBy (rightmost `on` snd) ticks)+ where + ticks = arr ! line++ starts_here = [ tick | tick@(_,span) <- ticks,+ GHC.srcSpanStartLine span == line ]++ (complete,incomplete) = partition ends_here starts_here+ where ends_here (_,span) = GHC.srcSpanEndLine span == line++findBreakByCoord :: Maybe FastString -> (Int,Int) -> TickArray+ -> Maybe (BreakIndex,SrcSpan)+findBreakByCoord mb_file (line, col) arr+ | not (inRange (bounds arr) line) = Nothing+ | otherwise =+ listToMaybe (sortBy (rightmost `on` snd) contains +++ sortBy (leftmost_smallest `on` snd) after_here)+ where + ticks = arr ! line++ -- the ticks that span this coordinate+ contains = [ tick | tick@(_,span) <- ticks, span `spans` (line,col),+ is_correct_file span ]++ is_correct_file span+ | Just f <- mb_file = GHC.srcSpanFile span == f+ | otherwise = True++ after_here = [ tick | tick@(_,span) <- ticks,+ GHC.srcSpanStartLine span == line,+ GHC.srcSpanStartCol span >= col ]++-- For now, use ANSI bold on terminals that we know support it.+-- Otherwise, we add a line of carets under the active expression instead.+-- In particular, on Windows and when running the testsuite (which sets+-- TERM to vt100 for other reasons) we get carets.+-- We really ought to use a proper termcap/terminfo library.+do_bold :: Bool+do_bold = (`isPrefixOf` unsafePerformIO mTerm) `any` ["xterm", "linux"]+ where mTerm = System.Environment.getEnv "TERM"+ `catchIO` \_ -> return "TERM not set"++start_bold :: String+start_bold = "\ESC[1m"+end_bold :: String+end_bold = "\ESC[0m"++listCmd :: String -> InputT GHCi ()+listCmd "" = do+ mb_span <- lift getCurrentBreakSpan+ case mb_span of+ Nothing ->+ printForUser' $ text "Not stopped at a breakpoint; nothing to list"+ Just span+ | GHC.isGoodSrcSpan span -> listAround span True+ | otherwise ->+ do resumes <- GHC.getResumeContext+ case resumes of+ [] -> panic "No resumes"+ (r:_) ->+ do let traceIt = case GHC.resumeHistory r of+ [] -> text "rerunning with :trace,"+ _ -> empty+ doWhat = traceIt <+> text ":back then :list"+ printForUser' (text "Unable to list source for" <+>+ ppr span+ $$ text "Try" <+> doWhat)+listCmd str = list2 (words str)++list2 :: [String] -> InputT GHCi ()+list2 [arg] | all isDigit arg = do+ (toplevel, _) <- GHC.getContext+ case toplevel of+ [] -> outputStrLn "No module to list"+ (mod : _) -> listModuleLine mod (read arg)+list2 [arg1,arg2] | looksLikeModuleName arg1, all isDigit arg2 = do+ mod <- wantInterpretedModule arg1+ listModuleLine mod (read arg2)+list2 [arg] = do+ wantNameFromInterpretedModule noCanDo arg $ \name -> do+ let loc = GHC.srcSpanStart (GHC.nameSrcSpan name)+ if GHC.isGoodSrcLoc loc+ then do+ tickArray <- lift $ getTickArray $ GHC.nameModule name+ let mb_span = findBreakByCoord (Just (GHC.srcLocFile loc))+ (GHC.srcLocLine loc, GHC.srcLocCol loc)+ tickArray+ case mb_span of+ Nothing -> listAround (GHC.srcLocSpan loc) False+ Just (_,span) -> listAround span False+ else+ noCanDo name $ text "can't find its location: " <>+ ppr loc+ where+ noCanDo n why = printForUser' $+ text "cannot list source code for " <> ppr n <> text ": " <> why+list2 _other = + outputStrLn "syntax: :list [<line> | <module> <line> | <identifier>]"++listModuleLine :: Module -> Int -> InputT GHCi ()+listModuleLine modl line = do+ graph <- GHC.getModuleGraph+ let this = filter ((== modl) . GHC.ms_mod) graph+ case this of+ [] -> panic "listModuleLine"+ summ:_ -> do+ let filename = expectJust "listModuleLine" (ml_hs_file (GHC.ms_location summ))+ loc = GHC.mkSrcLoc (mkFastString (filename)) line 0+ listAround (GHC.srcLocSpan loc) False++-- | list a section of a source file around a particular SrcSpan.+-- If the highlight flag is True, also highlight the span using+-- start_bold\/end_bold.++-- GHC files are UTF-8, so we can implement this by:+-- 1) read the file in as a BS and syntax highlight it as before+-- 2) convert the BS to String using utf-string, and write it out.+-- It would be better if we could convert directly between UTF-8 and the +-- console encoding, of course.+listAround :: MonadIO m => SrcSpan -> Bool -> InputT m ()+listAround span do_highlight = do+ contents <- liftIO $ BS.readFile $ unpackFS file+ let + lines = BS.split '\n' contents+ these_lines = take (line2 - line1 + 1 + pad_before + pad_after) $ + drop (line1 - 1 - pad_before) $ lines+ fst_line = max 1 (line1 - pad_before)+ line_nos = [ fst_line .. ]++ highlighted | do_highlight = zipWith highlight line_nos these_lines+ | otherwise = [\p -> BS.concat[p,l] | l <- these_lines]++ bs_line_nos = [ BS.pack (show l ++ " ") | l <- line_nos ]+ prefixed = zipWith ($) highlighted bs_line_nos+ --+ let output = BS.intercalate (BS.pack "\n") prefixed+ utf8Decoded <- liftIO $ BS.useAsCStringLen output+ $ \(p,n) -> utf8DecodeString (castPtr p) n+ outputStrLn utf8Decoded+ where+ file = GHC.srcSpanFile span+ line1 = GHC.srcSpanStartLine span+ col1 = GHC.srcSpanStartCol span+ line2 = GHC.srcSpanEndLine span+ col2 = GHC.srcSpanEndCol span++ pad_before | line1 == 1 = 0+ | otherwise = 1+ pad_after = 1++ highlight | do_bold = highlight_bold+ | otherwise = highlight_carets++ highlight_bold no line prefix+ | no == line1 && no == line2+ = let (a,r) = BS.splitAt col1 line+ (b,c) = BS.splitAt (col2-col1) r+ in+ BS.concat [prefix, a,BS.pack start_bold,b,BS.pack end_bold,c]+ | no == line1+ = let (a,b) = BS.splitAt col1 line in+ BS.concat [prefix, a, BS.pack start_bold, b]+ | no == line2+ = let (a,b) = BS.splitAt col2 line in+ BS.concat [prefix, a, BS.pack end_bold, b]+ | otherwise = BS.concat [prefix, line]++ highlight_carets no line prefix+ | no == line1 && no == line2+ = BS.concat [prefix, line, nl, indent, BS.replicate col1 ' ',+ BS.replicate (col2-col1) '^']+ | no == line1+ = BS.concat [indent, BS.replicate (col1 - 2) ' ', BS.pack "vv", nl, + prefix, line]+ | no == line2+ = BS.concat [prefix, line, nl, indent, BS.replicate col2 ' ',+ BS.pack "^^"]+ | otherwise = BS.concat [prefix, line]+ where+ indent = BS.pack (" " ++ replicate (length (show no)) ' ')+ nl = BS.singleton '\n'++-- --------------------------------------------------------------------------+-- Tick arrays++getTickArray :: Module -> GHCi TickArray+getTickArray modl = do+ st <- getGHCiState+ let arrmap = tickarrays st+ case lookupModuleEnv arrmap modl of+ Just arr -> return arr+ Nothing -> do+ (_breakArray, ticks) <- getModBreak modl + let arr = mkTickArray (assocs ticks)+ setGHCiState st{tickarrays = extendModuleEnv arrmap modl arr}+ return arr++discardTickArrays :: GHCi ()+discardTickArrays = do+ st <- getGHCiState+ setGHCiState st{tickarrays = emptyModuleEnv}++mkTickArray :: [(BreakIndex,SrcSpan)] -> TickArray+mkTickArray ticks+ = accumArray (flip (:)) [] (1, max_line) + [ (line, (nm,span)) | (nm,span) <- ticks,+ line <- srcSpanLines span ]+ where+ max_line = foldr max 0 (map GHC.srcSpanEndLine (map snd ticks))+ srcSpanLines span = [ GHC.srcSpanStartLine span .. + GHC.srcSpanEndLine span ]++lookupModule :: GHC.GhcMonad m => String -> m Module+lookupModule modName+ = GHC.findModule (GHC.mkModuleName modName) Nothing++-- don't reset the counter back to zero?+discardActiveBreakPoints :: GHCi ()+discardActiveBreakPoints = do+ st <- getGHCiState+ mapM (turnOffBreak.snd) (breaks st)+ setGHCiState $ st { breaks = [] }++deleteBreak :: Int -> GHCi ()+deleteBreak identity = do+ st <- getGHCiState+ let oldLocations = breaks st+ (this,rest) = partition (\loc -> fst loc == identity) oldLocations+ if null this + then printForUser (text "Breakpoint" <+> ppr identity <+>+ text "does not exist")+ else do+ mapM (turnOffBreak.snd) this+ setGHCiState $ st { breaks = rest }++turnOffBreak :: BreakLocation -> GHCi Bool+turnOffBreak loc = do+ (arr, _) <- getModBreak (breakModule loc)+ io $ setBreakFlag False arr (breakTick loc)++getModBreak :: Module -> GHCi (GHC.BreakArray, Array Int SrcSpan)+getModBreak mod = do+ Just mod_info <- GHC.getModuleInfo mod+ let modBreaks = GHC.modInfoModBreaks mod_info+ let array = GHC.modBreaks_flags modBreaks+ let ticks = GHC.modBreaks_locs modBreaks+ return (array, ticks)++setBreakFlag :: Bool -> GHC.BreakArray -> Int -> IO Bool +setBreakFlag toggle array index+ | toggle = GHC.setBreakOn array index + | otherwise = GHC.setBreakOff array index
+ LICENSE view
@@ -0,0 +1,31 @@+The Glasgow Haskell Compiler License++Copyright 2004, The University Court of the University of Glasgow. +All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++- Redistributions of source code must retain the above copyright notice,+this list of conditions and the following disclaimer.+ +- Redistributions in binary form must reproduce the above copyright notice,+this list of conditions and the following disclaimer in the documentation+and/or other materials provided with the distribution.+ +- Neither name of the University nor the names of its contributors may be+used to endorse or promote products derived from this software without+specific prior written permission. ++THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF+GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH+DAMAGE.
+ Main.hs view
@@ -0,0 +1,608 @@+{-# OPTIONS -fno-warn-incomplete-patterns -optc-DNON_POSIX_SOURCE #-}++-----------------------------------------------------------------------------+--+-- GHC Driver program+--+-- (c) The University of Glasgow 2005+--+-----------------------------------------------------------------------------++module Main (main) where++-- The official GHC API+import qualified GHC+import GHC ( DynFlags(..), HscTarget(..),+ GhcMode(..), GhcLink(..),+ LoadHowMuch(..), dopt, DynFlag(..) )+import CmdLineParser++-- Implementations of the various modes (--show-iface, mkdependHS. etc.)+import LoadIface ( showIface )+import HscMain ( newHscEnv )+import DriverPipeline ( oneShot, compileFile )+import DriverMkDepend ( doMkDependHS )+#ifdef GHCI+import GhciHaskeline.InteractiveUI ( interactiveUI, ghciWelcomeMsg )+#endif++-- Various other random stuff that we need+import Config+import HscTypes+import Packages ( dumpPackages )+import DriverPhases ( Phase(..), isSourceFilename, anyHsc,+ startPhase, isHaskellSrcFilename )+import BasicTypes ( failed )+import StaticFlags+import StaticFlagParser+import DynFlags+import ErrUtils+import FastString+import Outputable+import SrcLoc+import Util+import Panic+import MonadUtils ( liftIO )++-- Standard Haskell libraries+import System.IO+import System.Environment+import System.Exit+import System.FilePath+import Control.Monad+import Data.List+import Data.Maybe++import GHC.Paths++-----------------------------------------------------------------------------+-- ToDo:++-- time commands when run with -v+-- user ways+-- Win32 support: proper signal handling+-- reading the package configuration file is too slow+-- -K<size>++-----------------------------------------------------------------------------+-- GHC's command-line interface++main :: IO ()+main =+ + GHC.defaultErrorHandler defaultDynFlags $ do+ -- 1. extract the -B flag from the args+ argv0 <- getArgs++ let+ (minusB_args, argv1) = partition ("-B" `isPrefixOf`) argv0+ mbMinusB | null minusB_args = Just libdir+ | otherwise = Just (drop 2 (last minusB_args))++ let argv1' = map (mkGeneralLocated "on the commandline") ("--interactive" : argv1)+ (argv2, staticFlagWarnings) <- parseStaticFlags argv1'++ -- 2. Parse the "mode" flags (--make, --interactive etc.)+ (m_uber_mode, cli_mode, argv3, modeFlagWarnings) <- parseModeFlags argv2++ -- If all we want to do is to show the version number then do it+ -- now, before we start a GHC session etc.+ -- If we do it later then bootstrapping gets confused as it tries+ -- to find out what version of GHC it's using before package.conf+ -- exists, so starting the session fails.+ case m_uber_mode of+ -- ShowUsage currently has to be handled specially, as it needs to+ -- actually start up GHC so that it can find the usage.txt files+ -- in the libdir. It would be nice to embed the text in the+ -- executable so that we don't have to do that, and things are more+ -- uniform here.+ Just ShowUsage -> return ()+ Just um ->+ do case um of+ ShowInfo -> showInfo+ ShowSupportedLanguages -> showSupportedLanguages+ ShowVersion -> showVersion+ ShowNumVersion -> putStrLn cProjectVersion+ exitWith ExitSuccess+ Nothing -> return ()++ -- start our GHC session+ GHC.runGhc mbMinusB $ do++ dflags0 <- GHC.getSessionDynFlags++ -- set the default GhcMode, HscTarget and GhcLink. The HscTarget+ -- can be further adjusted on a module by module basis, using only+ -- the -fvia-C and -fasm flags. If the default HscTarget is not+ -- HscC or HscAsm, -fvia-C and -fasm have no effect.+ let dflt_target = hscTarget dflags0+ (mode, lang, link)+ = case cli_mode of+ DoInteractive -> (CompManager, HscInterpreted, LinkInMemory)+ DoEval _ -> (CompManager, HscInterpreted, LinkInMemory)+ DoMake -> (CompManager, dflt_target, LinkBinary)+ DoMkDependHS -> (MkDepend, dflt_target, LinkBinary)+ _ -> (OneShot, dflt_target, LinkBinary)++ let dflags1 = dflags0{ ghcMode = mode,+ hscTarget = lang,+ ghcLink = link,+ -- leave out hscOutName for now+ hscOutName = panic "Main.main:hscOutName not set",+ verbosity = case cli_mode of+ DoEval _ -> 0+ _other -> 1+ }++ -- turn on -fimplicit-import-qualified for GHCi now, so that it+ -- can be overriden from the command-line+ dflags1a | DoInteractive <- cli_mode = imp_qual_enabled+ | DoEval _ <- cli_mode = imp_qual_enabled+ | otherwise = dflags1+ where imp_qual_enabled = dflags1 `dopt_set` Opt_ImplicitImportQualified++ -- The rest of the arguments are "dynamic"+ -- Leftover ones are presumably files+ (dflags2, fileish_args, dynamicFlagWarnings) <- GHC.parseDynamicFlags dflags1a argv3++ -- As noted earlier, currently we hvae to handle ShowUsage down here+ case m_uber_mode of+ Just ShowUsage -> liftIO $ showGhcUsage dflags2 cli_mode+ _ -> return ()++ let flagWarnings = staticFlagWarnings+ ++ modeFlagWarnings+ ++ dynamicFlagWarnings+ liftIO $ handleFlagWarnings dflags2 flagWarnings++ -- make sure we clean up after ourselves+ GHC.defaultCleanupHandler dflags2 $ do++ liftIO $ showBanner cli_mode dflags2++ -- we've finished manipulating the DynFlags, update the session+ GHC.setSessionDynFlags dflags2+ dflags3 <- GHC.getSessionDynFlags+ hsc_env <- GHC.getSession++ let+ -- To simplify the handling of filepaths, we normalise all filepaths right + -- away - e.g., for win32 platforms, backslashes are converted+ -- into forward slashes.+ normal_fileish_paths = map (normalise . unLoc) fileish_args+ (srcs, objs) = partition_args normal_fileish_paths [] []++ -- Note: have v_Ld_inputs maintain the order in which 'objs' occurred on + -- the command-line.+ liftIO $ mapM_ (consIORef v_Ld_inputs) (reverse objs)++ ---------------- Display configuration -----------+ when (verbosity dflags3 >= 4) $+ liftIO $ dumpPackages dflags3++ when (verbosity dflags3 >= 3) $ do+ liftIO $ hPutStrLn stderr ("Hsc static flags: " ++ unwords staticFlags)++ ---------------- Final sanity checking -----------+ liftIO $ checkOptions cli_mode dflags3 srcs objs++ ---------------- Do the business -----------+ handleSourceError (\e -> do+ GHC.printExceptionAndWarnings e+ liftIO $ exitWith (ExitFailure 1)) $ do+ case cli_mode of+ PrintLibdir -> liftIO $ putStrLn (topDir dflags3)+ ShowInterface f -> liftIO $ doShowIface dflags3 f+ DoMake -> doMake srcs+ DoMkDependHS -> doMkDependHS (map fst srcs)+ StopBefore p -> oneShot hsc_env p srcs >> GHC.printWarnings+ DoInteractive -> interactiveUI srcs Nothing+ DoEval exprs -> interactiveUI srcs $ Just $ reverse exprs++ liftIO $ dumpFinalStats dflags3+ liftIO $ exitWith ExitSuccess++#ifndef GHCI+interactiveUI :: b -> c -> Ghc ()+interactiveUI _ _ =+ ghcError (CmdLineError "not built for interactive use")+#endif++-- -----------------------------------------------------------------------------+-- Splitting arguments into source files and object files. This is where we+-- interpret the -x <suffix> option, and attach a (Maybe Phase) to each source+-- file indicating the phase specified by the -x option in force, if any.++partition_args :: [String] -> [(String, Maybe Phase)] -> [String]+ -> ([(String, Maybe Phase)], [String])+partition_args [] srcs objs = (reverse srcs, reverse objs)+partition_args ("-x":suff:args) srcs objs+ | "none" <- suff = partition_args args srcs objs+ | StopLn <- phase = partition_args args srcs (slurp ++ objs)+ | otherwise = partition_args rest (these_srcs ++ srcs) objs+ where phase = startPhase suff+ (slurp,rest) = break (== "-x") args + these_srcs = zip slurp (repeat (Just phase))+partition_args (arg:args) srcs objs+ | looks_like_an_input arg = partition_args args ((arg,Nothing):srcs) objs+ | otherwise = partition_args args srcs (arg:objs)++ {-+ We split out the object files (.o, .dll) and add them+ to v_Ld_inputs for use by the linker.++ The following things should be considered compilation manager inputs:++ - haskell source files (strings ending in .hs, .lhs or other + haskellish extension),++ - module names (not forgetting hierarchical module names),++ - and finally we consider everything not containing a '.' to be+ a comp manager input, as shorthand for a .hs or .lhs filename.++ Everything else is considered to be a linker object, and passed+ straight through to the linker.+ -}+looks_like_an_input :: String -> Bool+looks_like_an_input m = isSourceFilename m + || looksLikeModuleName m+ || '.' `notElem` m++-- -----------------------------------------------------------------------------+-- Option sanity checks++-- | Ensure sanity of options.+--+-- Throws 'UsageError' or 'CmdLineError' if not.+checkOptions :: CmdLineMode -> DynFlags -> [(String,Maybe Phase)] -> [String] -> IO ()+ -- Final sanity checking before kicking off a compilation (pipeline).+checkOptions cli_mode dflags srcs objs = do+ -- Complain about any unknown flags+ let unknown_opts = [ f | (f@('-':_), _) <- srcs ]+ when (notNull unknown_opts) (unknownFlagsErr unknown_opts)++ when (notNull (filter isRTSWay (wayNames dflags))+ && isInterpretiveMode cli_mode) $+ hPutStrLn stderr ("Warning: -debug, -threaded and -ticky are ignored by GHCi")++ -- -prof and --interactive are not a good combination+ when (notNull (filter (not . isRTSWay) (wayNames dflags))+ && isInterpretiveMode cli_mode) $+ do ghcError (UsageError + "--interactive can't be used with -prof or -unreg.")+ -- -ohi sanity check+ if (isJust (outputHi dflags) && + (isCompManagerMode cli_mode || srcs `lengthExceeds` 1))+ then ghcError (UsageError "-ohi can only be used when compiling a single source file")+ else do++ -- -o sanity checking+ if (srcs `lengthExceeds` 1 && isJust (outputFile dflags)+ && not (isLinkMode cli_mode))+ then ghcError (UsageError "can't apply -o to multiple source files")+ else do++ let not_linking = not (isLinkMode cli_mode) || isNoLink (ghcLink dflags)++ when (not_linking && not (null objs)) $+ hPutStrLn stderr ("Warning: the following files would be used as linker inputs, but linking is not being done: " ++ unwords objs)++ -- Check that there are some input files+ -- (except in the interactive case)+ if null srcs && (null objs || not_linking) && needsInputsMode cli_mode+ then ghcError (UsageError "no input files")+ else do++ -- Verify that output files point somewhere sensible.+ verifyOutputFiles dflags+++-- Compiler output options++-- called to verify that the output files & directories+-- point somewhere valid. +--+-- The assumption is that the directory portion of these output+-- options will have to exist by the time 'verifyOutputFiles'+-- is invoked.+-- +verifyOutputFiles :: DynFlags -> IO ()+verifyOutputFiles dflags = do+ -- not -odir: we create the directory for -odir if it doesn't exist (#2278).+ let ofile = outputFile dflags+ when (isJust ofile) $ do+ let fn = fromJust ofile+ flg <- doesDirNameExist fn+ when (not flg) (nonExistentDir "-o" fn)+ let ohi = outputHi dflags+ when (isJust ohi) $ do+ let hi = fromJust ohi+ flg <- doesDirNameExist hi+ when (not flg) (nonExistentDir "-ohi" hi)+ where+ nonExistentDir flg dir = + ghcError (CmdLineError ("error: directory portion of " ++ + show dir ++ " does not exist (used with " ++ + show flg ++ " option.)"))++-----------------------------------------------------------------------------+-- GHC modes of operation++data UberMode+ = ShowUsage -- ghc -?+ | ShowVersion -- ghc -V/--version+ | ShowNumVersion -- ghc --numeric-version+ | ShowSupportedLanguages -- ghc --supported-languages+ | ShowInfo -- ghc --info+ deriving (Show)++data CmdLineMode+ = PrintLibdir -- ghc --print-libdir+ | ShowInterface String -- ghc --show-iface+ | DoMkDependHS -- ghc -M+ | StopBefore Phase -- ghc -E | -C | -S+ -- StopBefore StopLn is the default+ | DoMake -- ghc --make+ | DoInteractive -- ghc --interactive+ | DoEval [String] -- ghc -e foo -e bar => DoEval ["bar", "foo"]+ deriving (Show)++#ifdef GHCI+isInteractiveMode :: CmdLineMode -> Bool+isInteractiveMode DoInteractive = True+isInteractiveMode _ = False+#endif++-- isInterpretiveMode: byte-code compiler involved+isInterpretiveMode :: CmdLineMode -> Bool+isInterpretiveMode DoInteractive = True+isInterpretiveMode (DoEval _) = True+isInterpretiveMode _ = False++needsInputsMode :: CmdLineMode -> Bool+needsInputsMode DoMkDependHS = True+needsInputsMode (StopBefore _) = True+needsInputsMode DoMake = True+needsInputsMode _ = False++-- True if we are going to attempt to link in this mode.+-- (we might not actually link, depending on the GhcLink flag)+isLinkMode :: CmdLineMode -> Bool+isLinkMode (StopBefore StopLn) = True+isLinkMode DoMake = True+isLinkMode DoInteractive = True+isLinkMode (DoEval _) = True+isLinkMode _ = False++isCompManagerMode :: CmdLineMode -> Bool+isCompManagerMode DoMake = True+isCompManagerMode DoInteractive = True+isCompManagerMode (DoEval _) = True+isCompManagerMode _ = False+++-- -----------------------------------------------------------------------------+-- Parsing the mode flag++parseModeFlags :: [Located String]+ -> IO (Maybe UberMode,+ CmdLineMode,+ [Located String],+ [Located String])+parseModeFlags args = do+ let ((leftover, errs, warns), (mUberMode, mode, _, flags')) =+ runCmdLine (processArgs mode_flags args)+ (Nothing, StopBefore StopLn, "", [])+ when (not (null errs)) $ ghcError $ errorsToGhcException errs+ return (mUberMode, mode, flags' ++ leftover, warns)++type ModeM = CmdLineP (Maybe UberMode, CmdLineMode, String, [Located String])+ -- mode flags sometimes give rise to new DynFlags (eg. -C, see below)+ -- so we collect the new ones and return them.++mode_flags :: [Flag ModeM]+mode_flags =+ [ ------- help / version ----------------------------------------------+ Flag "?" (NoArg (setUberMode ShowUsage))+ Supported+ , Flag "-help" (NoArg (setUberMode ShowUsage))+ Supported+ , Flag "V" (NoArg (setUberMode ShowVersion))+ Supported+ , Flag "-version" (NoArg (setUberMode ShowVersion))+ Supported+ , Flag "-numeric-version" (NoArg (setUberMode ShowNumVersion))+ Supported+ , Flag "-info" (NoArg (setUberMode ShowInfo))+ Supported+ , Flag "-supported-languages" (NoArg (setUberMode ShowSupportedLanguages))+ Supported+ , Flag "-print-libdir" (PassFlag (setMode PrintLibdir))+ Supported++ ------- interfaces ----------------------------------------------------+ , Flag "-show-iface" (HasArg (\f -> setMode (ShowInterface f)+ "--show-iface"))+ Supported++ ------- primary modes ------------------------------------------------+ , Flag "M" (PassFlag (setMode DoMkDependHS))+ Supported+ , Flag "E" (PassFlag (setMode (StopBefore anyHsc)))+ Supported+ , Flag "C" (PassFlag (\f -> do setMode (StopBefore HCc) f+ addFlag "-fvia-C"))+ Supported+ , Flag "S" (PassFlag (setMode (StopBefore As)))+ Supported+ , Flag "-make" (PassFlag (setMode DoMake))+ Supported+ , Flag "-interactive" (PassFlag (setMode DoInteractive))+ Supported+ , Flag "e" (HasArg (\s -> updateMode (updateDoEval s) "-e"))+ Supported++ -- -fno-code says to stop after Hsc but don't generate any code.+ , Flag "fno-code" (PassFlag (\f -> do setMode (StopBefore HCc) f+ addFlag "-fno-code"+ addFlag "-fforce-recomp"))+ Supported+ ]++setUberMode :: UberMode -> ModeM ()+setUberMode m = do+ (_, cmdLineMode, flag, flags') <- getCmdLineState+ putCmdLineState (Just m, cmdLineMode, flag, flags')++setMode :: CmdLineMode -> String -> ModeM ()+setMode m flag = updateMode (\_ -> m) flag++updateDoEval :: String -> CmdLineMode -> CmdLineMode+updateDoEval expr (DoEval exprs) = DoEval (expr : exprs)+updateDoEval expr _ = DoEval [expr]++updateMode :: (CmdLineMode -> CmdLineMode) -> String -> ModeM ()+updateMode f flag = do+ (m_uber_mode, old_mode, old_flag, flags') <- getCmdLineState+ if null old_flag || flag == old_flag+ then putCmdLineState (m_uber_mode, f old_mode, flag, flags')+ else ghcError (UsageError+ ("cannot use `" ++ old_flag ++ "' with `" ++ flag ++ "'"))++addFlag :: String -> ModeM ()+addFlag s = do+ (u, m, f, flags') <- getCmdLineState+ -- XXX Can we get a useful Loc?+ putCmdLineState (u, m, f, mkGeneralLocated "addFlag" s : flags')+++-- ----------------------------------------------------------------------------+-- Run --make mode++doMake :: [(String,Maybe Phase)] -> Ghc ()+doMake [] = ghcError (UsageError "no input files")+doMake srcs = do+ let (hs_srcs, non_hs_srcs) = partition haskellish srcs++ haskellish (f,Nothing) = + looksLikeModuleName f || isHaskellSrcFilename f || '.' `notElem` f+ haskellish (_,Just phase) = + phase `notElem` [As, Cc, CmmCpp, Cmm, StopLn]++ hsc_env <- GHC.getSession+ o_files <- mapM (\x -> do+ f <- compileFile hsc_env StopLn x+ GHC.printWarnings+ return f)+ non_hs_srcs+ liftIO $ mapM_ (consIORef v_Ld_inputs) (reverse o_files)++ targets <- mapM (uncurry GHC.guessTarget) hs_srcs+ GHC.setTargets targets+ ok_flag <- GHC.load LoadAllTargets++ when (failed ok_flag) (liftIO $ exitWith (ExitFailure 1))+ return ()+++-- ---------------------------------------------------------------------------+-- --show-iface mode++doShowIface :: DynFlags -> FilePath -> IO ()+doShowIface dflags file = do+ hsc_env <- newHscEnv dflags+ showIface hsc_env file++-- ---------------------------------------------------------------------------+-- Various banners and verbosity output.++showBanner :: CmdLineMode -> DynFlags -> IO ()+showBanner _cli_mode dflags = do+ let verb = verbosity dflags++#ifdef GHCI+ -- Show the GHCi banner+ when (isInteractiveMode _cli_mode && verb >= 1) $ putStrLn ghciWelcomeMsg+#endif++ -- Display details of the configuration in verbose mode+ when (verb >= 2) $+ do hPutStr stderr "Glasgow Haskell Compiler, Version "+ hPutStr stderr cProjectVersion+ hPutStr stderr ", for Haskell 98, stage "+ hPutStr stderr cStage+ hPutStr stderr " booted by GHC version "+ hPutStrLn stderr cBooterVersion++-- We print out a Read-friendly string, but a prettier one than the+-- Show instance gives us+showInfo :: IO ()+showInfo = do+ let sq x = " [" ++ x ++ "\n ]"+ putStrLn $ sq $ concat $ intersperse "\n ," $ map show compilerInfo+ exitWith ExitSuccess++showSupportedLanguages :: IO ()+showSupportedLanguages = do mapM_ putStrLn supportedLanguages+ exitWith ExitSuccess++showVersion :: IO ()+showVersion = do+ putStrLn (cProjectName ++ ", version " ++ cProjectVersion)+ exitWith ExitSuccess++showGhcUsage :: DynFlags -> CmdLineMode -> IO ()+showGhcUsage dflags cli_mode = do + let usage_path + | DoInteractive <- cli_mode = ghciUsagePath dflags+ | otherwise = ghcUsagePath dflags+ usage <- readFile usage_path+ dump usage+ exitWith ExitSuccess+ where+ dump "" = return ()+ dump ('$':'$':s) = putStr progName >> dump s+ dump (c:s) = putChar c >> dump s++dumpFinalStats :: DynFlags -> IO ()+dumpFinalStats dflags = + when (dopt Opt_D_faststring_stats dflags) $ dumpFastStringStats dflags++dumpFastStringStats :: DynFlags -> IO ()+dumpFastStringStats dflags = do+ buckets <- getFastStringTable+ let (entries, longest, is_z, has_z) = countFS 0 0 0 0 buckets+ msg = text "FastString stats:" $$+ nest 4 (vcat [text "size: " <+> int (length buckets),+ text "entries: " <+> int entries,+ text "longest chain: " <+> int longest,+ text "z-encoded: " <+> (is_z `pcntOf` entries),+ text "has z-encoding: " <+> (has_z `pcntOf` entries)+ ])+ -- we usually get more "has z-encoding" than "z-encoded", because+ -- when we z-encode a string it might hash to the exact same string,+ -- which will is not counted as "z-encoded". Only strings whose+ -- Z-encoding is different from the original string are counted in+ -- the "z-encoded" total.+ putMsg dflags msg+ where+ x `pcntOf` y = int ((x * 100) `quot` y) <> char '%'++countFS :: Int -> Int -> Int -> Int -> [[FastString]] -> (Int, Int, Int, Int)+countFS entries longest is_z has_z [] = (entries, longest, is_z, has_z)+countFS entries longest is_z has_z (b:bs) = + let+ len = length b+ longest' = max len longest+ entries' = entries + len+ is_zs = length (filter isZEncoded b)+ has_zs = length (filter hasZEncoding b)+ in+ countFS entries' longest' (is_z + is_zs) (has_z + has_zs) bs++-- -----------------------------------------------------------------------------+-- Util++unknownFlagsErr :: [String] -> a+unknownFlagsErr fs = ghcError (UsageError ("unrecognised flags: " ++ unwords fs))
+ Setup.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMain
+ ghci-haskeline.cabal view
@@ -0,0 +1,50 @@+Name: ghci-haskeline+Version: 0.1+Copyright: BSD3+License: BSD3+License-File: LICENSE+-- Author: XXX+Build-Type: Simple+Cabal-Version: >= 1.6+Maintainer: Judah Jacobson <judah.jacobson@gmail.com>+Category: Compilers/Interpreters+Synopsis: An implementation of ghci using the Haskeline line-input library.+Description: This package reimplements ghci using the GHC API and the Haskeline+ package for line input in command-line programs.+ .+ This is an unofficial package; thus any bugs may be the fault of+ the maintainer rather than of the GHC team.+Homepage: http://code.haskell.org/~judah/ghci-haskeline++Executable ghci-haskeline+ Main-Is: Main.hs+ Other-modules: GhciHaskeline.GhciMonad+ GhciHaskeline.InteractiveUI+ GhciHaskeline.GhciTags++ Build-Depends: ghc == 6.10.*,+ base >=4 && < 5,+ array >= 0.1 && < 0.3,+ bytestring >= 0.9 && < 0.10,+ directory >= 1 && < 1.1,+ filepath >= 1 && < 1.2,+ process >= 1 && < 1.1+ Build-Depends: mtl, haskeline == 0.6.*++ if os(windows)+ Build-Depends: Win32+ else+ Build-Depends: unix+ Build-Depends: ghc-paths+ cpp-Options: -DGHCI+ ghc-options: -threaded++ Extensions: CPP, MagicHash, UnboxedTuples, PatternGuards,+ ForeignFunctionInterface, EmptyDataDecls,+ TypeSynonymInstances, MultiParamTypeClasses,+ FlexibleInstances, Rank2Types, ScopedTypeVariables,+ DeriveDataTypeable++ GHC-Options: -Wall -fno-warn-orphans -fno-warn-name-shadowing++