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 +30/−0
- README.md +84/−0
- Setup.hs +2/−0
- debugger-hs.cabal +76/−0
- src/Debugger/Builder.hs +164/−0
- src/Debugger/Render.hs +104/−0
- src/Debugger/Statement.hs +87/−0
- src/Lib.hs +6/−0
- test/Spec.hs +2/−0
- test/Test/Debugger/BuilderSpec.hs +195/−0
- test/Test/Debugger/RenderSpec.hs +237/−0
+ 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+ |]