|
| 1 | +{-# LANGUAGE LambdaCase #-} |
| 2 | +{-# LANGUAGE RecordWildCards #-} |
| 3 | +{-# LANGUAGE OverloadedStrings #-} |
| 4 | +module Breakpoints where |
| 5 | + |
| 6 | +import Text.Read ( readMaybe ) |
| 7 | +import Data.Maybe ( fromMaybe, maybeToList ) |
| 8 | +import Data.List ( sortOn ) |
| 9 | +import Control.Monad |
| 10 | +import Data.String.Conversions (cs) |
| 11 | +import qualified Data.Text as T |
| 12 | +import qualified Data.Bimap as Bimap |
| 13 | +import qualified Data.IntSet as IntSet |
| 14 | +import qualified Data.Map.Strict as Map |
| 15 | +import qualified Stg.Interpreter.Base as Stg |
| 16 | +import Stg.Interpreter.Base hiding (lookupEnv, getCurrentThreadState, Breakpoint) |
| 17 | +import Stg.Syntax hiding (sourceName, Scope) |
| 18 | +import Stg.IRLocation |
| 19 | + |
| 20 | +import DAP |
| 21 | +import DapBase |
| 22 | +import SourceCode |
| 23 | + |
| 24 | +---------------------------------------------------------------------------- |
| 25 | +-- | Clears the currently known breakpoint set |
| 26 | +clearBreakpoints :: Adaptor ESTG () |
| 27 | +clearBreakpoints = do |
| 28 | + updateDebugSession $ \estg -> estg {breakpointMap = mempty} |
| 29 | + |
| 30 | +---------------------------------------------------------------------------- |
| 31 | +-- | Adds new BreakpointId for a givent StgPoint |
| 32 | +addNewBreakpoint :: Stg.Breakpoint -> Adaptor ESTG BreakpointId |
| 33 | +addNewBreakpoint breakpoint = do |
| 34 | + bkpId <- getFreshBreakpointId |
| 35 | + updateDebugSession $ \estg@ESTG{..} -> estg {breakpointMap = Map.insertWith mappend breakpoint (IntSet.singleton bkpId) breakpointMap} |
| 36 | + pure bkpId |
| 37 | + |
| 38 | +commandSetBreakpoints :: Adaptor ESTG () |
| 39 | +commandSetBreakpoints = do |
| 40 | + SetBreakpointsArguments {..} <- getArguments |
| 41 | + maybeSourceRef <- getValidSourceRefFromSource setBreakpointsArgumentsSource |
| 42 | + |
| 43 | + -- the input SourceRef might be a remain of a previous DAP session, update it with the new valid one |
| 44 | + let refUpdatedSource = setBreakpointsArgumentsSource { sourceSourceReference = maybeSourceRef } |
| 45 | + |
| 46 | + clearBreakpoints |
| 47 | + {- |
| 48 | + supports placing breakpoint on: |
| 49 | + - Haskell |
| 50 | + - ExtStg |
| 51 | + -} |
| 52 | + ESTG {..} <- getDebugSession |
| 53 | + case (setBreakpointsArgumentsBreakpoints, maybeSourceRef, maybeSourceRef >>= flip Bimap.lookupR dapSourceRefMap) of |
| 54 | + -- HINT: breakpoint on Haskell |
| 55 | + (Just sourceBreakpoints, Just sourceRef, Just hsCodeDesc@(Haskell pkg mod)) |
| 56 | + | Just extStgSourceRef <- Bimap.lookup (ExtStg pkg mod) dapSourceRefMap |
| 57 | + , Just hsSourceFilePath <- Bimap.lookupR hsCodeDesc haskellSrcPathMap |
| 58 | + -> do |
| 59 | + (_sourceCodeText, _locations, hsSrcLocs) <- getSourceFromFullPak extStgSourceRef |
| 60 | + breakpoints <- forM sourceBreakpoints $ \SourceBreakpoint{..} -> do |
| 61 | + -- filter all relevant ranges |
| 62 | + {- |
| 63 | + SP_RhsClosureExpr |
| 64 | + -} |
| 65 | + let onlySupported = \case |
| 66 | + SP_RhsClosureExpr{} -> True |
| 67 | + _ -> True -- TODO |
| 68 | + let relevantLocations = filter (onlySupported . fst . fst) $ case sourceBreakpointColumn of |
| 69 | + Nothing -> |
| 70 | + [ (p, spanSize) |
| 71 | + | p@(_,SourceNote RealSrcSpan'{..} _) <- hsSrcLocs |
| 72 | + , srcSpanFile == hsSourceFilePath |
| 73 | + , srcSpanSLine <= sourceBreakpointLine |
| 74 | + , srcSpanELine >= sourceBreakpointLine |
| 75 | + , let spanSize = (srcSpanELine - srcSpanSLine, srcSpanECol - srcSpanSCol) |
| 76 | + ] |
| 77 | + Just col -> |
| 78 | + [ (p, spanSize) |
| 79 | + | p@(_,SourceNote RealSrcSpan'{..} _) <- hsSrcLocs |
| 80 | + , srcSpanFile == hsSourceFilePath |
| 81 | + , srcSpanSLine <= sourceBreakpointLine |
| 82 | + , srcSpanELine >= sourceBreakpointLine |
| 83 | + , srcSpanSCol <= col |
| 84 | + , srcSpanECol >= col |
| 85 | + , let spanSize = (srcSpanELine - srcSpanSLine, srcSpanECol - srcSpanSCol) |
| 86 | + ] |
| 87 | + debugMessage . cs . unlines $ "relevant haskell locations:" : map show relevantLocations |
| 88 | + -- use the first location found |
| 89 | + -- HINT: locations are sorted according the span size, small spans are preferred more |
| 90 | + case map fst . take 1 $ sortOn snd relevantLocations of |
| 91 | + (stgPoint@(SP_RhsClosureExpr _closureName), SourceNote RealSrcSpan'{..} _) : _ -> do |
| 92 | + let hitCount = fromMaybe 0 (sourceBreakpointHitCondition >>= readMaybe . T.unpack) :: Int |
| 93 | + sendAndWait (CmdAddBreakpoint (BkpStgPoint stgPoint) hitCount) |
| 94 | + bkpId <- addNewBreakpoint $ BkpStgPoint stgPoint |
| 95 | + pure $ defaultBreakpoint |
| 96 | + { breakpointVerified = True |
| 97 | + , breakpointSource = Just refUpdatedSource |
| 98 | + , breakpointLine = Just srcSpanSLine |
| 99 | + , breakpointColumn = Just srcSpanSCol |
| 100 | + , breakpointEndLine = Just srcSpanELine |
| 101 | + , breakpointEndColumn = Just srcSpanECol |
| 102 | + , breakpointId = Just bkpId |
| 103 | + } |
| 104 | + _ -> |
| 105 | + pure $ defaultBreakpoint |
| 106 | + { breakpointVerified = False |
| 107 | + , breakpointSource = Just refUpdatedSource |
| 108 | + , breakpointMessage = Just "no hs code found" |
| 109 | + } |
| 110 | + sendSetBreakpointsResponse breakpoints |
| 111 | + |
| 112 | + -- HINT: breakpoint on ExtStg |
| 113 | + (Just sourceBreakpoints, Just sourceRef, Just ExtStg{}) -> do |
| 114 | + (_sourceCodeText, locations, _hsSrcLocs) <- getSourceFromFullPak sourceRef |
| 115 | + breakpoints <- forM sourceBreakpoints $ \SourceBreakpoint{..} -> do |
| 116 | + -- filter all relevant ranges |
| 117 | + {- |
| 118 | + SP_RhsClosureExpr |
| 119 | + -} |
| 120 | + let onlySupported = \case |
| 121 | + SP_RhsClosureExpr{} -> True |
| 122 | + _ -> False |
| 123 | + let relevantLocations = filter (onlySupported . fst) $ case sourceBreakpointColumn of |
| 124 | + Nothing -> |
| 125 | + [ p |
| 126 | + | p@(_,((startRow, startCol), (endRow, endCol))) <- locations |
| 127 | + , startRow <= sourceBreakpointLine |
| 128 | + , endRow >= sourceBreakpointLine |
| 129 | + ] |
| 130 | + Just col -> |
| 131 | + [ p |
| 132 | + | p@(_,((startRow, startCol), (endRow, endCol))) <- locations |
| 133 | + , startRow <= sourceBreakpointLine |
| 134 | + , endRow >= sourceBreakpointLine |
| 135 | + , startCol <= col |
| 136 | + , endCol >= col |
| 137 | + ] |
| 138 | + debugMessage . cs $ "relevantLocations: " ++ show relevantLocations |
| 139 | + -- use the first location found |
| 140 | + case sortOn snd relevantLocations of |
| 141 | + (stgPoint@(SP_RhsClosureExpr _closureName), ((startRow, startCol), (endRow, endCol))) : _ -> do |
| 142 | + let hitCount = fromMaybe 0 (sourceBreakpointHitCondition >>= readMaybe . T.unpack) :: Int |
| 143 | + sendAndWait (CmdAddBreakpoint (BkpStgPoint stgPoint) hitCount) |
| 144 | + bkpId <- addNewBreakpoint $ BkpStgPoint stgPoint |
| 145 | + pure $ defaultBreakpoint |
| 146 | + { breakpointVerified = True |
| 147 | + , breakpointSource = Just refUpdatedSource |
| 148 | + , breakpointLine = Just startRow |
| 149 | + , breakpointColumn = Just startCol |
| 150 | + , breakpointEndLine = Just endRow |
| 151 | + , breakpointEndColumn = Just endCol |
| 152 | + , breakpointId = Just bkpId |
| 153 | + } |
| 154 | + _ -> |
| 155 | + pure $ defaultBreakpoint |
| 156 | + { breakpointVerified = False |
| 157 | + , breakpointSource = Just refUpdatedSource |
| 158 | + , breakpointMessage = Just "no code found" |
| 159 | + } |
| 160 | + sendSetBreakpointsResponse breakpoints |
| 161 | + v -> do |
| 162 | + sendSetBreakpointsResponse [] |
0 commit comments