diff options
author | Roland Senn <rsx@bluewin.ch> | 2019-05-14 09:45:36 +0200 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2019-06-09 18:44:18 -0400 |
commit | 10452959136fbf271ac21eb0740030c046db36e1 (patch) | |
tree | 5772061b3ef4c6e9f4ee421aeae80986cfad8833 /ghc | |
parent | a22e51ea6f7a046c87d57ce30d143eef6abee9ff (diff) | |
download | haskell-10452959136fbf271ac21eb0740030c046db36e1.tar.gz |
Add disable/enable commands to ghci debugger #2215
This patch adds two new commands `:enable` and `:disable` to the GHCi debugger.
Opposite to `:set stop <n> :continue` a breakpoint disabled with `:disable` will
not loose its previously set stop command.
A new field breakEnabled is added to the BreakLocation data structure to
track the enable/disable state. When a breakpoint is disabled with a `:disable`
command, the following happens:
The corresponding BreakLocation data element is searched dictionary of the
`breaks` field of the GHCiStateMonad. If the break point is found and not
already in the disabled state, the breakpoint is removed from bytecode.
The BreakLocation data structure is kept in the breaks list and the new
breakEnabled field is set to false.
The `:enable` command works similar.
The breaks field in the GHCiStateMonad was changed from an association list
to int `IntMap`.
Diffstat (limited to 'ghc')
-rw-r--r-- | ghc/GHCi/UI.hs | 111 | ||||
-rw-r--r-- | ghc/GHCi/UI/Monad.hs | 22 |
2 files changed, 101 insertions, 32 deletions
diff --git a/ghc/GHCi/UI.hs b/ghc/GHCi/UI.hs index 7b64644526..ab3992ccd0 100644 --- a/ghc/GHCi/UI.hs +++ b/ghc/GHCi/UI.hs @@ -108,6 +108,7 @@ import qualified Data.Set as S import Data.Maybe import Data.Map (Map) import qualified Data.Map as M +import qualified Data.IntMap.Strict as IntMap import Data.Time.LocalTime ( getZonedTime ) import Data.Time.Format ( formatTime, defaultTimeLocale ) import Data.Version ( showVersion ) @@ -187,8 +188,10 @@ ghciCommands = map mkCmd [ ("def", keepGoing (defineMacro False), completeExpression), ("def!", keepGoing (defineMacro True), completeExpression), ("delete", keepGoing deleteCmd, noCompletion), + ("disable", keepGoing disableCmd, noCompletion), ("doc", keepGoing' docCmd, completeIdentifier), ("edit", keepGoing' editFile, completeFilename), + ("enable", keepGoing enableCmd, noCompletion), ("etags", keepGoing createETagsFileCmd, completeFilename), ("force", keepGoing forceCmd, completeExpression), ("forward", keepGoing forwardCmd, noCompletion), @@ -331,8 +334,12 @@ defFullHelpText = " :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 <number> ... delete the specified breakpoints\n" ++ " :delete * delete all breakpoints\n" ++ + " :disable <number> ... disable the specified breakpoints\n" ++ + " :disable * disable all breakpoints\n" ++ + " :enable <number> ... enable the specified breakpoints\n" ++ + " :enable * enable all breakpoints\n" ++ " :force <expr> print <expr>, forcing unevaluated parts\n" ++ " :forward [<n>] go forward in the history N step s(after :back)\n" ++ " :history [<n>] after :trace, show the execution history\n" ++ @@ -493,7 +500,7 @@ interactiveUI config srcs maybe_exprs = do -- incremented after reading a line. line_number = 0, break_ctr = 0, - breaks = [], + breaks = IntMap.empty, tickarrays = emptyModuleEnv, ghci_commands = availableCommands config, ghci_macros = [], @@ -1300,7 +1307,7 @@ toBreakIdAndLocation (Just inf) = do let md = GHC.breakInfo_module inf nm = GHC.breakInfo_number inf st <- getGHCiState - return $ listToMaybe [ id_loc | id_loc@(_,loc) <- breaks st, + return $ listToMaybe [ id_loc | id_loc@(_,loc) <- IntMap.assocs (breaks st), breakModule loc == md, breakTick loc == nm ] @@ -2813,14 +2820,14 @@ setStop str@(c:_) | isDigit c 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 } + case IntMap.lookup nm old_breaks of + Nothing -> printForUser (text "Breakpoint" <+> ppr nm <+> + text "does not exist") + Just loc -> do + let new_breaks = IntMap.insert nm + loc { onBreakCmd = dropWhile isSpace rest } + old_breaks + setGHCiState st{ breaks = new_breaks } setStop cmd = modifyGHCiState (\st -> st { stop = cmd }) setPrompt :: GhciMonad m => PromptFunction -> m () @@ -3521,6 +3528,56 @@ deleteCmd argLine = withSandboxOnly ":delete" $ do | all isDigit str = deleteBreak (read str) | otherwise = return () +enableCmd :: GhciMonad m => String -> m () +enableCmd argLine = withSandboxOnly ":enable" $ do + enaDisaSwitch True $ words argLine + +disableCmd :: GhciMonad m => String -> m () +disableCmd argLine = withSandboxOnly ":disable" $ do + enaDisaSwitch False $ words argLine + +enaDisaSwitch :: GhciMonad m => Bool -> [String] -> m () +enaDisaSwitch enaDisa [] = + printForUser (text "The" <+> text strCmd <+> + text "command requires at least one argument.") + where + strCmd = if enaDisa then ":enable" else ":disable" +enaDisaSwitch enaDisa ("*" : _) = enaDisaAllBreaks enaDisa +enaDisaSwitch enaDisa idents = do + mapM_ (enaDisaOneBreak enaDisa) idents + where + enaDisaOneBreak :: GhciMonad m => Bool -> String -> m () + enaDisaOneBreak enaDisa strId = do + sdoc_loc <- getBreakLoc enaDisa strId + case sdoc_loc of + Left sdoc -> printForUser sdoc + Right loc -> enaDisaAssoc enaDisa (read strId, loc) + +getBreakLoc :: GhciMonad m => Bool -> String -> m (Either SDoc BreakLocation) +getBreakLoc enaDisa strId = do + st <- getGHCiState + case readMaybe strId >>= flip IntMap.lookup (breaks st) of + Nothing -> return $ Left (text "Breakpoint" <+> text strId <+> + text "not found") + Just loc -> + if breakEnabled loc == enaDisa + then return $ Left + (text "Breakpoint" <+> text strId <+> + text "already in desired state") + else return $ Right loc + +enaDisaAssoc :: GhciMonad m => Bool -> (Int, BreakLocation) -> m () +enaDisaAssoc enaDisa (intId, loc) = do + st <- getGHCiState + newLoc <- turnBreakOnOff enaDisa loc + let new_breaks = IntMap.insert intId newLoc (breaks st) + setGHCiState $ st { breaks = new_breaks } + +enaDisaAllBreaks :: GhciMonad m => Bool -> m() +enaDisaAllBreaks enaDisa = do + st <- getGHCiState + mapM_ (enaDisaAssoc enaDisa) $ IntMap.assocs $ breaks st + historyCmd :: GHC.GhcMonad m => String -> m () historyCmd arg | null arg = history 20 @@ -3648,6 +3705,7 @@ findBreakAndSet md lookupTickTree = do , breakLoc = RealSrcSpan pan , breakTick = tick , onBreakCmd = "" + , breakEnabled = True } printForUser $ text "Breakpoint " <> ppr nm <> @@ -3913,26 +3971,29 @@ mkTickArray ticks discardActiveBreakPoints :: GhciMonad m => m () discardActiveBreakPoints = do st <- getGHCiState - mapM_ (turnOffBreak.snd) (breaks st) - setGHCiState $ st { breaks = [] } + mapM_ (turnBreakOnOff False) $ breaks st + setGHCiState $ st { breaks = IntMap.empty } deleteBreak :: GhciMonad m => Int -> m () 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 + let oldLocations = breaks st + case IntMap.lookup identity oldLocations of + Nothing -> printForUser (text "Breakpoint" <+> ppr identity <+> + text "does not exist") + Just loc -> do + _ <- (turnBreakOnOff False) loc + let rest = IntMap.delete identity oldLocations setGHCiState $ st { breaks = rest } -turnOffBreak :: GHC.GhcMonad m => BreakLocation -> m () -turnOffBreak loc = do - (arr, _) <- getModBreak (breakModule loc) - hsc_env <- GHC.getSession - liftIO $ enableBreakpoint hsc_env arr (breakTick loc) False +turnBreakOnOff :: GHC.GhcMonad m => Bool -> BreakLocation -> m BreakLocation +turnBreakOnOff onOff loc + | onOff == breakEnabled loc = return loc + | otherwise = do + (arr, _) <- getModBreak (breakModule loc) + hsc_env <- GHC.getSession + liftIO $ enableBreakpoint hsc_env arr (breakTick loc) onOff + return loc { breakEnabled = onOff } getModBreak :: GHC.GhcMonad m => Module -> m (ForeignRef BreakArray, Array Int SrcSpan) diff --git a/ghc/GHCi/UI/Monad.hs b/ghc/GHCi/UI/Monad.hs index 696303b949..6ecb079ea0 100644 --- a/ghc/GHCi/UI/Monad.hs +++ b/ghc/GHCi/UI/Monad.hs @@ -66,6 +66,7 @@ import qualified System.Console.Haskeline as Haskeline import Control.Monad.Trans.Class import Control.Monad.IO.Class import Data.Map.Strict (Map) +import qualified Data.IntMap.Strict as IntMap import qualified GHC.LanguageExtensions as LangExt ----------------------------------------------------------------------------- @@ -84,7 +85,7 @@ data GHCiState = GHCiState options :: [GHCiOption], line_number :: !Int, -- ^ input line break_ctr :: !Int, - breaks :: ![(Int, BreakLocation)], + breaks :: !(IntMap.IntMap BreakLocation), tickarrays :: ModuleEnv TickArray, -- ^ 'tickarrays' caches the 'TickArray' for loaded modules, -- so that we don't rebuild it each time the user sets @@ -213,6 +214,7 @@ data BreakLocation { breakModule :: !GHC.Module , breakLoc :: !SrcSpan , breakTick :: {-# UNPACK #-} !Int + , breakEnabled:: !Bool , onBreakCmd :: String } @@ -220,21 +222,27 @@ 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 +prettyLocations :: IntMap.IntMap BreakLocation -> SDoc +prettyLocations locs = + case IntMap.null locs of + True -> text "No active breakpoints." + False -> vcat $ map (\(i, loc) -> brackets (int i) <+> ppr loc) $ IntMap.toAscList locs instance Outputable BreakLocation where - ppr loc = (ppr $ breakModule loc) <+> ppr (breakLoc loc) <+> + ppr loc = (ppr $ breakModule loc) <+> ppr (breakLoc loc) <+> pprEnaDisa <+> if null (onBreakCmd loc) then Outputable.empty else doubleQuotes (text (onBreakCmd loc)) + where pprEnaDisa = case breakEnabled loc of + True -> text "enabled" + False -> text "disabled" recordBreak :: GhciMonad m => BreakLocation -> m (Bool{- was already present -}, Int) recordBreak brkLoc = do st <- getGHCiState - let oldActiveBreaks = breaks st + let oldmap = breaks st + oldActiveBreaks = IntMap.assocs oldmap -- don't store the same break point twice case [ nm | (nm, loc) <- oldActiveBreaks, loc == brkLoc ] of (nm:_) -> return (True, nm) @@ -242,7 +250,7 @@ recordBreak brkLoc = do let oldCounter = break_ctr st newCounter = oldCounter + 1 setGHCiState $ st { break_ctr = newCounter, - breaks = (oldCounter, brkLoc) : oldActiveBreaks + breaks = IntMap.insert oldCounter brkLoc oldmap } return (False, oldCounter) |