packages feed

debugger-hs-0.1.0.0: src/Debugger/Builder.hs

-- NOTE: meant to be imported qualified
module Debugger.Builder
  ( Builder
  , ToSelection(..)
  , runBuilder
  , break
  , command
  , continue
  , step
  , stepN
  , next
  , nextN
  , run
  , reset
  , print
  , set
  , call
  , delete
  , disable
  , enable
  , source
  , shell
  , target
  , info
  ) where

import Prelude hiding (break, print)
import Control.Monad.State.Strict
import qualified Data.Text as T
import qualified Data.DList as DList
import Debugger.Statement


type Counter = Int

data BuilderState
  = BuilderState
  { stmts :: DList.DList Statement
  , varCounter :: Counter
  }

-- | Builder pattern that allows using monadic do-syntax to build a GDB script.
newtype Builder a
  = Builder (State BuilderState a)
  deriving (Functor, Applicative, Monad, MonadState BuilderState)
  via State BuilderState


-- | Creates a GDB script based on a builder.
runBuilder :: Builder a -> Script
runBuilder = runBuilder' 0

runBuilder' :: Counter -> Builder a -> [Statement]
runBuilder' counter (Builder m) =
  let result = execState m (BuilderState DList.empty counter)
   in DList.apply (stmts result) []

-- | Emits a breakpoint statement.
--   Returns the 'Id' that corresponds with this newly set breakpoint.
break :: Location -> Builder Id
break loc = do
  emit $ Break loc
  var <- newBreakpointVar
  set var "$bpnum"
  pure $ Id var

-- | Emits a command statement, that should be triggered when a breakpoint is triggered.
command :: Id -> Builder () -> Builder ()
command bp cmds = do
  counter <- gets varCounter
  let statements = runBuilder' counter cmds
  emit $ Command bp statements

-- | Emits a continue statement.
continue :: Builder ()
continue = emit Continue

-- | Emits a run statement.
run :: Builder ()
run = emit Run

-- | Emits a reset statement.
reset :: Builder ()
reset = emit Reset

-- | Emits a print statement.
print :: Expr -> Builder ()
print = emit . Print

-- | Emits a set statement.
set :: Var -> Expr -> Builder ()
set var value = emit $ Set var value

-- | Emits a call statement.
call :: Expr -> Builder ()
call = emit . Call

-- | Helper typeclass, used for manipulating 1, many, or all 'Id's.
class ToSelection a where
  toSelection :: a -> Selection

instance ToSelection Id where
  toSelection = Single

instance ToSelection [Id] where
  toSelection = Many

instance ToSelection Selection where
  toSelection = id

-- | Emits a delete statement.
delete :: ToSelection a => a -> Builder ()
delete = emit . Delete . toSelection

-- | Emits a disable statement.
disable :: ToSelection a => a -> Builder ()
disable = emit . Disable . toSelection

-- | Emits an enable statement.
enable = emit . Enable . toSelection
enable :: ToSelection a => a -> Builder ()

-- | Emits a "next" statement. If you want to repeat next N times, use 'nextN'.
next :: Builder ()
next = emit $ Next Nothing

-- | Emits a "next" statement that is repeated N times.
nextN :: Int -> Builder ()
nextN = emit . Next . Just

-- | Emits a step statement. If you want to step N times, use 'stepN'.
step :: Builder ()
step = emit $ Step Nothing

-- | Emits a step statement that is repeated N times.
stepN :: Int -> Builder ()
stepN = emit . Step . Just

-- | Emits a shell statement.
shell :: ShellCommand -> Builder ()
shell = emit . Shell

-- | Emits a source statement.
source :: FilePath -> Builder ()
source = emit . Source

-- | Emits a target statement.
target :: TargetConfig -> Builder ()
target = emit . Target

-- | Emits an info statement.
info :: InfoOptions -> Builder ()
info = emit . Info

emit :: Statement -> Builder ()
emit stmt =
  modify $ \s -> s { stmts = DList.snoc (stmts s) stmt }

newBreakpointVar :: Builder Var
newBreakpointVar = do
  currentId <- gets varCounter
  modify $ \s -> s { varCounter = varCounter s + 1 }
  pure $ "$var" <> T.pack (show currentId)