packages feed

debugger-hs (empty) → 0.1.0.0

raw patch · 11 files changed

+987/−0 lines, 11 filesdep +basedep +debugger-hsdep +dlistsetup-changed

Dependencies added: base, debugger-hs, dlist, hspec, mtl, neat-interpolation, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Luc Tielen (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Luc Tielen nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,84 @@+# debugger-hs++A Haskell library for creating/metaprogramming GDB scripts.++## Why?++GDB has Python integration, and while it is powerful, it has some problems:++- Python code can end up interleaved in the GDB code, making it complicated+  really quickly.+- Accessing variables between GDB and Python can get complicated too.++Besides that, also some things in GDB don't work as expected, such as commands+that behave differently inside a user-defined command ("define"), which can lead+to unexpected and buggy results.++If we use Haskell however, we get the following nice things:++- A typesystem,+- Can use full Haskell ecosystem for metaprogramming,+- The generated output will be 100% GDB script only:+  - easier to use afterwards,+  - inspectable,+  - no need for this package once the script has been generated.++## How?++Partial evaluation/staged programming. You write a Haskell file that makes use+of the DSL this package provides, and render it to a script that can be+passed in to GDB like you would normally.++Here's an example of how you can use this library to generate a GDB script:++```haskell+module Main where++import qualified Debugger.Builder as D+import qualified Debugger.Render as D+import qualified Debugger.Statement as D++-- First we build up a Haskell value that represents our GDB scripts.+script :: D.Builder ()+script = do+  bp <- D.break (D.Function "main")+  D.command bp $ do+    D.print "42"+    D.continue++-- And then we can render the GDB script to a file:+main :: IO ()+main = do+  let gdbScript = D.runBuilder script+  D.renderIO gdbScript "./script.gdb"+```++This will render the following GDB scripts:++```gdb+break main+set $var0 = $bpnum+command $var0+  print "42"+  continue+end+```++```bash+# Replace $PROGRAM with the program you want to debug.+$ gdb $PROGRAM <<< $(< /path/to/stack-script)+# or if your shell doesn't support the previous command:+$ /path/to/stack-script > ./script.gdb+$ gdb $PROGRAM -ex "source ./script.gdb"+```++## TODO++- [x] Create core AST datatype+- [x] Create builder monad for easily constructing GDB scripts using Haskell do-syntax.+- [x] Write function for compiling AST -> GDB script+- [ ] Create CLI application (stack/nix?) that takes the DSL and outputs GDB to stdout.+- [ ] Add helper functions for easily adding breakpoints (using tools like grep)+- [ ] Extend core AST datatype to support more functionality+- [ ] Add LLDB support also?+- [ ] DSL (to support GDB and LLDB at same time)?
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ debugger-hs.cabal view
@@ -0,0 +1,76 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack+--+-- hash: ab9171f6a3d676dafad59e27a5c0982d34707fbc6d4ed681346b09e602b2946c++name:           debugger-hs+version:        0.1.0.0+synopsis:       Write your GDB scripts in Haskell.+description:    Please see the README on GitHub at <https://github.com/luc-tielen/debugger-hs#readme>+category:       debugging+homepage:       https://github.com/luc-tielen/debugger-hs#readme+bug-reports:    https://github.com/luc-tielen/debugger-hs/issues+author:         Luc Tielen+maintainer:     luc.tielen@gmail.com+copyright:      Luc Tielen, 2021+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/luc-tielen/debugger-hs++library+  exposed-modules:+      Debugger.Builder+      Debugger.Render+      Debugger.Statement+      Lib+  other-modules:+      Paths_debugger_hs+  hs-source-dirs:+      src+  default-extensions:+      DerivingVia+      LambdaCase+      OverloadedStrings+      FlexibleContexts+      FlexibleInstances+  ghc-options: -Wall+  build-depends:+      base >=4.7 && <5+    , dlist ==1.*+    , mtl ==2.*+    , text ==1.*+  default-language: Haskell2010++test-suite debugger-hs-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Test.Debugger.BuilderSpec+      Test.Debugger.RenderSpec+      Paths_debugger_hs+  hs-source-dirs:+      test+  default-extensions:+      DerivingVia+      LambdaCase+      OverloadedStrings+      FlexibleContexts+      FlexibleInstances+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , debugger-hs+    , hspec >=2.6.1 && <3.0.0+    , mtl ==2.*+    , neat-interpolation ==0.*+    , text ==1.*+  default-language: Haskell2010
+ src/Debugger/Builder.hs view
@@ -0,0 +1,164 @@+-- 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)+
+ src/Debugger/Render.hs view
@@ -0,0 +1,104 @@+module Debugger.Render+  ( renderIO+  , renderScript+  ) where++import Control.Monad.Reader+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Debugger.Statement+++-- | Renders a GDB script and writes it to the given file path.+renderIO :: Script -> FilePath -> IO ()+renderIO script path =+  let txt = renderScript script+   in TIO.writeFile path txt++-- | Renders a GDB script+renderScript :: Script -> T.Text+renderScript script =+  interleaveNewlines $ map render script++render :: Statement -> T.Text+render stmt = runReader (go stmt) 0+  where+    go = \case+      Break loc ->+        pure $ "break " <> renderLoc loc+      Command bp stmts -> do+        block <- local (+2) $ traverse (indent <=< go) stmts+        -- need to indent the end explicitly, because lines will be joined+        -- together before returning and only start is indented+        end <- indent "end"+        pure $ interleaveNewlines+          [ "command " <> renderId bp+          , interleaveNewlines block+          , end+          ]+      Continue ->+        pure "continue"+      Next count ->+        pure $ "next" <> renderCount count+      Step count ->+        pure $ "step" <> renderCount count+      Run ->+        pure "run"+      Reset ->+        pure "monitor reset"+      Set var expr ->+        pure $ T.unwords ["set", var, "=", expr]+      Call expr ->+        pure $ T.unwords ["call", expr]+      Print val ->+        pure $ "print " <> "\"" <> val <> "\""+      Delete sel ->+        pure $ T.strip $ "delete " <> renderSelection sel+      Disable sel ->+        pure $ T.strip $ "disable " <> renderSelection sel+      Enable sel ->+        pure $ T.strip $ "enable " <> renderSelection sel+      Shell cmd ->+        pure $ "shell " <> cmd+      Source file ->+        pure $ "source " <> T.pack file+      Target target ->+        pure $ "target " <> renderTargetConfig target+      Info opts ->+        pure $ "info " <> renderInfoOpts opts++    indent txt = do+      spaces <- ask+      let indentation = T.replicate spaces " "+      pure $ indentation <> txt+++renderId :: Id -> T.Text+renderId (Id txt) = txt++renderLoc :: Location -> T.Text+renderLoc = \case+  Function func -> func+  File path line -> T.pack path <> ":" <> T.pack (show line)++renderSelection :: Selection -> T.Text+renderSelection = \case+  Single bp -> renderId bp+  Many bps -> T.intercalate " " $ map renderId bps+  All -> ""++renderTargetConfig :: TargetConfig -> T.Text+renderTargetConfig = \case+  Remote port -> "remote tcp:localhost:" <> T.pack (show port)++renderInfoOpts :: InfoOptions -> T.Text+renderInfoOpts = \case+  Breakpoints -> "breakpoints"++renderCount :: Maybe Int -> T.Text+renderCount = \case+  Nothing -> ""+  Just x -> " " <> T.pack (show x)++interleaveNewlines :: [T.Text] -> T.Text+interleaveNewlines txts = T.intercalate "\n" txts
+ src/Debugger/Statement.hs view
@@ -0,0 +1,87 @@+module Debugger.Statement+  ( Line+  , Location(..)+  , Var+  , Expr+  , ShellCommand+  , Id(..)+  , Selection(..)+  , Port+  , TargetConfig(..)+  , InfoOptions(..)+  , Statement(..)+  , Script+  ) where++import Data.Text (Text)++type Var = Text  -- TODO different type?++type Expr = Text  -- TODO different type?++type ShellCommand = Text++-- | Datatype representing a ID of a breakpoint.+newtype Id = Id Text+  deriving (Eq, Show)++type Line = Int++-- | Helper datatype for a selection of 1 or more (breakpoints)+data Selection+  = Single Id+  | Many [Id]+  | All+  deriving (Eq, Show)++type Port = Int++-- TODO: add other variants+-- | Datatype for configuring the GDB target.+data TargetConfig+  = Remote Port  -- assumes tcp as protocol, and localhost as host for now+  deriving (Eq, Show)++-- | Enumeration of all things info can be requested about.+data InfoOptions+  = Breakpoints+  deriving (Eq, Show)++-- | A place to set a breakpoint at.+data Location+  = Function Text+  | File FilePath Line+  deriving (Eq, Show)++-- | Main AST data type used to build a script with.+data Statement+  = Break Location+  | Command Id [Statement]+  | Continue+  | Step (Maybe Int)+  | Next (Maybe Int)+  | Run+  | Reset+  | Delete Selection+  | Enable Selection+  | Disable Selection+  | Shell ShellCommand+  | Source FilePath+  | Print Expr+  | Set Var Expr+  | Call Expr+  | Target TargetConfig+  | Info InfoOptions+  deriving (Eq, Show)++-- | A script is a collection of statements.+type Script = [Statement]++  {-+TODO+data Statement+  = Break Location -- hbreak? conditional breakpoints?+  | Printf Text [Expr]+  | If Expr [Statement]+  -- set logging on (opts), ...+-}
+ src/Lib.hs view
@@ -0,0 +1,6 @@+module Lib+    ( someFunc+    ) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ test/Spec.hs view
@@ -0,0 +1,2 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-}+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Test/Debugger/BuilderSpec.hs view
@@ -0,0 +1,195 @@+module Test.Debugger.BuilderSpec+  ( module Test.Debugger.BuilderSpec+  ) where++import Prelude hiding (break, print)+import Data.Foldable+import Test.Hspec+import Debugger.Builder+import Debugger.Statement+++spec :: Spec+spec = describe "script builder" $ parallel $ do+  it "handles empty scripts" $ do+    runBuilder (pure ()) `shouldBe` []++  it "maintains order of statements" $ do+    let script = do+          print "1"+          print "2"+    runBuilder script `shouldBe` [Print "1", Print "2"]++  it "can emit break statements" $ do+    let script loc = do+          break loc+    for_ [Function "main", File "file.c" 42] $ \loc ->+      runBuilder (script loc) `shouldBe` [Break loc, Set "$var0" "$bpnum"]++  it "can emit command statements" $ do+    let script = do+          bp <- break (Function "func1")+          command bp $ do+            print "123"+            print "456"+    runBuilder script `shouldBe`+      [ Break (Function "func1")+      , Set "$var0" "$bpnum"+      , Command (Id "$var0") [Print "123", Print "456"]+      ]++  it "returns correct breakpoint variable for use in commands" $ do+    let script = do+          _ <- break (Function "func1")+          bp2 <- break (Function "func2")+          command bp2 $ do+            print "42"+    runBuilder script `shouldBe`+      [ Break (Function "func1")+      , Set "$var0" "$bpnum"+      , Break (Function "func2")+      , Set "$var1" "$bpnum"+      , Command (Id "$var1") [Print "42"]+      ]++  it "can handle nested commands" $ do+    let script = do+          bp1 <- break (Function "func1")+          command bp1 $ do+            bp2 <- break (Function "func2")+            command bp2 $ do+              print "42"+              continue+    runBuilder script `shouldBe`+      [ Break (Function "func1")+      , Set "$var0" "$bpnum"+      , Command (Id "$var0")+        [ Break (Function "func2")+        , Set "$var1" "$bpnum"+        , Command (Id "$var1")+          [ Print "42"+          , Continue+          ]+        ]+      ]++  it "can emit continue statements" $ do+    let script = continue+    runBuilder script `shouldBe` [Continue]++  it "can emit next statements" $ do+    let script1 = next+        script2 = nextN 10+    runBuilder script1 `shouldBe` [Next Nothing]+    runBuilder script2 `shouldBe` [Next (Just 10)]++  it "can emit step statements" $ do+    let script1 = step+        script2 = stepN 10+    runBuilder script1 `shouldBe` [Step Nothing]+    runBuilder script2 `shouldBe` [Step (Just 10)]++  it "can emit run statements" $ do+    let script = run+    runBuilder script `shouldBe` [Run]++  it "can emit reset statements" $ do+    let script = reset+    runBuilder script `shouldBe` [Reset]++  it "can emit set statements" $ do+    let script = set "$var" "123"+    runBuilder script `shouldBe` [Set "$var" "123"]++  it "can emit call statements" $ do+    let script = call "my_function(123, 456)"+    runBuilder script `shouldBe` [Call "my_function(123, 456)"]++  it "can emit delete statements" $ do+    let script1 = do+          bp <- break (Function "main")+          delete bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          delete [bp1, bp2]+        script3 = delete All+    runBuilder script1 `shouldBe`+      [ Break (Function "main")+      , Set "$var0" "$bpnum"+      , Delete $ Single (Id "$var0")+      ]+    runBuilder script2 `shouldBe`+      [ Break (Function "f")+      , Set "$var0" "$bpnum"+      , Break (Function "g")+      , Set "$var1" "$bpnum"+      , Delete $ Many [Id "$var0", Id "$var1"]+      ]+    runBuilder script3 `shouldBe` [Delete All]++  it "can emit disable statements" $ do+    let script1 = do+          bp <- break (Function "main")+          disable bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          disable [bp1, bp2]+        script3 = disable All+    runBuilder script1 `shouldBe`+      [ Break (Function "main")+      , Set "$var0" "$bpnum"+      , Disable $ Single (Id "$var0")+      ]+    runBuilder script2 `shouldBe`+      [ Break (Function "f")+      , Set "$var0" "$bpnum"+      , Break (Function "g")+      , Set "$var1" "$bpnum"+      , Disable $ Many [Id "$var0", Id "$var1"]+      ]+    runBuilder script3 `shouldBe` [Disable All]++  it "can emit disable statements" $ do+    let script1 = do+          bp <- break (Function "main")+          enable bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          enable [bp1, bp2]+        script3 = enable All+    runBuilder script1 `shouldBe`+      [ Break (Function "main")+      , Set "$var0" "$bpnum"+      , Enable $ Single (Id "$var0")+      ]+    runBuilder script2 `shouldBe`+      [ Break (Function "f")+      , Set "$var0" "$bpnum"+      , Break (Function "g")+      , Set "$var1" "$bpnum"+      , Enable $ Many [Id "$var0", Id "$var1"]+      ]+    runBuilder script3 `shouldBe` [Enable All]++  it "can emit print statements" $ do+    let script = print "1"+    runBuilder script `shouldBe` [Print "1"]++  it "can emit source statements" $ do+    let script = source "./other_script.gdb"+    runBuilder script `shouldBe` [Source "./other_script.gdb"]++  it "can emit shell statements" $ do+    let script = shell "sleep 1"+    runBuilder script `shouldBe` [Shell "sleep 1"]++  it "can emit target statements" $ do+    let script = target (Remote 9001)+    runBuilder script `shouldBe` [Target (Remote 9001)]++  it "can emit info statements" $ do+    let script = info Breakpoints+    runBuilder script `shouldBe` [Info Breakpoints]
+ test/Test/Debugger/RenderSpec.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE QuasiQuotes #-}++module Test.Debugger.RenderSpec+  ( module Test.Debugger.RenderSpec+  ) where++import Prelude hiding (break, print)+import qualified Data.Text as T+import Test.Hspec+import NeatInterpolation+import Debugger.Builder+import Debugger.Render+import Debugger.Statement+import Control.Monad.Cont+++-- NOTE: 2 $s is to escape the quasiquoter, renders as 1 $+++shouldRenderAs :: Builder a -> T.Text -> IO ()+shouldRenderAs script expected =+  renderScript (runBuilder script) `shouldBe` expected++spec :: Spec+spec = describe "rendering scripts" $ parallel $ do+  it "handles empty scripts" $ do+    pure () `shouldRenderAs` ""++  it "renders break statements" $ do+    break (Function "main") `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      |]+    break (File "./a/b.c" 42) `shouldRenderAs` [text|+      break ./a/b.c:42+      set $$var0 = $$bpnum+      |]++  it "renders command statements" $ do+    let script = do+          bp <- break (Function "main")+          command bp $ do+            print "42"+            continue+    script `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      command $$var0+        print "42"+        continue+      end+      |]++  it "renders nested command statements" $ do+    let script = do+          bp1 <- break (Function "main")+          command bp1 $ do+            bp2 <- break (File "file.cpp" 1234)+            command bp2 $ do+              print "42"+              continue+    script `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      command $$var0+        break file.cpp:1234+        set $$var1 = $$bpnum+        command $$var1+          print "42"+          continue+        end+      end+      |]++  it "renders continue statements" $ do+    continue `shouldRenderAs` [text|+      continue+      |]++  it "renders next statements" $ do+    let script1 = next+        script2 = nextN 10+    script1 `shouldRenderAs` [text|+      next+      |]+    script2 `shouldRenderAs` [text|+      next 10+      |]++  it "renders step statements" $ do+    let script1 = step+        script2 = stepN 10+    script1 `shouldRenderAs` [text|+      step+      |]+    script2 `shouldRenderAs` [text|+      step 10+      |]++  it "renders reset statements" $ do+    reset `shouldRenderAs` [text|+      monitor reset+      |]++  it "renders set statements" $ do+    set "$var" "123" `shouldRenderAs` [text|+      set $$var = 123+      |]++  it "renders call statements" $ do+    call "function(123)" `shouldRenderAs` [text|+      call function(123)+      |]++  it "renders delete statements" $ do+    let script1 = do+          bp <- break (Function "main")+          delete bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          delete [bp1, bp2]+        script3 = delete All+    script1 `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      delete $$var0+      |]+    script2 `shouldRenderAs` [text|+      break f+      set $$var0 = $$bpnum+      break g+      set $$var1 = $$bpnum+      delete $$var0 $$var1+      |]+    script3 `shouldRenderAs` [text|+      delete+      |]++  it "renders disable statements" $ do+    let script1 = do+          bp <- break (Function "main")+          disable bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          disable [bp1, bp2]+        script3 = disable All+    script1 `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      disable $$var0+      |]+    script2 `shouldRenderAs` [text|+      break f+      set $$var0 = $$bpnum+      break g+      set $$var1 = $$bpnum+      disable $$var0 $$var1+      |]+    script3 `shouldRenderAs` [text|+      disable+      |]++  it "renders enable statements" $ do+    let script1 = do+          bp <- break (Function "main")+          enable bp+        script2 = do+          bp1 <- break (Function "f")+          bp2 <- break (Function "g")+          enable [bp1, bp2]+        script3 = enable All+    script1 `shouldRenderAs` [text|+      break main+      set $$var0 = $$bpnum+      enable $$var0+      |]+    script2 `shouldRenderAs` [text|+      break f+      set $$var0 = $$bpnum+      break g+      set $$var1 = $$bpnum+      enable $$var0 $$var1+      |]+    script3 `shouldRenderAs` [text|+      enable+      |]++  it "renders print statements" $ do+    print "42" `shouldRenderAs` [text|+      print "42"+      |]++  it "renders shell statements" $ do+    shell "sleep 1" `shouldRenderAs` [text|+      shell sleep 1+      |]++  it "renders source statements" $ do+    source "./other_script.gdb" `shouldRenderAs` [text|+      source ./other_script.gdb+      |]++  it "renders target statements" $ do+    let script = target (Remote 9001)+    script `shouldRenderAs` [text|+      target remote tcp:localhost:9001+      |]++  it "renders target statements" $ do+    let script = info Breakpoints+    script `shouldRenderAs` [text|+      info breakpoints+      |]++  it "mixes well with other Haskell concepts" $ do+    let script = flip runContT delete $ do+          bp1 <- ContT $ withBreakpoint (Function "func1")+          bp2 <- ContT $ withBreakpoint (Function "func2")+          pure [bp1, bp2]+        withBreakpoint :: Location -> (Id -> Builder ()) -> Builder ()+        withBreakpoint loc f = do+          bp <- break loc+          command bp $ f bp++    script `shouldRenderAs` [text|+      break func1+      set $$var0 = $$bpnum+      command $$var0+        break func2+        set $$var1 = $$bpnum+        command $$var1+          delete $$var0 $$var1+        end+      end+      |]