summaryrefslogtreecommitdiff
path: root/ghc
diff options
context:
space:
mode:
authorIan Lynagh <igloo@earth.li>2009-04-29 00:58:38 +0000
committerIan Lynagh <igloo@earth.li>2009-04-29 00:58:38 +0000
commit46aed8a4a084add708bbd119d19905105d5f0d72 (patch)
tree2f93cdcc414bc3a3b128512b0a1ee3d6ddce95bf /ghc
parente213baf0e233efca39d627efcbabeeaac14f3e5c (diff)
downloadhaskell-46aed8a4a084add708bbd119d19905105d5f0d72.tar.gz
Use haskeline, rather than editline, for line editing in ghci
Diffstat (limited to 'ghc')
-rw-r--r--ghc/GhciMonad.hs389
-rw-r--r--ghc/GhciTags.hs140
-rw-r--r--ghc/InteractiveUI.hs2237
-rw-r--r--ghc/ghc-bin.cabal.in16
-rw-r--r--ghc/ghc.mk20
5 files changed, 2795 insertions, 7 deletions
diff --git a/ghc/GhciMonad.hs b/ghc/GhciMonad.hs
new file mode 100644
index 0000000000..341e94a5e3
--- /dev/null
+++ b/ghc/GhciMonad.hs
@@ -0,0 +1,389 @@
+{-# OPTIONS_GHC -fno-cse -fno-warn-orphans #-}
+-- -fno-cse is needed for GLOBAL_VAR's to behave properly
+
+-----------------------------------------------------------------------------
+--
+-- Monadery code used in InteractiveUI
+--
+-- (c) The GHC Team 2005-2006
+--
+-----------------------------------------------------------------------------
+
+module GhciMonad where
+
+#include "HsVersions.h"
+
+import qualified GHC
+import Outputable hiding (printForUser, printForUserPartWay)
+import qualified Pretty
+import qualified Outputable
+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
+import qualified 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.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
+
+-----------------------------------------------------------------------------
+-- 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.
+ 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
+
+runStmt :: String -> GHC.SingleStep -> GHCi GHC.RunResult
+runStmt expr step = 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 :: (SrcSpan -> Bool) -> GHC.SingleStep -> GHCi GHC.RunResult
+resume canLogSpan step = GHC.resume canLogSpan 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)
diff --git a/ghc/GhciTags.hs b/ghc/GhciTags.hs
new file mode 100644
index 0000000000..b53a56f967
--- /dev/null
+++ b/ghc/GhciTags.hs
@@ -0,0 +1,140 @@
+-----------------------------------------------------------------------------
+--
+-- GHCi's :ctags and :etags commands
+--
+-- (c) The GHC Team 2005-2007
+--
+-----------------------------------------------------------------------------
+
+module GhciTags (createCTagsFileCmd, createETagsFileCmd) where
+
+import GHC
+import 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
+
diff --git a/ghc/InteractiveUI.hs b/ghc/InteractiveUI.hs
new file mode 100644
index 0000000000..4aa441eb36
--- /dev/null
+++ b/ghc/InteractiveUI.hs
@@ -0,0 +1,2237 @@
+{-# OPTIONS -fno-cse #-}
+-- -fno-cse is needed for GLOBAL_VAR's to behave properly
+
+{-# OPTIONS -#include "Linker.h" #-}
+-----------------------------------------------------------------------------
+--
+-- GHC Interactive User Interface
+--
+-- (c) The GHC Team 2005-2006
+--
+-----------------------------------------------------------------------------
+
+module InteractiveUI ( interactiveUI, ghciWelcomeMsg ) where
+
+#include "HsVersions.h"
+
+import qualified GhciMonad
+import GhciMonad hiding (runStmt)
+import GhciTags
+import Debugger
+
+-- The GHC interface
+import qualified GHC hiding (resume, runStmt)
+import GHC ( LoadHowMuch(..), Target(..), TargetId(..),
+ Module, ModuleName, TyThing(..), Phase,
+ BreakIndex, SrcSpan, Resume, SingleStep,
+ Ghc, handleSourceError )
+import PprTyThing
+import DynFlags
+
+import Packages
+import PackageConfig
+import UniqFM
+
+import HscTypes ( implicitTyThings, handleFlagWarnings )
+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 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 )
+
+-----------------------------------------------------------------------------
+
+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, completeShowOptions),
+ ("sprint", keepGoing sprintCmd, completeExpression),
+ ("step", keepGoing stepCmd, completeIdentifier),
+ ("steplocal", keepGoing stepLocalCmd, completeIdentifier),
+ ("stepmodule",keepGoing stepModuleCmd, completeIdentifier),
+ ("type", keepGoing' typeOfExpr, completeExpression),
+ ("trace", keepGoing traceCmd, completeExpression),
+ ("undef", keepGoing undefineMacro, completeMacro),
+ ("unset", keepGoing unsetOptions, completeSetOptions)
+ ]
+
+
+-- We initialize readline (in the interactiveUI function) to use
+-- word_break_chars as the default set of completion word break characters.
+-- This can be overridden for a particular command (for example, filename
+-- expansion shouldn't consider '/' to be a word break) by setting the third
+-- entry in the Command tuple above.
+--
+-- NOTE: in order for us to override the default correctly, any custom entry
+-- must be a SUBSET of word_break_chars.
+word_break_chars :: String
+word_break_chars = let symbols = "!#$%&*+/<=>?@\\^|-~"
+ specials = "(),;[]`{}"
+ spaces = " \t\n"
+ in spaces ++ specials ++ symbols
+
+flagWordBreakChars :: String
+flagWordBreakChars = " \t\n"
+
+
+keepGoing :: (String -> GHCi ()) -> (String -> InputT GHCi Bool)
+keepGoing a str = keepGoing' (lift . a) str
+
+keepGoing' :: Monad m => (String -> m ()) -> String -> m Bool
+keepGoing' a str = a str >> return False
+
+keepGoingPaths :: ([FilePath] -> InputT GHCi ()) -> (String -> InputT GHCi Bool)
+keepGoingPaths a str
+ = do case toArgs str of
+ Left err -> Encoding.encode err >>= liftIO . BS.hPutStrLn stderr
+ Right args -> a args
+ return False
+
+shortHelpText :: String
+shortHelpText = "use :? for help.\n"
+
+helpText :: String
+helpText =
+ " Commands available from the prompt:\n" ++
+ "\n" ++
+ " <statement> evaluate/run <statement>\n" ++
+ " : repeat last command\n" ++
+ " :{\\n ..lines.. \\n:}\\n multiline command\n" ++
+ " :add [*]<module> ... add module(s) to the current target set\n" ++
+ " :browse[!] [[*]<mod>] display the names defined by module <mod>\n" ++
+ " (!: more details; *: all top-level names)\n" ++
+ " :cd <dir> change directory to <dir>\n" ++
+ " :cmd <expr> run the commands returned by <expr>::IO String\n" ++
+ " :ctags [<file>] create tags file for Vi (default: \"tags\")\n" ++
+ " :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.lookupModule (GHC.mkModuleName "Prelude") Nothing
+ GHC.setContext [] [prel_mod]
+
+ default_editor <- liftIO $ findEditor
+
+ 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 = [],
+ 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
+ mb_id_loc <- toBreakIdAndLocation mb_info
+ let breakCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
+ if (null breakCmd)
+ then printStoppedAtBreakInfo (head resumes) names
+ else enqueueCommands [breakCmd]
+ -- run the command set with ":set stop <cmd>"
+ st <- getGHCiState
+ enqueueCommands [stop st]
+ return ()
+ | otherwise -> resume step_here GHC.SingleStep >>=
+ afterRunStmt step_here >> return ()
+ _ -> return ()
+
+ flushInterpBuffers
+ io installSignalHandlers
+ b <- isOptionSet RevertCAFs
+ when b revertCAFs
+
+ return (case run_result of GHC.RunOk _ -> True; _ -> False)
+
+toBreakIdAndLocation ::
+ Maybe GHC.BreakInfo -> GHCi (Maybe (Int, BreakLocation))
+toBreakIdAndLocation Nothing = return Nothing
+toBreakIdAndLocation (Just info) = do
+ let mod = GHC.breakInfo_module info
+ nm = GHC.breakInfo_number info
+ st <- getGHCiState
+ return $ listToMaybe [ id_loc | id_loc@(_,loc) <- breaks st,
+ breakModule loc == mod,
+ breakTick loc == nm ]
+
+printStoppedAtBreakInfo :: Resume -> [Name] -> GHCi ()
+printStoppedAtBreakInfo resume names = do
+ printForUser $ ptext (sLit "Stopped at") <+>
+ ppr (GHC.resumeSpan resume)
+ -- printTypeOfNames session names
+ let namesSorted = sortBy compareNames names
+ tythings <- catMaybes `liftM` mapM GHC.lookupName namesSorted
+ docs <- pprTypeAndContents [id | AnId id <- tythings]
+ printForUserPartWay docs
+
+printTypeOfNames :: [Name] -> GHCi ()
+printTypeOfNames names
+ = mapM_ (printTypeOfName ) $ sortBy compareNames names
+
+compareNames :: Name -> Name -> Ordering
+n1 `compareNames` n2 = compareWith n1 `compare` compareWith n2
+ where compareWith n = (getOccString n, getSrcSpan n)
+
+printTypeOfName :: Name -> GHCi ()
+printTypeOfName n
+ = do maybe_tything <- GHC.lookupName n
+ case maybe_tything of
+ Nothing -> return ()
+ Just thing -> printTyThing thing
+
+
+data MaybeCommand = GotCommand Command | BadCommand | NoLastCommand
+
+specialCommand :: String -> InputT GHCi Bool
+specialCommand ('!':str) = lift $ shellEscape (dropWhile isSpace str)
+specialCommand str = do
+ let (cmd,rest) = break isSpace str
+ maybe_cmd <- lift $ lookupCommand cmd
+ case maybe_cmd of
+ GotCommand (_,f,_) -> f (dropWhile isSpace rest)
+ BadCommand ->
+ do liftIO $ hPutStr stdout ("unknown command ':" ++ cmd ++ "'\n"
+ ++ shortHelpText)
+ return False
+ NoLastCommand ->
+ do liftIO $ hPutStr stdout ("there is no last command to perform\n"
+ ++ shortHelpText)
+ return False
+
+lookupCommand :: String -> GHCi (MaybeCommand)
+lookupCommand "" = do
+ st <- getGHCiState
+ case last_command st of
+ Just c -> return $ GotCommand c
+ Nothing -> return NoLastCommand
+lookupCommand str = do
+ mc <- 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)
+ -- make sure we force any exceptions in the result, while we are still
+ -- inside the exception handler for commands:
+ seqList str (return ())
+ enqueueCommands (lines str)
+ return False
+
+undefineMacro :: String -> GHCi ()
+undefineMacro str = mapM_ undef (words str)
+ where undef macro_name = do
+ cmds <- 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) = ASSERT( all isExternalName scope )
+ partition ((== modl) . GHC.moduleName . GHC.nameModule) scope
+ in
+ (text "global names: " <+> ppr global) $$
+ (text "local names: " <+> ppr local)
+ _ -> empty))
+ return True
+ afterLoad (successIf ok) False prev_context
+
+reloadModule :: String -> InputT GHCi ()
+reloadModule m = do
+ prev_context <- GHC.getContext
+ doLoad True prev_context $
+ if null m then LoadAllTargets
+ else LoadUpTo (GHC.mkModuleName m)
+ return ()
+
+doLoad :: Bool -> ([Module],[Module]) -> 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' $ sep [text str, nest 2 (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) = ASSERT( all isExternalName names )
+ 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 case value of
+ '\"' : _ -> case reads value of
+ [(value', xs)] | all isSpace xs ->
+ setGHCiState (st { prompt = value' })
+ _ ->
+ io $ hPutStrLn stderr "Can't parse prompt string. Use Haskell syntax."
+ _ -> setGHCiState (st { prompt = value })
+
+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
+ 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, completeShowOptions,
+ 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
+ _ -> 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
+ loaded_mods <- liftM (map GHC.ms_mod_name) getLoadedModules
+ return $ filter (w `isPrefixOf`)
+ $ map (showSDoc.ppr) $ loaded_mods ++ 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":"prompt":"editor":"stop":flagList
+ flagList = map head $ group $ sort allFlags
+
+completeShowOptions = wrapCompleter flagWordBreakChars $ \w -> do
+ return (filter (w `isPrefixOf`) options)
+ where options = ["args", "prog", "prompt", "editor", "stop",
+ "modules", "bindings", "linker", "breaks",
+ "context", "packages", "languages"]
+
+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 = ASSERT( isExternalName n ) 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 pred 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 ASSERT( isExternalName name )
+ 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 <- ASSERT( isExternalName name )
+ 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.lookupModule (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
diff --git a/ghc/ghc-bin.cabal.in b/ghc/ghc-bin.cabal.in
index 3374edf62d..df3b515386 100644
--- a/ghc/ghc-bin.cabal.in
+++ b/ghc/ghc-bin.cabal.in
@@ -28,15 +28,29 @@ Executable ghc
Main-Is: Main.hs
if flag(base3)
Build-Depends: base >= 3 && < 5,
- directory >= 1 && < 1.1
+ array >= 0.1 && < 0.3,
+ bytestring >= 0.9 && < 0.10,
+ directory >= 1 && < 1.1,
+ process >= 1 && < 1.1
else
Build-Depends: base < 3
Build-Depends: base, ghc
Build-Depends: filepath >= 1 && < 1.2
+ if os(windows)
+ Build-Depends: Win32
+ else
+ Build-Depends: unix
GHC-Options: -Wall
if flag(ghci)
CPP-Options: -DGHCI
+ GHC-Options: -fno-warn-name-shadowing
+ Other-Modules: InteractiveUI, GhciMonad, GhciTags
+ Build-Depends: mtl, haskeline
+ Extensions: ForeignFunctionInterface,
+ UnboxedTuples,
+ FlexibleInstances,
+ MagicHash
Extensions: CPP, PatternGuards
diff --git a/ghc/ghc.mk b/ghc/ghc.mk
index ccd4c5d685..3a3edec1ce 100644
--- a/ghc/ghc.mk
+++ b/ghc/ghc.mk
@@ -41,8 +41,8 @@ endif
ghc_stage1_MODULES = Main
-ghc_stage2_MODULES = $(ghc_stage1_MODULES)
-ghc_stage3_MODULES = $(ghc_stage1_MODULES)
+ghc_stage2_MODULES = $(ghc_stage1_MODULES) GhciMonad GhciTags InteractiveUI
+ghc_stage3_MODULES = $(ghc_stage2_MODULES)
ghc_stage1_PROG = ghc-stage1$(exeext)
ghc_stage2_PROG = ghc-stage2$(exeext)
@@ -53,10 +53,18 @@ ghc_stage1_USE_BOOT_LIBS = YES
ghc_stage1_HC_OPTS += -package $(compiler_PACKAGE)-$(compiler_stage1_VERSION)
ghc_stage2_HC_OPTS += -package $(compiler_PACKAGE)-$(compiler_stage2_VERSION)
ghc_stage3_HC_OPTS += -package $(compiler_PACKAGE)-$(compiler_stage3_VERSION)
-
-ghc_stage1_HC_OPTS += -XCPP -XPatternGuards
-ghc_stage2_HC_OPTS += -XCPP -XPatternGuards
-ghc_stage3_HC_OPTS += -XCPP -XPatternGuards
+ghc_stage2_HC_OPTS += -package haskeline
+ghc_stage3_HC_OPTS += -package haskeline
+
+ghc_language_extension_flags = -XCPP \
+ -XPatternGuards \
+ -XForeignFunctionInterface \
+ -XUnboxedTuples \
+ -XFlexibleInstances \
+ -XMagicHash
+ghc_stage1_HC_OPTS += $(ghc_language_extension_flags)
+ghc_stage2_HC_OPTS += $(ghc_language_extension_flags)
+ghc_stage3_HC_OPTS += $(ghc_language_extension_flags)
# In stage1 we might not benefit from cross-package dependencies and
# recompilation checking. We must force recompilation here, otherwise