diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..4aa91ea --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/elps.iml b/.idea/elps.iml new file mode 100644 index 0000000..80cb5f0 --- /dev/null +++ b/.idea/elps.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ef004d1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8c7a7a9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/_examples/wasm/server/main.go b/_examples/wasm/server/main.go index e66e561..a49d877 100644 --- a/_examples/wasm/server/main.go +++ b/_examples/wasm/server/main.go @@ -1,6 +1,6 @@ // Copyright © 2018 The ELPS authors -// A basic HTTP server. +// A basic HTTP delveserver. // By default, it serves the current working directory on port 8080. package main diff --git a/cmd/run.go b/cmd/run.go index 6da145f..ddf1fb7 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -4,6 +4,7 @@ package cmd import ( "fmt" + "github.com/luthersystems/elps/lisp/x/debugger" "io/ioutil" "os" @@ -14,8 +15,11 @@ import ( ) var ( - runExpression bool - runPrint bool + runExpression bool + runPrint bool + runDebugger bool + runDebuggerPort int + runDebuggerType string ) // runCmd represents the run command @@ -24,6 +28,11 @@ var runCmd = &cobra.Command{ Short: "Run lisp code", Long: `Run lisp code provided supplied via the command line or a file.`, Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + fmt.Fprintln(os.Stderr, "No file specified") + os.Exit(1) + } + env := lisp.NewEnv(nil) reader := parser.NewReader() env.Runtime.Reader = reader @@ -43,6 +52,21 @@ var runCmd = &cobra.Command{ fmt.Fprintln(os.Stderr, rc) os.Exit(1) } + + var debugInst lisp.Debugger + if runDebugger { + var mode debugger.DebugMode + switch debugger.DebugMode(runDebuggerType) { + case debugger.DebugModeDelve: + mode = debugger.DebugModeDelve + case debugger.DebugModeDAP: + mode = debugger.DebugModeDAP + default: + fmt.Fprintln(os.Stderr, "Invalid debugger. Specify delve or dap") + os.Exit(1) + } + debugInst = debugger.NewDebugger(env, fmt.Sprintf(":%d", runDebuggerPort), mode) + } for i := range args { res := env.LoadFile(args[i]) if res.Type == lisp.LError { @@ -50,6 +74,9 @@ var runCmd = &cobra.Command{ os.Exit(1) } } + if runDebugger { + debugInst.Complete() + } }, } @@ -79,4 +106,10 @@ func init() { "Interpret arguments as lisp expressions") runCmd.Flags().BoolVarP(&runPrint, "print", "p", false, "Print expression values to stdout") + runCmd.Flags().BoolVarP(&runDebugger, "debugger", "d", false, + "Enable the debugger") + runCmd.Flags().IntVarP(&runDebuggerPort, "debugger.listen", "l", 8883, + "Port for the debugger") + runCmd.Flags().StringVarP(&runDebuggerType, "debugger.type", "t", "dap", + "Type of debugger (delve or dap)") } diff --git a/go.mod b/go.mod index c7bcef7..8b1c29b 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,16 @@ require ( github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/go-delve/delve v1.4.0 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 + github.com/google/go-dap v0.2.0 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/magiconair/properties v1.8.0 // indirect + github.com/magiconair/properties v1.8.0 github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 // indirect github.com/pelletier/go-toml v1.1.0 // indirect github.com/prataprc/goparsec v0.0.0-20180208125142-3db61e7995f1 + github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973 github.com/spf13/afero v1.1.0 // indirect github.com/spf13/cast v1.2.0 // indirect github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index 8aa4678..37f16bc 100644 --- a/go.sum +++ b/go.sum @@ -8,11 +8,15 @@ github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ= +github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-delve/delve v1.4.0 h1:O+1dw1XBZXqhC6fIPQwGxLlbd2wDRau7NxNhVpw02ag= +github.com/go-delve/delve v1.4.0/go.mod h1:gQM0ReOJLNAvPuKAXfjHngtE93C2yc/ekTbo7YbAHSo= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -20,41 +24,72 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk= +github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9 h1:Y94YB7jrsihrbGSqRNMwRWJ2/dCxr0hdC2oPRohkx0A= github.com/mitchellh/go-homedir v0.0.0-20180523094522-3864e76763d9/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 h1:/rdJjIiKG5rRdwG5yxHmSE/7ZREjpyC0kL7GxGT/qJw= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM= github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 h1:wBza4Dlm/NCQF572oSGNZ69flNFxlwIHjtwS6oy3Rvw= +github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prataprc/goparsec v0.0.0-20180208125142-3db61e7995f1 h1:19MwL2eVeBf4Dqpu34nZL53jZCXi90K9BiC7YHwAUVw= github.com/prataprc/goparsec v0.0.0-20180208125142-3db61e7995f1/go.mod h1:YbpxZqbf10o5u96/iDpcfDQmbIOTX/iNCH/yBByTfaM= +github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973 h1:3AJZYTzw3gm3TNTt30x0CCKD7GOn2sdd50Hn35fQkGY= +github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4= github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI= +golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -62,17 +97,23 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd h1:r7DufRZuZbWB7j439YfAzP8RPDa9unLkpwQKUYbIMPI= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -81,14 +122,28 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/lisp/env.go b/lisp/env.go index 0f3af99..9e2261c 100644 --- a/lisp/env.go +++ b/lisp/env.go @@ -767,10 +767,14 @@ func (env *LEnv) EvalSExpr(s *LVal) *LVal { fun := call.Cells[0] // call is not an empty expression -- fun is known LFun args := call args.Cells = args.Cells[1:] - if (env.Runtime.Profiler != nil) { + if env.Runtime.Profiler != nil { env.Runtime.Profiler.Start(fun) defer env.Runtime.Profiler.End(fun) } + if env.Runtime.Debugger != nil { + env.Runtime.Debugger.Start(s, fun) + defer env.Runtime.Debugger.End(fun) + } switch fun.FunType { case LFunNone: return env.FunCall(fun, args) diff --git a/lisp/profiler.go b/lisp/profiler.go index a50cf2e..a80948d 100644 --- a/lisp/profiler.go +++ b/lisp/profiler.go @@ -18,3 +18,16 @@ type Profiler interface { End(function *LVal) } +// Interface for a debugger +type Debugger interface { + // Is the profiler enabled? + IsEnabled() bool + // End the session and output summary lines + Complete() error + // Is done + Done() bool + // Marks the start of a process + Start(expr *LVal, function *LVal) + // Marks the end of a process + End(function *LVal) +} diff --git a/lisp/runtime.go b/lisp/runtime.go index 70dfd6b..0ef0ba1 100644 --- a/lisp/runtime.go +++ b/lisp/runtime.go @@ -20,6 +20,7 @@ type Runtime struct { Reader Reader Library SourceLibrary Profiler Profiler + Debugger Debugger numenv atomicCounter numsym atomicCounter } diff --git a/lisp/x/debugger/dapserver/dapserver.go b/lisp/x/debugger/dapserver/dapserver.go new file mode 100644 index 0000000..fe1d6ce --- /dev/null +++ b/lisp/x/debugger/dapserver/dapserver.go @@ -0,0 +1,455 @@ +package dapserver + +// implements VSCode's DAP protocol (https://microsoft.github.io/debug-adapter-protocol/specification) +// to keep Amir happy. +// +// Extension point docs at https://code.visualstudio.com/api/extension-guides/debugger-extension +// More useful info on how to integrate in vscode at https://marketplace.visualstudio.com/items?itemName=andreweinand.mock-debug +// and https://github.com/Microsoft/vscode-debugadapter-node + +import ( + "bufio" + "github.com/go-delve/delve/service/api" + "github.com/google/go-dap" + "github.com/luthersystems/elps/lisp/x/debugger/delveserver" + "github.com/luthersystems/elps/lisp/x/debugger/events" + log "github.com/sirupsen/logrus" + "io" + "net" + "path/filepath" + "runtime" + "sync" + "time" +) + +type Server struct { + EndChannel chan bool + Address string + listener net.Listener + connQueue chan net.Conn + connection *connection + sequence int + sequenceLock sync.Mutex + wg *sync.WaitGroup + debugger *debuggerwrapper +} + +type connection struct { + s *Server + rw *bufio.ReadWriter + queue chan dap.Message + kill chan bool + Connected bool + Event chan events.EventType +} + +func NewServer(debugger delveserver.ServerDebugger, address string, handlers int) (*Server, error) { + server := &Server{ + Address: address, + connection: &connection{}, + connQueue: make(chan net.Conn, 10), + sequence: 0, + wg: new(sync.WaitGroup), + debugger: &debuggerwrapper{ + debugger: debugger, + files: make(map[string][]string), + }, + EndChannel: make(chan bool), + } + server.connection.s = server + return server, nil +} + +func (s *Server) Run() error { + go startMonitor() + log.Infof("Listening on %s", s.Address) + listener, err := net.Listen("tcp", s.Address) + if err != nil { + return err + } + + s.listener = listener + s.wg.Add(1) + go s.listen() + + return nil +} + +func startMonitor() { + for { + log.Debugf("Monitor heartbeat") + time.Sleep(1 * time.Second) + runtime.Gosched() + } +} + +func (s *Server) Stop() error { + s.EndChannel <- true + s.wg.Wait() + return nil +} + +func (s *Server) Event(x events.EventType) { + if s.connection.Connected { + go func(ch chan events.EventType) { ch <- x }(s.connection.Event) + } +} + +func (s *Server) Breakpoint(bp *api.Breakpoint) { + log.Infof("Hit breakpoint %v", bp) + _, filename := filepath.Split(bp.File) + if s.connection.Connected { + go func(ch chan dap.Message, path string) { + s.connection.queue <- &dap.BreakpointEvent{ + Event: dap.Event{ + Event: "breakpoint", + ProtocolMessage: dap.ProtocolMessage{ + Seq: s.incSequence(), + Type: "event", + }, + }, + Body: dap.BreakpointEventBody{ + Reason: "breakpoint", + Breakpoint: dap.Breakpoint{ + Id: bp.ID, + Verified: true, + Message: "Stopped at breakpoint", + Source: dap.Source{ + Path: bp.File, + Name: filename, + }, + Line: bp.Line, + Column: 1, + EndLine: bp.Line, + EndColumn: 1, + }, + }, + } + }(s.connection.queue, filename) + } + runtime.Gosched() +} + +func (s *Server) listen() { + defer s.listener.Close() + for { + conn, err := s.listener.Accept() + if err != nil { + log.Errorf("Connection failed: %v", err) + continue + } + log.Infof("Accepted connection from %v", conn.RemoteAddr()) + s.connection.start(conn) + return + } +} + +func (s *Server) getSequence() int { + s.sequenceLock.Lock() + defer s.sequenceLock.Unlock() + return s.sequence +} + +func (s *Server) incSequence() int { + s.sequenceLock.Lock() + defer s.sequenceLock.Unlock() + s.sequence += 1 + return s.sequence +} + +func (h *connection) killconnection() { + h.kill <- true +} + +func (h *connection) start(conn net.Conn) { + h.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) + h.queue = make(chan dap.Message) + h.Connected = true + h.Event = make(chan events.EventType) + go func() { + for { + select { + case <-h.kill: + h.kill <- true + return + case event := <-h.Event: + h.sendEventMessage(event) + } + } + }() + go func() { + for { + select { + case message := <-h.queue: + h.sendMessage(message) + case <-h.kill: + h.kill <- true + return + + } + } + }() + for { + select { + case <-h.kill: + h.kill <- true + return + default: + // no-op + } + err := h.handleRequest() + if err != nil { + if err == io.EOF { + log.Errorf("Connection closed: %v", err) + h.s.EndChannel <- true + return + } + log.Fatal("Server error: ", err) + } + } +} + +func (h *connection) handleRequest() error { + request, err := dap.ReadProtocolMessage(h.rw.Reader) + if err != nil { + return err + } + log.Debugf("Received request\n\t%#v\n", request) + h.s.wg.Add(1) + go func() { + defer func() { + if r := recover(); r != nil { + log.Fatalf("PANIC! Quitting - %s", r) + h.kill <- true + } + }() + h.dispatchRequest(request) + h.s.wg.Done() + }() + return nil +} + +func (h *connection) sendHandler() { + for message := range h.queue { + log.Debugf("Sending message: %s", message) + err := dap.WriteProtocolMessage(h.rw.Writer, message) + if err != nil { + log.Errorf("Error sending message: %s", err.Error()) + } + _ = h.rw.Flush() + } +} + +func (h *connection) dispatchRequest(request dap.Message) { + switch request := request.(type) { + case *dap.InitializeRequest: + h.s.debugger.onInitializeRequest(request, h) + case *dap.LaunchRequest: + h.s.debugger.onLaunchRequest(request, h.queue, h.s.incSequence()) + case *dap.AttachRequest: + h.s.debugger.onAttachRequest(request, h.queue, h.s.incSequence()) + case *dap.DisconnectRequest: + h.s.debugger.onDisconnectRequest(request, h.queue, h.s.incSequence()) + h.killconnection() + case *dap.TerminateRequest: + h.s.debugger.onTerminateRequest(request, h.queue, h.s.incSequence()) + case *dap.RestartRequest: + h.s.debugger.onRestartRequest(request, h.queue, h.s.incSequence()) + case *dap.SetBreakpointsRequest: + h.s.debugger.onSetBreakpointsRequest(request, h.queue, h.s.incSequence()) + case *dap.SetFunctionBreakpointsRequest: + h.s.debugger.onSetFunctionBreakpointsRequest(request, h.queue, h.s.incSequence()) + case *dap.SetExceptionBreakpointsRequest: + h.s.debugger.onSetExceptionBreakpointsRequest(request, h.queue, h.s.incSequence()) + case *dap.ConfigurationDoneRequest: + h.s.debugger.onConfigurationDoneRequest(request, h.queue, h.s.incSequence()) + case *dap.ContinueRequest: + h.s.debugger.onContinueRequest(request, h.queue, h.s.incSequence()) + case *dap.NextRequest: + h.s.debugger.onNextRequest(request, h.queue, h.s.incSequence()) + case *dap.StepInRequest: + h.s.debugger.onStepInRequest(request, h.queue, h.s.incSequence()) + case *dap.StepOutRequest: + h.s.debugger.onStepOutRequest(request, h.queue, h.s.incSequence()) + case *dap.StepBackRequest: + h.s.debugger.onStepBackRequest(request, h.queue, h.s.incSequence()) + case *dap.ReverseContinueRequest: + h.s.debugger.onReverseContinueRequest(request, h.queue, h.s.incSequence()) + case *dap.RestartFrameRequest: + h.s.debugger.onRestartFrameRequest(request, h.queue, h.s.incSequence()) + case *dap.GotoRequest: + h.s.debugger.onGotoRequest(request, h.queue, h.s.incSequence()) + case *dap.PauseRequest: + h.s.debugger.onPauseRequest(request, h.queue, h.s.incSequence()) + case *dap.StackTraceRequest: + h.s.debugger.onStackTraceRequest(request, h.queue, h.s.incSequence()) + case *dap.ScopesRequest: + h.s.debugger.onScopesRequest(request, h.queue, h.s.incSequence()) + case *dap.VariablesRequest: + h.s.debugger.onVariablesRequest(request, h.queue, h.s.incSequence()) + case *dap.SetVariableRequest: + h.s.debugger.onSetVariableRequest(request, h.queue, h.s.incSequence()) + case *dap.SetExpressionRequest: + h.s.debugger.onSetExpressionRequest(request, h.queue, h.s.incSequence()) + case *dap.SourceRequest: + h.s.debugger.onSourceRequest(request, h.queue, h.s.incSequence()) + case *dap.ThreadsRequest: + h.s.debugger.onThreadsRequest(request, h.queue, h.s.incSequence()) + case *dap.TerminateThreadsRequest: + h.s.debugger.onTerminateThreadsRequest(request, h.queue, h.s.incSequence()) + case *dap.EvaluateRequest: + h.s.debugger.onEvaluateRequest(request, h.queue, h.s.incSequence()) + case *dap.StepInTargetsRequest: + h.s.debugger.onStepInTargetsRequest(request, h.queue, h.s.incSequence()) + case *dap.GotoTargetsRequest: + h.s.debugger.onGotoTargetsRequest(request, h.queue, h.s.incSequence()) + case *dap.CompletionsRequest: + h.s.debugger.onCompletionsRequest(request, h.queue, h.s.incSequence()) + case *dap.ExceptionInfoRequest: + h.s.debugger.onExceptionInfoRequest(request, h.queue, h.s.incSequence()) + case *dap.LoadedSourcesRequest: + h.s.debugger.onLoadedSourcesRequest(request, h.queue, h.s.incSequence()) + case *dap.DataBreakpointInfoRequest: + h.s.debugger.onDataBreakpointInfoRequest(request, h.queue, h.s.incSequence()) + case *dap.SetDataBreakpointsRequest: + h.s.debugger.onSetDataBreakpointsRequest(request, h.queue, h.s.incSequence()) + case *dap.ReadMemoryRequest: + h.s.debugger.onReadMemoryRequest(request, h.queue, h.s.incSequence()) + case *dap.DisassembleRequest: + h.s.debugger.onDisassembleRequest(request, h.queue, h.s.incSequence()) + case *dap.CancelRequest: + h.s.debugger.onCancelRequest(request, h.queue, h.s.incSequence()) + case *dap.BreakpointLocationsRequest: + h.s.debugger.onBreakpointLocationsRequest(request, h.queue, h.s.incSequence()) + case *dap.ModulesRequest: + h.s.debugger.onModulesRequest(request, h.queue, h.s.incSequence()) + default: + log.Fatalf("Unable to process %#v", request) + } +} + +func (h *connection) sendEventMessage(event events.EventType) { + log.Infof("Sending event %s", event) + switch event { + case events.EventTypeContinued: + h.queue <- &dap.ContinuedEvent{ + Event: dap.Event{Event: "continued", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.ContinuedEventBody{ + AllThreadsContinued: true, + ThreadId: 1, + }, + } + case events.EventTypeExited: + h.queue <- &dap.ExitedEvent{ + Event: dap.Event{Event: "continued", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.ExitedEventBody{ + ExitCode: 0, //TODO this is so not true + }, + } + case events.EventTypeStarted: + // this is a no-op for us here + case events.EventTypeStoppedPaused: + h.queue <- &dap.StoppedEvent{ + Event: dap.Event{ + Event: "stopped", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.StoppedEventBody{ + AllThreadsStopped: true, + ThreadId: 1, + Reason: "pause", + }, + } + case events.EventTypeStoppedBreakpoint: + h.queue <- &dap.StoppedEvent{ + Event: dap.Event{ + Event: "stopped", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.StoppedEventBody{ + AllThreadsStopped: true, + ThreadId: 1, + Reason: "breakpoint", + }, + } + case events.EventTypeStoppedEntry: + h.queue <- &dap.StoppedEvent{ + Event: dap.Event{Event: "stopped", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.StoppedEventBody{ + AllThreadsStopped: true, + ThreadId: 1, + Reason: "entry", // we need to propagate this by using more reasons + }, + } + case events.EventTypeStoppedStep: + h.queue <- &dap.StoppedEvent{ + Event: dap.Event{Event: "stopped", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.StoppedEventBody{ + AllThreadsStopped: true, + ThreadId: 1, + Reason: "step", // we need to propagate this by using more reasons + }, + } + case events.EventTypeTerminated: + h.queue <- &dap.TerminatedEvent{ + Event: dap.Event{ + Event: "terminated", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + Body: dap.TerminatedEventBody{ + Restart: false, + }, + } + case events.EventTypeInitialized: + h.queue <- &dap.InitializedEvent{ + Event: dap.Event{ + Event: "initialized", + ProtocolMessage: dap.ProtocolMessage{ + Seq: h.s.incSequence(), + Type: "event", + }, + }, + } + } +} + +func (h *connection) sendMessage(message dap.Message) { + log.Debugf("Sending message over wire: %#v", message) + err := dap.WriteProtocolMessage(h.rw, message) + if err != nil { + log.Warnf("Error sending: %s", err.Error()) + } + err = h.rw.Flush() + if err != nil { + log.Warnf("Error flushing: %s", err.Error()) + } +} diff --git a/lisp/x/debugger/dapserver/debuggerwrapper.go b/lisp/x/debugger/dapserver/debuggerwrapper.go new file mode 100644 index 0000000..a8819a1 --- /dev/null +++ b/lisp/x/debugger/dapserver/debuggerwrapper.go @@ -0,0 +1,890 @@ +package dapserver + +import ( + "encoding/json" + "github.com/go-delve/delve/service/api" + "github.com/go-delve/delve/service/rpc2" + "github.com/golang-collections/collections/stack" + "github.com/google/go-dap" + "github.com/luthersystems/elps/lisp/x/debugger/delveserver" + "github.com/luthersystems/elps/lisp/x/debugger/events" + log "github.com/sirupsen/logrus" + "io/ioutil" + "math/rand" + "strings" +) + +type debuggerwrapper struct { + debugger delveserver.ServerDebugger + files map[string][]string +} + +func (d *debuggerwrapper) onInitializeRequest(request *dap.InitializeRequest, handler *connection) { + handler.queue <- &dap.InitializeResponse{ + Response: dap.Response{ + Success: true, + RequestSeq: request.Seq, + ProtocolMessage: dap.ProtocolMessage{ + Seq: handler.s.incSequence(), + Type: "response", + }, + Command: request.Command, + }, + Body: dap.Capabilities{ + SupportsConfigurationDoneRequest: true, + SupportsFunctionBreakpoints: false, + SupportsConditionalBreakpoints: false, + SupportsHitConditionalBreakpoints: false, + SupportsEvaluateForHovers: true, + SupportsStepBack: false, + SupportsSetVariable: true, + SupportsRestartFrame: false, + SupportsGotoTargetsRequest: false, + SupportsStepInTargetsRequest: false, + SupportsCompletionsRequest: false, + SupportsModulesRequest: true, + SupportsRestartRequest: false, + SupportsExceptionOptions: false, + SupportsValueFormattingOptions: false, + SupportsExceptionInfoRequest: false, + SupportTerminateDebuggee: true, + SupportsDelayedStackTraceLoading: false, + SupportsLoadedSourcesRequest: false, + SupportsLogPoints: false, + SupportsTerminateThreadsRequest: false, + SupportsSetExpression: true, + SupportsTerminateRequest: true, + SupportsDataBreakpoints: false, + SupportsReadMemoryRequest: false, + SupportsDisassembleRequest: false, + SupportsCancelRequest: false, + SupportsBreakpointLocationsRequest: true, + }, + } + + handler.Event <- events.EventTypeInitialized + if d.debugger.IsStopped() { + handler.Event <- events.EventTypeStoppedEntry + } +} + +func (d *debuggerwrapper) onLaunchRequest(request *dap.LaunchRequest, returnchan chan dap.Message, seq int) { + returnchan <- &dap.LaunchResponse{ + Response: dap.Response{ + RequestSeq: request.GetSeq(), + Success: true, + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + }, + } +} + +func (d *debuggerwrapper) onAttachRequest(request *dap.AttachRequest, returnchan chan dap.Message, seq int) { + returnchan <- &dap.AttachResponse{ + Response: dap.Response{ + RequestSeq: request.GetSeq(), + Success: true, + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + }, + } +} + +func (d *debuggerwrapper) onDisconnectRequest(request *dap.DisconnectRequest, returnchan chan dap.Message, seq int) { + if request.Arguments.TerminateDebuggee { + d.debugger.Complete() + panic("DONE") + } + returnchan <- &dap.DisconnectResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + Message: "Complete", + }, + } +} + +func (d *debuggerwrapper) onTerminateRequest(request *dap.TerminateRequest, returnchan chan dap.Message, seq int) { + d.debugger.Complete() + returnchan <- &dap.TerminateResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + Message: "Complete", + }, + } + panic("DONE") +} + +func (d *debuggerwrapper) onRestartRequest(request *dap.RestartRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported restartRequest was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest, returnchan chan dap.Message, seq int) { + log.Infof("BREAKPOINT request %v", request) + if request.Arguments.SourceModified { + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Cannot modify source whilst running", + }, + } + return + } + log.Infof("Name is %s", request.Arguments.Source.Name) + out := make([]dap.Breakpoint, 0) + for id, bp := range d.debugger.GetAllBreakpoints() { + if bp.File == request.Arguments.Source.Path { + err := d.debugger.RemoveBreakpoint(id) + if err != nil { + log.Errorf("Error removing breakpoint: %s", err.Error()) + } + } + } + for _, v := range request.Arguments.Breakpoints { + id := rand.Int() + d.debugger.CreateBreakpoint(&api.Breakpoint{ + ID: id, + Name: request.Arguments.Source.Name, + Addr: 0, + Addrs: nil, + File: request.Arguments.Source.Path, + Line: v.Line, + FunctionName: "", + Cond: v.Condition, + Tracepoint: false, + TraceReturn: false, + Goroutine: false, + Stacktrace: 0, + TotalHitCount: 0, + }) + } + for id, bp := range d.debugger.GetAllBreakpoints() { + if bp.File == request.Arguments.Source.Path { + out = append(out, dap.Breakpoint{ + Id: id, + Verified: true, + Source: request.Arguments.Source, + Line: bp.Line, + Column: 1, + EndLine: bp.Line, + EndColumn: 1, + }) + } else { + log.Infof("%s didn't match %s", bp.File, request.Arguments.Source.Path) + } + } + + bpResponse := &dap.SetBreakpointsResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + Body: dap.SetBreakpointsResponseBody{Breakpoints: out}, + } + returnchan <- bpResponse + o, _ := json.Marshal(bpResponse) + log.Info(string(o)) +} + +func (d *debuggerwrapper) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported setFunctionBreakpoint was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported setExceptionBreakpoint was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest, returnchan chan dap.Message, seq int) { + returnchan <- &dap.ConfigurationDoneResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + } +} + +func (d *debuggerwrapper) onContinueRequest(request *dap.ContinueRequest, returnchan chan dap.Message, seq int) { + d.debugger.Continue() + returnchan <- &dap.ContinueResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + Body: dap.ContinueResponseBody{ + AllThreadsContinued: true, + }, + } +} + +func (d *debuggerwrapper) onNextRequest(request *dap.NextRequest, returnchan chan dap.Message, seq int) { + d.debugger.Step() + returnchan <- &dap.NextResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + } +} + +func (d *debuggerwrapper) onStepInRequest(request *dap.StepInRequest, returnchan chan dap.Message, seq int) { + d.debugger.Step() + returnchan <- &dap.StepInResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + } +} + +func (d *debuggerwrapper) onStepOutRequest(request *dap.StepOutRequest, returnchan chan dap.Message, seq int) { + d.debugger.Step() + returnchan <- &dap.StepOutResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + } +} + +func (d *debuggerwrapper) onStepBackRequest(request *dap.StepBackRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported stepBack was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onReverseContinueRequest(request *dap.ReverseContinueRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported reverseContinue was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onRestartFrameRequest(request *dap.RestartFrameRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported restartFrame was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onGotoRequest(request *dap.GotoRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported goto was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onPauseRequest(request *dap.PauseRequest, returnchan chan dap.Message, seq int) { + d.debugger.Halt() + returnchan <- &dap.PauseResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.GetSeq(), + }, + } +} + +func (d *debuggerwrapper) onStackTraceRequest(request *dap.StackTraceRequest, returnchan chan dap.Message, seq int) { + st := &rpc2.StacktraceOut{} + d.debugger.GetStacktrace(st) + out := d.debugger.GetDapStacktrace() + returnchan <- &dap.StackTraceResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + Success: true, + RequestSeq: request.Seq, + }, + Body: dap.StackTraceResponseBody{ + StackFrames: out, + TotalFrames: len(out), + }, + } +} + +func (d *debuggerwrapper) onScopesRequest(request *dap.ScopesRequest, returnchan chan dap.Message, seq int) { + source := d.debugger.GetDapStacktrace()[0] + returnchan <- &dap.ScopesResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.ScopesResponseBody{ + Scopes: []dap.Scope{ + { + VariablesReference: 2, + PresentationHint: "locals", + Name: "Scope", + }, + { + Name: "Arguments", + PresentationHint: "locals", + VariablesReference: 1, + Expensive: false, + Source: source.Source, + Line: source.Line, + Column: source.Column, + EndLine: source.EndLine, + EndColumn: source.EndColumn, + }, + }, + }, + } +} + +func (d *debuggerwrapper) onVariablesRequest(request *dap.VariablesRequest, returnchan chan dap.Message, seq int) { + variables := make([]dap.Variable, 0) + if request.Arguments.VariablesReference == 1 { + variables := d.debugger.GetArguments() + returnchan <- &dap.VariablesResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.VariablesResponseBody{ + Variables: variables, + }, + } + return + } + + for _, vari := range d.debugger.GetVariables() { + outVar := dap.Variable{ + Name: vari.Name, + Value: vari.Value, + Type: vari.Type, + PresentationHint: dap.VariablePresentationHint{ + Kind: "data", + Visibility: "public", + Attributes: []string{}, + }, + VariablesReference: 0, // TODO support children + } + variables = append(variables, outVar) + } + returnchan <- &dap.VariablesResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.VariablesResponseBody{ + Variables: variables, + }, + } +} + +func (d *debuggerwrapper) onSetVariableRequest(request *dap.SetVariableRequest, returnchan chan dap.Message, seq int) { + err := d.debugger.SetVariableInScope(api.EvalScope{}, request.Arguments.Name, request.Arguments.Value) + if err != nil { + returnchan <- &dap.SetVariableResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: err.Error(), + }, + Body: dap.SetVariableResponseBody{}, + } + return + } + returnchan <- &dap.SetVariableResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.SetVariableResponseBody{ + Value: request.Arguments.Value, + Type: "LVal", + }, + } +} + +func (d *debuggerwrapper) onSetExpressionRequest(request *dap.SetExpressionRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported setExpression was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onSourceRequest(request *dap.SourceRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported source request was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onThreadsRequest(request *dap.ThreadsRequest, returnchan chan dap.Message, seq int) { + returnchan <- &dap.ThreadsResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.ThreadsResponseBody{ + Threads: []dap.Thread{ + { + Id: 1, + Name: "Execution thread", + }, + }, + }, + } +} + +func (d *debuggerwrapper) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported terminateThreads was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onEvaluateRequest(request *dap.EvaluateRequest, returnchan chan dap.Message, seq int) { + returnchan <- &dap.EvaluateResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.EvaluateResponseBody{ + Result: d.debugger.Eval(request.Arguments.Expression), + Type: "LVal", + // TODO support children + }, + } +} + +func (d *debuggerwrapper) onStepInTargetsRequest(request *dap.StepInTargetsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported stepInTargets was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onGotoTargetsRequest(request *dap.GotoTargetsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported gotoTargets was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onCompletionsRequest(request *dap.CompletionsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported completions was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onExceptionInfoRequest(request *dap.ExceptionInfoRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported exceptionInfo was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported loadedSources was made by client") + returnchan <- &dap.LoadedSourcesResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + Body: dap.LoadedSourcesResponseBody{ + Sources: []dap.Source{ + + }, + }, + } +} + +func (d *debuggerwrapper) onDataBreakpointInfoRequest(request *dap.DataBreakpointInfoRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported dataBreakpointInfo was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported setDataBreakpoints was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onReadMemoryRequest(request *dap.ReadMemoryRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported readMemory was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onDisassembleRequest(request *dap.DisassembleRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported disassemble was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) onCancelRequest(request *dap.CancelRequest, returnchan chan dap.Message, seq int) { + log.Error("Invalid request to non-supported cancelRequest was made by client") + returnchan <- &dap.ErrorResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + RequestSeq: request.GetSeq(), + Success: false, + Message: "Not supported", + }, + } +} + +func (d *debuggerwrapper) getCodeLine(file string, line int) string { + var data []string + var ok bool + if data, ok = d.files[file]; !ok { + dataBytes, err := ioutil.ReadFile(file) + if err != nil { + panic(err) + } + data = strings.Split(string(dataBytes), "\n") + d.files[file] = data + } + return data[line-1] +} + +func (d *debuggerwrapper) getBreakpointsFromLine(file string, lineNumber int) []dap.BreakpointLocation { + locations := make([]dap.BreakpointLocation, 0) + stk := stack.New() + for { + line := d.getCodeLine(file, lineNumber) + for pos, r := range []rune(line) { + switch r { + case '(', '{': + stk.Push([]int{pos + 1, lineNumber}) + case '}', ')': + start := stk.Pop().([]int) + locations = append(locations, dap.BreakpointLocation{ + Line: start[1], + Column: start[0], + EndLine: lineNumber, + EndColumn: pos + 1, + }) + } + } + if stk.Len() == 0 || lineNumber == len(d.files[file]) { + break + } + lineNumber += 1 + } + return locations +} + +func (d *debuggerwrapper) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest, returnchan chan dap.Message, seq int) { + // NB This is the POSSIBLE locations for breakpoints - not the actual locations + locations := d.getBreakpointsFromLine(request.Arguments.Source.Path, request.Arguments.Line) + resp := &dap.BreakpointLocationsResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.BreakpointLocationsResponseBody{ + Breakpoints: locations, + }, + } + returnchan <- resp +} + +func (d *debuggerwrapper) onModulesRequest(request *dap.ModulesRequest, returnchan chan dap.Message, seq int) { + modules := d.debugger.GetModules() + returnchan <- &dap.ModulesResponse{ + Response: dap.Response{ + ProtocolMessage: dap.ProtocolMessage{ + Seq: seq, + Type: "response", + }, + Command: request.Command, + RequestSeq: request.GetSeq(), + Success: true, + }, + Body: dap.ModulesResponseBody{ + Modules: modules, + TotalModules: len(modules), + }, + } +} diff --git a/lisp/x/debugger/dapserver/debuggerwrapper_test.go b/lisp/x/debugger/dapserver/debuggerwrapper_test.go new file mode 100644 index 0000000..8e3f7b2 --- /dev/null +++ b/lisp/x/debugger/dapserver/debuggerwrapper_test.go @@ -0,0 +1,65 @@ +package dapserver + +import ( + "github.com/google/go-dap" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBreakpoints(t *testing.T) { + d := &debuggerwrapper{ + files: map[string][]string{ + "test.lisp": []string{ + "(line 1)", + "(line (get-line 2))", + "(line (", + " (something something)", + "))", + }, + }, + } + + assert.Equal(t, []dap.BreakpointLocation{ + { + Line: 1, + Column: 1, + EndLine: 1, + EndColumn: 8, + }, + }, d.getBreakpointsFromLine("test.lisp", 1)) + assert.Equal(t, []dap.BreakpointLocation{ + { + Line: 2, + Column: 7, + EndLine: 2, + EndColumn: 18, + }, + { + Line: 2, + Column: 1, + EndLine: 2, + EndColumn: 19, + }, + }, d.getBreakpointsFromLine("test.lisp", 2)) + assert.Equal(t, []dap.BreakpointLocation{ + { + Line: 4, + Column: 3, + EndLine: 4, + EndColumn: 23, + }, + { + Line: 3, + Column: 7, + EndLine: 5, + EndColumn: 1, + }, + { + Line: 3, + Column: 1, + EndLine: 5, + EndColumn: 2, + }, + + }, d.getBreakpointsFromLine("test.lisp", 3)) +} diff --git a/lisp/x/debugger/debugger.go b/lisp/x/debugger/debugger.go new file mode 100644 index 0000000..51da36c --- /dev/null +++ b/lisp/x/debugger/debugger.go @@ -0,0 +1,989 @@ +package debugger + +import ( + "errors" + "fmt" + "github.com/go-delve/delve/service/rpc2" + "github.com/google/go-dap" + "github.com/luthersystems/elps/lisp" + "github.com/luthersystems/elps/lisp/x/debugger/dapserver" + "github.com/luthersystems/elps/lisp/x/debugger/delveserver" + "github.com/luthersystems/elps/lisp/x/debugger/events" + "github.com/luthersystems/elps/lisp/x/profiler" + "github.com/luthersystems/elps/parser/token" + "github.com/sirupsen/logrus" + "math/rand" + "os" + "reflect" + "runtime" + "strconv" + "strings" + "sync" + "time" +) +import "github.com/go-delve/delve/service/api" + +type Debugger struct { + sync.Mutex + enabled bool + breakpoints map[int]*api.Breakpoint + env *lisp.LEnv + runtime *lisp.Runtime + lastModified time.Time + stopped bool + step chan bool + run chan bool + server DebugServer + currentOp *op + logger *logrus.Logger + pwd string + callRefs *callRef + isEvaling bool + reenteringStart bool +} + +func (d *Debugger) Done() bool { + panic("implement me") +} + +type op struct { + name string + source *token.Location + args map[string]*lisp.LVal +} + +type DebugServer interface { + Run() error + Stop() error + Event(events.EventType) + Breakpoint(v *api.Breakpoint) +} + +type DebugMode string + +const DebugModeDelve = DebugMode("delve") +const DebugModeDAP = DebugMode("dap") + +func NewDebugger(env *lisp.LEnv, address string, mode DebugMode) lisp.Debugger { + db := &Debugger{ + runtime: env.Runtime, + env: env, + enabled: true, + stopped: true, + run: make(chan bool), + step: make(chan bool), + lastModified: time.Now(), + breakpoints: make(map[int]*api.Breakpoint), + logger: logrus.New(), + } + var srv DebugServer + if mode == DebugModeDelve { + srv = delveserver.NewServer(db, address) + } else if mode == DebugModeDAP { + var err error + srv, err = dapserver.NewServer(db, address, 2) + if err != nil { + panic(err.Error()) + } + go func(end chan bool) { + die := <-end + if die { + panic("Remote process terminated debuggee") + } + }(srv.(*dapserver.Server).EndChannel) + } + srv.Run() + db.server = srv + db.runtime.Debugger = db + return db +} + +func (d *Debugger) IsStopped() bool { + return d.stopped +} + +func (d *Debugger) SetLispPath(path string) { + d.pwd = strings.ReplaceAll(path, `\`, `/`) +} + +func (d *Debugger) IsEnabled() bool { + return d.enabled +} + +func (d *Debugger) Enable() error { + if d.enabled { + return nil + } + return errors.New("Cannot re-enable debugger from here") +} + +func (d *Debugger) SetFile(filename string) error { + return errors.New("Not used") +} + +func (d *Debugger) Complete() error { + d.Lock() + defer d.Unlock() + d.logger.Infof("COMPLETE") + d.enabled = false + d.server.Stop() + d.run <- true + d.server.Event(events.EventTypeTerminated) + return nil +} + +func (d *Debugger) Start(expr *lisp.LVal, function *lisp.LVal) { + if d.isEvaling { + return + } + fname := "" + if function.FunData() != nil { + fname = function.FunData().FID + } + source := expr.Source + if source == nil || source.File == "" { + for f := len(d.runtime.Stack.Frames) - 1; f >= 0; f-- { + frame := d.runtime.Stack.Frames[f] + if frame.Source != nil || frame.Source.File == "" { + continue + } + source = frame.Source + break + } + } + if source == nil { + source = &token.Location{ + File: "Unknown", + Line: 0, + } + } + sourceStr := fmt.Sprintf("@%s:%d", source.File, source.Line) + d.logger.Debugf("Instr %v %s params %v %s %d", function.Type, fname, function.Cells, sourceStr, len(d.runtime.Stack.Frames)) + switch function.Type { + case lisp.LInt: + return + case lisp.LBytes: + return + case lisp.LString: + return + case lisp.LFloat: + return + case lisp.LSortMap: + return + case lisp.LArray: + return + } + args := make(map[string]*lisp.LVal) + paramCounter := 0 + fname = d.runtime.Package.FunNames[fname] + if function.Type == lisp.LFun { + argList := function.Cells[0] + restMode := false + for count, argName := range argList.Cells { + if argName.Str == lisp.VarArgSymbol { + restMode = true + continue + } + if restMode { + for pos := count; pos < len(expr.Cells); pos++ { + args[fmt.Sprintf("%s[%d]", argName.Str, pos)] = expr.Cells[pos] + } + break + } + val := &lisp.LVal{ + Type: lisp.LInvalid, + } + if len(expr.Cells) >= count { + val = expr.Cells[count+1] + } + args[argName.Str] = val + } + } else { + for k, v := range function.Cells { + argName := d.runtime.Package.Symbols[fname].Cells[paramCounter].String() + if argName == "" { + argName = strconv.Itoa(k) + } + d.logger.Debugf("Arg %s %v is %v", argName, d.runtime.Package.Symbols[fname].Cells[paramCounter], d.mapValue(v)) + if cell := expr.Cells[k+1]; cell != nil { + args[argName] = cell + } else { + args[argName] = v + } + } + } + d.currentOp = &op{ + name: fname, + source: source, + args: args, + } + if !d.reenteringStart { + d.incrementCallRef(expr, function) + } else { + d.reenteringStart = false + } + // don't want to pause on code we can't see... + if source.File == "" { + runtime.Gosched() + return + } + if !d.stopped { + d.Lock() + if expr.Source != nil { + fstat, _ := os.Stat(expr.Source.Path) + for _, v := range d.breakpoints { + vStat, _ := os.Stat(v.File) + if (os.SameFile(vStat, fstat) || fmt.Sprintf("%s%c%s", d.pwd, os.PathSeparator, expr.Source.File) == v.File) && + v.Line == expr.Source.Line { + d.logger.Infof("BREAKPOINT") + d.stopped = true + d.Unlock() + d.server.Breakpoint(v) + d.server.Event(events.EventTypeStoppedBreakpoint) + // or it won't yield... + runtime.Gosched() + d.reenteringStart = true + d.Start(expr, function) + return + } + } + } + d.Unlock() + runtime.Gosched() + return + } + select { + case <-d.run: + d.server.Event(events.EventTypeContinued) + d.logger.Infof("RUN") + d.stopped = false + case <-d.step: + d.server.Event(events.EventTypeContinued) + d.server.Event(events.EventTypeStoppedStep) + d.logger.Infof("STEP") + } + // if we don't do this, ELPS' indomitable stack traces prevent the debugger receiving + // calls if we only have a few processor cores + runtime.Gosched() +} + +func (p *Debugger) getFunctionParameters(function *lisp.LVal) (string, string) { + return profiler.GetFunNameFromFID(p.runtime, function.FunData().FID), function.FunData().Package +} + +func (d *Debugger) End(function *lisp.LVal) { + if d.isEvaling { + return + } + d.logger.Debugf("End %v ", function.FunData().FID) + switch function.Type { + case lisp.LFun, lisp.LSymbol, lisp.LSExpr, lisp.LQSymbol: + d.decrementCallRef() + } + // no op for now except that we yield the CPU + runtime.Gosched() +} + +func (d *Debugger) GetBreakpoint(id int) (*api.Breakpoint, error) { + d.Lock() + defer d.Unlock() + if bp, ok := d.breakpoints[id]; ok { + return bp, nil + } + return nil, errors.New("not found") +} + +func (d *Debugger) GetBreakpointByName(name string) (*api.Breakpoint, error) { + d.Lock() + defer d.Unlock() + for _, v := range d.breakpoints { + if v.Name == name { + return v, nil + } + } + return nil, errors.New("not found") +} + +func (d *Debugger) GetModules() []dap.Module { + modules := make([]dap.Module, 0) + for k := range d.runtime.Registry.Packages { + modules = append(modules, dap.Module{ + Id: k, + Name: k, + }) + } + return modules +} + +func (d *Debugger) Eval(text string) string { + d.Lock() + d.isEvaling = true + defer func() { + d.isEvaling = false + d.Unlock() + }() + d.logger.Debugf("Evaluating %s", text) + tEnv := lisp.NewEnv(nil) + tEnv.Runtime.Registry = d.runtime.Registry + tEnv.Runtime.Package = d.runtime.Package + tEnv.Runtime.Reader = d.runtime.Reader + tEnv.Runtime.Debugger = nil + v := tEnv.LoadString("eval", text) + return d.mapValue(v) +} + +func (d *Debugger) GetDapStacktrace() []dap.StackFrame { + current := d.callRefs + out := make([]dap.StackFrame, 0) + for { + if current == nil { + break + } + d.logger.Debugf("Line %s in %s line %d col %d", current.name, current.file, current.line, current.col) + hint := "normal" + origin := "" + if current.file == "Unknown file" || current.file == "" { + hint = "deemphasize" + origin = "internal module" + } + out = append(out, dap.StackFrame{ + Id: 0, + Name: current.name, + Source: dap.Source{ + Name: current.file, + Path: current.path, + PresentationHint: hint, + Origin: origin, + }, + Line: current.line, + Column: current.col, + ModuleId: current.packageName, + PresentationHint: hint, + }) + current = current.prev + } + return out +} + +func (d *Debugger) GetStacktrace(st *rpc2.StacktraceOut) { + d.logger.Debug("Returning STACK") + st.Locations = make([]api.Stackframe, 0) + for _, frame := range d.runtime.Stack.Frames { + var source = frame.Source + if source == nil { + source = &token.Location{ + File: "Unknown file", + Path: "", + Pos: 0, + Line: 0, + Col: 0, + } + } + st.Locations = append(st.Locations, api.Stackframe{ + Location: api.Location{ + PC: 0, + File: fmt.Sprintf("%s", source.Path), + Line: source.Line, + Function: &api.Function{ + Name_: "f", + Value: 0, + Type: 0, + GoType: 0, + Optimized: false, + }, + PCs: []uint64{}, + }, + Locals: []api.Variable{ + + }, + Arguments: []api.Variable{ + + }, + FrameOffset: 0, + FramePointerOffset: 0, + Defers: []api.Defer{}, + Bottom: false, + Err: "", + }) + } +} + +func (d *Debugger) GetAllBreakpoints() map[int]*api.Breakpoint { + d.logger.Debug("Returning BREAKPOINTS") + d.Lock() + defer d.Unlock() + return d.breakpoints +} + +func (d *Debugger) CreateBreakpoint(breakpoint *api.Breakpoint) *api.Breakpoint { + // TODO ADD COLUMNS + d.Lock() + defer d.Unlock() + d.lastModified = time.Now() + if breakpoint.ID == 0 { + breakpoint.ID = rand.Int() + } + d.breakpoints[breakpoint.ID] = breakpoint + return breakpoint +} + +func (d *Debugger) RemoveBreakpoint(id int) error { + d.Lock() + defer d.Unlock() + d.lastModified = time.Now() + _, err := d.GetBreakpoint(id) + if err != nil { + return err + } + d.breakpoints[id] = nil + return nil +} + +func (d *Debugger) AmendBreakpoint(bp *api.Breakpoint) error { + d.Lock() + defer d.Unlock() + d.lastModified = time.Now() + _, err := d.GetBreakpoint(bp.ID) + if err != nil { + return err + } + d.breakpoints[bp.ID] = bp + return nil +} + +func (d *Debugger) GetThread() *api.Thread { + d.logger.Debug("Returning THREADS") + var loc *api.Thread + if d.currentOp != nil { + var source = d.currentOp.source + if source == nil { + source = &token.Location{ + File: "Unknown file", + Path: "", + Pos: 0, + Line: 0, + Col: 0, + } + } + loc = &api.Thread{ + PC: uint64(source.Pos), + File: fmt.Sprintf("%s/%s", d.pwd, source.File), + Line: source.Line, + Function: &api.Function{ + Name_: d.currentOp.name, + Value: 0, + Type: 0, + GoType: 0, + Optimized: false, + }, + ID: 1, + GoroutineID: 1, + } + } else { + loc = &api.Thread{ + ID: 1, + GoroutineID: 1, + } + } + return loc +} + +func mapLispType(in lisp.LType) string { + switch in { + case lisp.LFloat: + return "float" + case lisp.LInt: + return "int" + case lisp.LString: + return "string" + case lisp.LQSymbol: + return "q_symbol" + case lisp.LSExpr: + return "s_expr" + case lisp.LSortMap: + return "sorted map" + case lisp.LNative: + return "native" + case lisp.LFun: + return "function" + case lisp.LQuote: + return "quote" + case lisp.LArray: + return "array" + case lisp.LError: + return "error" + case lisp.LBytes: + return "[]byte" + case lisp.LInvalid: + return "invalid" + default: + return "unknown" + + } +} + +func mapKind(in *lisp.LVal) reflect.Kind { + switch in.Type { + case lisp.LFloat: + return reflect.Float64 + case lisp.LInt: + return reflect.Int + case lisp.LString: + return reflect.String + case lisp.LQSymbol: + return reflect.Struct + case lisp.LSExpr: + return reflect.Struct + case lisp.LSortMap: + return reflect.Map + case lisp.LNative: + return reflect.ValueOf(in.Native).Kind() + case lisp.LFun: + return reflect.Func + case lisp.LQuote: + return reflect.String + case lisp.LArray: + return reflect.Slice + case lisp.LError: + return reflect.Struct + case lisp.LBytes: + return reflect.Slice + case lisp.LInvalid: + return reflect.Invalid + default: + return reflect.Invalid + } +} + +func (d *Debugger) sexprAsString(in *lisp.LVal) string { + out := "" + for _, v := range in.Cells { + out += d.mapValue(v) + " " + } + return out +} + +func (d *Debugger) mapValue(in *lisp.LVal) string { + switch in.Type { + case lisp.LFloat: + return strconv.FormatFloat(in.Float, 'f', 8, 64) + case lisp.LInt: + return strconv.FormatInt(int64(in.Int), 10) + case lisp.LString: + return in.Str + case lisp.LQSymbol: + return in.Str + case lisp.LSymbol: + return in.Str + case lisp.LSExpr: + return d.sexprAsString(in) + case lisp.LSortMap: + return "map" + case lisp.LNative: + return fmt.Sprintf("%v", in.Native) + case lisp.LFun: + return in.FunData().FID + case lisp.LQuote: + return in.Str + case lisp.LArray: + return "array" + case lisp.LError: + return fmt.Sprintf("%v", lisp.GoError(in)) + case lisp.LBytes: + return "array" + case lisp.LInvalid: + return "INVALID" + default: + return in.Type.String() + } +} + +func (d *Debugger) extractChildren(in *lisp.LVal) []api.Variable { + children := make([]api.Variable, 0) + switch in.Type { + case lisp.LSortMap: + for _, v := range in.Cells[:1] { + children = append(children, api.Variable{ + Type: mapLispType(v.Type), + RealType: mapLispType(v.Type), + Value: d.mapValue(v), + Kind: mapKind(v), + }) + } + case lisp.LArray: + for _, v := range in.Cells[:1] { + children = append(children, api.Variable{ + Type: mapLispType(v.Type), + RealType: mapLispType(v.Type), + Value: d.mapValue(v), + Kind: mapKind(v), + }) + } + case lisp.LBytes: + for _, v := range in.Bytes() { + children = append(children, api.Variable{ + Type: "byte", + RealType: "byte", + Value: string(v), + Kind: reflect.Uint8, + }) + } + } + return children +} + +func (d *Debugger) GetVariables() []api.Variable { + d.logger.Debug("Returning VARS") + d.Lock() + defer d.Unlock() + out := make([]api.Variable, 0) + count := 0 + // deliberate copy - prevents us having to stop + symbols := d.runtime.Package.Symbols + for k, v := range symbols { + if v.Type == lisp.LFun && v.FunData().Builtin != nil { + continue + } + var source = v.Source + if source == nil { + source = &token.Location{ + File: "Unknown file", + Path: "", + Pos: 0, + Line: 0, + Col: 0, + } + } + children := make([]api.Variable, 0) + strVal := d.mapValue(v) + if strVal == "map" || strVal == "array" { + children = d.extractChildren(v) + } + out = append(out, api.Variable{ + Name: k, + Addr: uintptr(count), + OnlyAddr: false, + Type: mapLispType(v.Type), + RealType: mapLispType(v.Type), + Flags: 0, + Kind: mapKind(v), + Value: strVal, + Len: int64(len(children)), + Cap: int64(len(children)), + Children: children, + Base: 0, + Unreadable: "", + LocationExpr: "", + DeclLine: int64(source.Line), + }) + count++ + } + return out +} + +func (d *Debugger) GetFunctionArgs() []api.Variable { + d.logger.Debug("Returning ARGS") + return []api.Variable{} +} + +func (d *Debugger) GetGoRoutine() *api.Goroutine { + d.logger.Debug("Returning GOROUTINE") + d.Lock() + defer d.Unlock() + var loc *api.Location + if d.currentOp != nil { + var source = d.currentOp.source + if source == nil { + source = &token.Location{ + File: "Unknown file", + Path: "", + Pos: 0, + Line: 0, + Col: 0, + } + } + loc = &api.Location{ + PC: uint64(source.Pos), + File: fmt.Sprintf("%s/%s", d.pwd, source.File), + Line: source.Line, + Function: &api.Function{ + Name_: d.currentOp.name, + Value: 0, + Type: 0, + GoType: 0, + Optimized: false, + }, + PCs: nil, + } + } else { + loc = &api.Location{ + PC: 0, + } + } + return &api.Goroutine{ + ID: 1, + CurrentLoc: *loc, + UserCurrentLoc: *loc, + GoStatementLoc: *loc, + StartLoc: api.Location{}, + ThreadID: 1, + Unreadable: "", + } +} + +func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol string, value string) error { + d.logger.Infof("SETTING variable %s to %s", symbol, value) + d.Lock() + defer d.Unlock() + d.lastModified = time.Now() + if existing, exists := d.runtime.Package.Symbols[symbol]; exists { + switch existing.Type { + case lisp.LString: + d.runtime.Package.Symbols[symbol].Str = value + return nil + case lisp.LInt: + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + d.runtime.Package.Symbols[symbol].Int = int(v) + return nil + case lisp.LFloat: + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + d.runtime.Package.Symbols[symbol].Float = v + return nil + } + return errors.New("Cannot set this type") + } + return errors.New("Symbol not in scope") +} + +func (d *Debugger) Sources(filter string) ([]string, error) { + d.logger.Debug("Returning SOURCES") + intermediate := make(map[string]bool) + for _, currPkg := range d.runtime.Registry.Packages { + d.appendSourcesToMap(intermediate, currPkg, filter) + } + out := make([]string, 0) + for k := range intermediate { + out = append(out, k) + } + return out, nil +} + +func (d *Debugger) getSourcesForPackage(currPkg *lisp.Package) []string { + intermediate := make(map[string]bool) + d.appendSourcesToMap(intermediate, currPkg, "") + out := make([]string, 0) + for k := range intermediate { + out = append(out, k) + } + return out +} + +func (d *Debugger) GetArguments() []dap.Variable { + out := make([]dap.Variable, 0) + for k, v := range d.currentOp.args { + argValue := d.mapValue(v) + if d.currentOp.name != "defun" && d.currentOp.name != "set" && d.currentOp.name != "let" && d.currentOp.name != "let*" && d.currentOp.name != "defmacro" { + if v.Type == lisp.LSymbol { + d.logger.Infof("Remapping %s", argValue) + argValue = d.Eval(argValue) + } + } + out = append(out, dap.Variable{ + Name: k, + Value: argValue, + Type: mapLispType(v.Type), + }) + d.logger.Infof("Sending arg %s as %s", out[len(out)-1].Name, out[len(out)-1].Value) + } + d.logger.Debugf("Args: %v", out) + return out +} + +func (d *Debugger) appendSourcesToMap(intermediate map[string]bool, currPkg *lisp.Package, filter string) { + for _, sym := range currPkg.Symbols { + if sym.Source == nil { + continue + } + if filter != "" && !strings.HasPrefix(fmt.Sprintf("%s/%s", d.pwd, sym.Source.File), filter) { + continue + } + intermediate[fmt.Sprintf("%s/%s", d.pwd, sym.Source.File)] = true + } +} + +func (d *Debugger) Functions(filter string) ([]string, error) { + d.logger.Debug("Returning FUNCTIONS") + d.Lock() + defer d.Unlock() + out := make([]string, 0) + seen := make(map[string]bool) + for _, v := range d.runtime.Package.FunNames { + if seen[v] { + continue + } + out = append(out, v) + } + return out, nil +} + +func (d *Debugger) FindLocation(scope api.EvalScope, loc string, lines bool) ([]api.Location, error) { + return nil, errors.New("Not implemented... yet") +} + +func (d *Debugger) ListPackagesBuildInfo(files bool) []api.PackageBuildInfo { + d.logger.Debug("Returning BUILD INFO") + d.Lock() + defer d.Unlock() + out := make([]api.PackageBuildInfo, 0) + for k, v := range d.runtime.Registry.Packages { + out = append(out, api.PackageBuildInfo{ + ImportPath: k, + DirectoryPath: v.Name, + Files: d.getSourcesForPackage(v), + }) + } + return out +} + +func (d *Debugger) State(blocking bool) (*api.DebuggerState, error) { + d.logger.Debug("Returning STATE") + state := &api.DebuggerState{ + Running: !d.stopped, + CurrentThread: d.GetThread(), + SelectedGoroutine: d.GetGoRoutine(), + Threads: []*api.Thread{d.GetThread()}, + NextInProgress: false, + Exited: !d.enabled, + ExitStatus: 0, + When: "", + Err: nil, + } + return state, nil +} + +func (d *Debugger) Continue() { + if d.stopped { + go func(d *Debugger) { + d.run <- true + }(d) + } +} + +func (d *Debugger) Step() { + if d.stopped { + go func(d *Debugger) { + d.step <- true + }(d) + } +} + +func (d *Debugger) Halt() { + d.stopped = true +} + +func (d *Debugger) Command(a *api.DebuggerCommand) (*api.DebuggerState, error) { + // TODO this is Delve specific + d.logger.Debug("Command: %s", a.Name) + d.lastModified = time.Now() + started := false + switch a.Name { + case api.Halt: + d.Halt() + case api.Continue: + started = true + d.Continue() + case api.Next: + return nil, errors.New("Not implemented") + case api.Step: + started = true + d.Step() + case api.Call: + return nil, errors.New("Not implemented") + case api.ReverseStepInstruction: + return nil, errors.New("Not implemented") + case api.Rewind: + return nil, errors.New("Not implemented") + case api.SwitchGoroutine: + return nil, errors.New("Not implemented") + case api.SwitchThread: + return nil, errors.New("Not implemented") + } + state, err := d.State(false) + if err != nil && started == true { + state.Running = true + } + return state, err +} + +func (d *Debugger) LastModified() time.Time { + return d.lastModified +} + +// Generates a call ref so the same item can be located again +func (p *Debugger) incrementCallRef(expr, function *lisp.LVal) *callRef { + p.Lock() + defer p.Unlock() + name, module := p.getFunctionParameters(function) + frameRef := new(callRef) + frameRef.name = name + frameRef.children = make([]*callRef, 0) + if function.Source != nil { + frameRef.file = expr.Source.File + frameRef.line = expr.Source.Line + frameRef.col = expr.Source.Col + frameRef.path = expr.Source.Path + frameRef.packageName = module + } + if len(p.runtime.Stack.Frames) > 0 { + p.logger.Debug("Overriding...") + current := p.runtime.Stack.Frames[len(p.runtime.Stack.Frames)-1] + if frameRef.file == "" { + p.logger.Debugf("Overriding... file %s", current.Source.File) + frameRef.file = current.Source.File + } + if frameRef.path == "" { + p.logger.Debugf("Overriding... path %s", current.Source.Path) + frameRef.path = current.Source.Path + } + if frameRef.line == 0 { + frameRef.line = current.Source.Line + } + if frameRef.col == 0 { + frameRef.col = current.Source.Col + } + } + current := p.callRefs + if current != nil { + frameRef.prev = current + frameRef.prev.children = append(frameRef.prev.children, frameRef) + } + frameRef.start = time.Now() + p.callRefs = frameRef + return frameRef +} + +// Finds a call ref for the current scope +func (p *Debugger) decrementCallRef() *callRef { + current := p.callRefs + p.callRefs = current.prev + return current +} + +// Represents something that got called +type callRef struct { + start time.Time + prev *callRef + name string + children []*callRef + file string + path string + line int + col int + packageName string +} diff --git a/lisp/x/debugger/debugger_test.go b/lisp/x/debugger/debugger_test.go new file mode 100644 index 0000000..2c2e911 --- /dev/null +++ b/lisp/x/debugger/debugger_test.go @@ -0,0 +1,54 @@ +package debugger + +import ( + "github.com/go-delve/delve/service/api" + "github.com/luthersystems/elps/lisp" + "github.com/luthersystems/elps/parser" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewDebugger(t *testing.T) { + env := lisp.NewEnv(nil) + env.Runtime.Reader = parser.NewReader() + lerr := lisp.InitializeUserEnv(env) + if lisp.GoError(lerr) != nil { + t.Fatal(lisp.GoError(lerr)) + } + // Create a profiler + profiler := NewDebugger(env, ":8883", DebugModeDelve) + profiler.(*Debugger).CreateBreakpoint(&api.Breakpoint{ + ID: 1, + Name: "REUBEN TEST", + Addr: 0, + Addrs: []uint64{}, + File: "test.lisp", + Line: 3, + }) + var testsrc *lisp.LVal + // Some spurious functions to check we get a profile out + testsrc = env.LoadString("test.lisp", ` +(set 'j 4) +(defun print-it + ('x) + (debug-print x) +) +(defun add-it + ('x 'y) + (+ x y) +) +(defun recurse-it + ('x) + (if + (< x j) + (recurse-it (- x 1)) + (add-it x 3) + ) +) +(print-it "Hello") +(print-it (add-it (add-it 3 (recurse-it 5)) 8))`) + lerr = env.Eval(testsrc) + assert.NotEqual(t, lisp.LError, lerr.Type) + profiler.Complete() + t.Fail() +} diff --git a/lisp/x/debugger/delveserver/rpc.go b/lisp/x/debugger/delveserver/rpc.go new file mode 100644 index 0000000..4e92577 --- /dev/null +++ b/lisp/x/debugger/delveserver/rpc.go @@ -0,0 +1,302 @@ +package delveserver + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/go-delve/delve/pkg/logflags" + "github.com/sirupsen/logrus" + "io" + "net/rpc" + "net/rpc/jsonrpc" + "reflect" + "runtime" + "sync" + "unicode" + "unicode/utf8" +) + +func (s *RPCServer) Run() error { + go func() { + defer s.listener.Close() + for { + c, err := s.listener.Accept() + if err != nil { + s.log.Errorf("Error starting: %v", err) + select { + case <-s.stopChan: + // We were supposed to exit, do nothing and return + return + default: + logrus.Errorf("%s", err.Error()) + return + } + } + + go s.serveJSONCodec(c) + } + }() + return nil +} + +func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logrus.Entry) { + typ := reflect.TypeOf(rcvr) + rcvrv := reflect.ValueOf(rcvr) + sname := reflect.Indirect(rcvrv).Type().Name() + if sname == "" { + log.Debugf("rpc.Register: no service name for type %s", typ) + return + } + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + mname := method.Name + mtype := method.Type + // method must be exported + if method.PkgPath != "" { + continue + } + // Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback) + if mtype.NumIn() != 3 { + log.Warn("method", mname, "has wrong number of ins:", mtype.NumIn()) + continue + } + // First arg need not be a pointer. + argType := mtype.In(1) + if !isExportedOrBuiltinType(argType) { + log.Warn(mname, "argument type not exported:", argType) + continue + } + + replyType := mtype.In(2) + synchronous := replyType.String() != "service.RPCCallback" + + if synchronous { + // Second arg must be a pointer. + if replyType.Kind() != reflect.Ptr { + log.Warn("method", mname, "reply type not a pointer:", replyType) + continue + } + // Reply type must be exported. + if !isExportedOrBuiltinType(replyType) { + log.Warn("method", mname, "reply type not exported:", replyType) + continue + } + + // Method needs one out. + if mtype.NumOut() != 1 { + log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut()) + continue + } + // The return type of the method must be error. + if returnType := mtype.Out(0); returnType != typeOfError { + log.Warn("method", mname, "returns", returnType.String(), "not error") + continue + } + } else { + // Method needs zero outs. + if mtype.NumOut() != 0 { + log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut()) + continue + } + } + methods[sname+"."+mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType, Synchronous: synchronous, Rcvr: rcvrv} + } +} + +type methodType struct { + method reflect.Method + Rcvr reflect.Value + ArgType reflect.Type + ReplyType reflect.Type + Synchronous bool +} + +var typeOfError = reflect.TypeOf((*error)(nil)).Elem() + +func isExportedOrBuiltinType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} + +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +var invalidRequest = struct{}{} + +func (s *RPCServer) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *rpc.Response, reply interface{}, codec rpc.ServerCodec, errmsg string) { + resp.ServiceMethod = req.ServiceMethod + if errmsg != "" { + resp.Error = errmsg + reply = invalidRequest + } + resp.Seq = req.Seq + sending.Lock() + defer sending.Unlock() + err := codec.WriteResponse(resp, reply) + if err != nil { + s.log.Error("writing response:", err) + } +} + +type internalError struct { + Err interface{} + Stack []internalErrorFrame +} + +type internalErrorFrame struct { + Pc uintptr + Func string + File string + Line int +} + +func newInternalError(ierr interface{}, skip int) *internalError { + r := &internalError{ierr, nil} + for i := skip; ; i++ { + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + fname := "" + fn := runtime.FuncForPC(pc) + if fn != nil { + fname = fn.Name() + } + r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line}) + } + return r +} + +func (err *internalError) Error() string { + var out bytes.Buffer + fmt.Fprintf(&out, "Internal debugger error: %v\n", err.Err) + for _, frame := range err.Stack { + fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line) + } + return out.String() +} + +func (s *RPCServer) serveJSONCodec(conn io.ReadWriteCloser) { + sending := new(sync.Mutex) + codec := jsonrpc.NewServerCodec(conn) + var req rpc.Request + var resp rpc.Response + for { + req = rpc.Request{} + err := codec.ReadRequestHeader(&req) + if err != nil { + if err != io.EOF { + s.log.Error("rpc:", err) + } + break + } + + mtype, ok := s.methods[req.ServiceMethod] + if !ok { + s.log.Errorf("rpc: can't find method %s", req.ServiceMethod) + s.sendResponse(sending, &req, &rpc.Response{}, nil, codec, fmt.Sprintf("unknown method: %s", req.ServiceMethod)) + continue + } + + var argv, replyv reflect.Value + + // Decode the argument value. + argIsValue := false // if true, need to indirect before calling. + if mtype.ArgType.Kind() == reflect.Ptr { + argv = reflect.New(mtype.ArgType.Elem()) + } else { + argv = reflect.New(mtype.ArgType) + argIsValue = true + } + // argv guaranteed to be a pointer now. + if err = codec.ReadRequestBody(argv.Interface()); err != nil { + return + } + if argIsValue { + argv = argv.Elem() + } + s.log.Infof("Received req for %s with arg %v", mtype.method.Name, argv) + if mtype.Synchronous { + if logflags.RPC() { + argvbytes, _ := json.Marshal(argv.Interface()) + s.log.Debugf("<- %s(%T%s)", req.ServiceMethod, argv.Interface(), argvbytes) + } + replyv = reflect.New(mtype.ReplyType.Elem()) + function := mtype.method.Func + var returnValues []reflect.Value + var errInter interface{} + func() { + defer func() { + if ierr := recover(); ierr != nil { + errInter = newInternalError(ierr, 2) + } + }() + returnValues = function.Call([]reflect.Value{mtype.Rcvr, argv, replyv}) + errInter = returnValues[0].Interface() + }() + + errmsg := "" + if errInter != nil { + errmsg = errInter.(error).Error() + } + resp = rpc.Response{} + if logflags.RPC() { + replyvbytes, _ := json.Marshal(replyv.Interface()) + s.log.Debugf("-> %T%s error: %q", replyv.Interface(), replyvbytes, errmsg) + } + s.sendResponse(sending, &req, &resp, replyv.Interface(), codec, errmsg) + } else { + if logflags.RPC() { + argvbytes, _ := json.Marshal(argv.Interface()) + s.log.Debugf("(async %d) <- %s(%T%s)", req.Seq, req.ServiceMethod, argv.Interface(), argvbytes) + } + function := mtype.method.Func + ctl := &RPCCallback{s, sending, codec, req} + go func() { + defer func() { + if ierr := recover(); ierr != nil { + ctl.Return(nil, newInternalError(ierr, 2)) + } + }() + function.Call([]reflect.Value{mtype.Rcvr, argv, reflect.ValueOf(ctl)}) + }() + } + } + codec.Close() +} + +func (cb *RPCCallback) Return(out interface{}, err error) { + errmsg := "" + if err != nil { + errmsg = err.Error() + } + var resp rpc.Response + if logflags.RPC() { + outbytes, _ := json.Marshal(out) + cb.s.log.Debugf("(async %d) -> %T%s error: %q", cb.req.Seq, out, outbytes, errmsg) + } + cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg) +} + +type RPCCallback struct { + s *RPCServer + sending *sync.Mutex + codec rpc.ServerCodec + req rpc.Request +} + +// Stop stops the JSON-RPC delveserver. +func (s *RPCServer) Stop() error { + if s.stopChan != nil { + close(s.stopChan) + s.stopChan = nil + } + s.listener.Close() + return s.debugger.Complete() +} diff --git a/lisp/x/debugger/delveserver/server.go b/lisp/x/debugger/delveserver/server.go new file mode 100644 index 0000000..8f3208b --- /dev/null +++ b/lisp/x/debugger/delveserver/server.go @@ -0,0 +1,325 @@ +package delveserver + +import ( + "errors" + "fmt" + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/service" + "github.com/go-delve/delve/service/api" + "github.com/go-delve/delve/service/rpc2" + "github.com/luthersystems/elps/lisp/x/debugger/events" + "github.com/sirupsen/logrus" + "net" +) + +type RPCServer struct { + // debugger is a debugger service. + debugger ServerDebugger + // listener is used to serve HTTP. + listener net.Listener + // stopChan is used to stop the listener goroutine. + stopChan chan struct{} + log *logrus.Entry + methods map[string]*methodType +} + +func NewServer(debugger ServerDebugger, address string) *RPCServer { + listener, err := net.Listen("tcp", address) + if err != nil { + panic(fmt.Sprintf("Can't bind to %s: %v", address, err)) + } + logger := logflags.RPCLogger() + logflags.Setup(true, "", "") + logflags.WriteAPIListeningMessage(address) + logger.Level = logrus.DebugLevel + rpc := &RPCServer{ + listener: listener, + debugger: debugger, + stopChan: make(chan struct{}), + log: logger, + } + methods := make(map[string]*methodType) + suitableMethods(rpc, methods, rpc.log) + rpc.methods = methods + return rpc +} + +func (s *RPCServer) Breakpoint(v *api.Breakpoint) { + // no-op +} + +func (s *RPCServer) LastModified(arg rpc2.LastModifiedIn, out *rpc2.LastModifiedOut) error { + out.Time = s.debugger.LastModified() + return nil +} + +func (s *RPCServer) Event(x events.EventType) { + // no-op +} + +// Detach detaches the debugger, optionally killing the process. +func (s *RPCServer) Detach(arg rpc2.DetachIn, out *rpc2.DetachOut) error { + err := s.debugger.Complete() + if arg.Kill { + panic("Quitting on command of debugger") + } + return err +} + +// Restart restarts program. +func (s *RPCServer) Restart(arg rpc2.RestartIn, cb service.RPCCallback) { + panic("Not implemented") +} + +// State returns the current debugger state. +func (s *RPCServer) State(arg rpc2.StateIn, cb service.RPCCallback) { + var out rpc2.StateOut + st, err := s.debugger.State(arg.NonBlocking) + if err != nil { + cb.Return(nil, err) + return + } + out.State = st + cb.Return(out, nil) +} + +// Command interrupts, continues and steps through the program. +func (s *RPCServer) Command(command api.DebuggerCommand, cb service.RPCCallback) { + st, err := s.debugger.Command(&command) + if err != nil { + cb.Return(nil, err) + return + } + var out rpc2.CommandOut + out.State = *st + cb.Return(out, nil) +} + +// GetBreakpoint gets a breakpoint by Name (if Name is not an empty string) or by ID. +func (s *RPCServer) GetBreakpoint(arg rpc2.GetBreakpointIn, out *rpc2.GetBreakpointOut) error { + var bp *api.Breakpoint + var err error + if arg.Name != "" { + bp, err = s.debugger.GetBreakpointByName(arg.Name) + if err == nil { + return fmt.Errorf("no breakpoint with name %s", arg.Name) + } + } else { + bp, err = s.debugger.GetBreakpoint(arg.Id) + if err == nil { + return fmt.Errorf("no breakpoint with id %d", arg.Id) + } + } + out.Breakpoint = *bp + return nil +} + +// Stacktrace returns stacktrace of goroutine Id up to the specified Depth. +// +// If Full is set it will also the variable of all local variables +// and function arguments of all stack frames. +func (s *RPCServer) Stacktrace(arg rpc2.StacktraceIn, out *rpc2.StacktraceOut) error { + s.debugger.GetStacktrace(out) + return nil +} + +// Ancestors returns the stacktraces for the ancestors of a goroutine. +func (s *RPCServer) Ancestors(arg rpc2.AncestorsIn, out *rpc2.AncestorsOut) error { + out.Ancestors = []api.Ancestor{} + return nil +} + +// ListBreakpoints gets all breakpoints. +func (s *RPCServer) ListBreakpoints(arg rpc2.ListBreakpointsIn, out *rpc2.ListBreakpointsOut) error { + breakpoints := s.debugger.GetAllBreakpoints() + for _, v := range breakpoints { + out.Breakpoints = append(out.Breakpoints, v) + } + return nil +} + +func (s *RPCServer) CreateBreakpoint(arg rpc2.CreateBreakpointIn, out *rpc2.CreateBreakpointOut) error { + out.Breakpoint = *s.debugger.CreateBreakpoint(&arg.Breakpoint) + return nil +} + +func (s *RPCServer) ClearBreakpoint(arg rpc2.ClearBreakpointIn, out *rpc2.ClearBreakpointOut) error { + var bp *api.Breakpoint + var err error + if arg.Name != "" { + bp, err = s.debugger.GetBreakpointByName(arg.Name) + if err == nil { + return fmt.Errorf("no breakpoint with name %s", arg.Name) + } + } else { + bp, err = s.debugger.GetBreakpoint(arg.Id) + if err == nil { + return fmt.Errorf("no breakpoint with id %d", arg.Id) + } + } + err = s.debugger.RemoveBreakpoint(bp.ID) + if err != nil { + return err + } + out.Breakpoint = bp + return nil +} + +func (s *RPCServer) AmendBreakpoint(arg rpc2.AmendBreakpointIn, out *rpc2.AmendBreakpointOut) error { + return s.debugger.AmendBreakpoint(&arg.Breakpoint) +} + +func (s *RPCServer) CancelNext(arg rpc2.CancelNextIn, out *rpc2.CancelNextOut) error { + return nil +} + +func (s *RPCServer) ListThreads(arg rpc2.ListThreadsIn, out *rpc2.ListThreadsOut) (err error) { + out.Threads = []*api.Thread{ + s.debugger.GetThread(), + } + return nil +} + +func (s *RPCServer) GetThread(arg rpc2.GetThreadIn, out *rpc2.GetThreadOut) error { + if arg.Id != 0 { + return errors.New("We've only got one thread...") + } + out.Thread = s.debugger.GetThread() + return nil +} + +func (s *RPCServer) ListPackageVars(arg rpc2.ListPackageVarsIn, out *rpc2.ListPackageVarsOut) error { + out.Variables = s.debugger.GetVariables() + return nil +} + +func (s *RPCServer) ListRegisters(arg rpc2.ListRegistersIn, out *rpc2.ListRegistersOut) error { + out.Registers = "" + out.Regs = []api.Register{} + return nil +} + +func (s *RPCServer) ListLocalVars(arg rpc2.ListLocalVarsIn, out *rpc2.ListLocalVarsOut) error { + out.Variables = s.debugger.GetVariables() + return nil +} + +func (s *RPCServer) ListFunctionArgs(arg rpc2.ListFunctionArgsIn, out *rpc2.ListFunctionArgsOut) error { + out.Args = s.debugger.GetFunctionArgs() + return nil +} + +func (s *RPCServer) Eval(arg rpc2.EvalIn, out *rpc2.EvalOut) error { + return nil +} + +func (s *RPCServer) Set(arg rpc2.SetIn, out *rpc2.SetOut) error { + return s.debugger.SetVariableInScope(arg.Scope, arg.Symbol, arg.Value) +} + +func (s *RPCServer) ListSources(arg rpc2.ListSourcesIn, out *rpc2.ListSourcesOut) error { + ss, err := s.debugger.Sources(arg.Filter) + if err != nil { + return err + } + out.Sources = ss + return nil +} + +func (s *RPCServer) ListFunctions(arg rpc2.ListFunctionsIn, out *rpc2.ListFunctionsOut) error { + fns, err := s.debugger.Functions(arg.Filter) + if err != nil { + return err + } + out.Funcs = fns + return nil +} + +func (s *RPCServer) ListTypes(arg rpc2.ListTypesIn, out *rpc2.ListTypesOut) error { + return nil +} + +func (s *RPCServer) ListGoroutines(arg rpc2.ListGoroutinesIn, out *rpc2.ListGoroutinesOut) error { + out.Goroutines = []*api.Goroutine{ + s.debugger.GetGoRoutine(), + } + return nil +} + +func (c *RPCServer) AttachedToExistingProcess(arg rpc2.AttachedToExistingProcessIn, out *rpc2.AttachedToExistingProcessOut) error { + out.Answer = true + return nil +} + +// FindLocation returns concrete location information described by a location expression. +// +// loc ::= : | [:] | // | (+|-) | | *
+// * can be the full path of a file or just a suffix +// * ::= .. | .(*). | . | . | (*). | +// * must be unambiguous +// * // will return a location for each function matched by regex +// * + returns a location for the line that is lines after the current line +// * - returns a location for the line that is lines before the current line +// * returns a location for a line in the current file +// * *
returns the location corresponding to the specified address +// +// NOTE: this function does not actually set breakpoints. +func (c *RPCServer) FindLocation(arg rpc2.FindLocationIn, out *rpc2.FindLocationOut) error { + var err error + out.Locations, err = c.debugger.FindLocation(arg.Scope, arg.Loc, arg.IncludeNonExecutableLines) + return err +} + +func (c *RPCServer) Disassemble(arg rpc2.DisassembleIn, out *rpc2.DisassembleOut) error { + return errors.New("Can't dissasemble Lisp...") +} + +func (s *RPCServer) Recorded(arg rpc2.RecordedIn, out *rpc2.RecordedOut) error { + return nil +} + +func (s *RPCServer) Checkpoint(arg rpc2.CheckpointIn, out *rpc2.CheckpointOut) error { + return errors.New("Not implemented") +} + +func (s *RPCServer) ListCheckpoints(arg rpc2.ListCheckpointsIn, out *rpc2.ListCheckpointsOut) error { + return errors.New("Not implemented") +} + +func (s *RPCServer) ClearCheckpoint(arg rpc2.ClearCheckpointIn, out *rpc2.ClearCheckpointOut) error { + return errors.New("Not implemented") +} + +func (s *RPCServer) IsMulticlient(arg rpc2.IsMulticlientIn, out *rpc2.IsMulticlientOut) error { + out.IsMulticlient = false + return nil +} + +func (s *RPCServer) FunctionReturnLocations(in rpc2.FunctionReturnLocationsIn, out *rpc2.FunctionReturnLocationsOut) error { + return nil +} + +func (s *RPCServer) ListDynamicLibraries(in rpc2.ListDynamicLibrariesIn, out *rpc2.ListDynamicLibrariesOut) error { + return nil +} + +func (s *RPCServer) ListPackagesBuildInfo(in rpc2.ListPackagesBuildInfoIn, out *rpc2.ListPackagesBuildInfoOut) error { + out.List = s.debugger.ListPackagesBuildInfo(in.IncludeFiles) + return nil +} + +type StopRecordingIn struct { +} + +type StopRecordingOut struct { +} + +func (s *RPCServer) StopRecording(arg StopRecordingIn, cb service.RPCCallback) { + var out StopRecordingOut + err := s.debugger.Complete() + if err != nil { + cb.Return(nil, err) + return + } + cb.Return(out, nil) +} diff --git a/lisp/x/debugger/delveserver/server_debugger.go b/lisp/x/debugger/delveserver/server_debugger.go new file mode 100644 index 0000000..bea2375 --- /dev/null +++ b/lisp/x/debugger/delveserver/server_debugger.go @@ -0,0 +1,39 @@ +package delveserver + +import ( + "github.com/go-delve/delve/service/api" + "github.com/go-delve/delve/service/rpc2" + "github.com/google/go-dap" + "time" +) + +type ServerDebugger interface { + GetBreakpoint(id int) (*api.Breakpoint, error) + GetBreakpointByName(name string) (*api.Breakpoint, error) + GetStacktrace(st *rpc2.StacktraceOut) + GetDapStacktrace() []dap.StackFrame + GetAllBreakpoints() map[int]*api.Breakpoint + CreateBreakpoint(breakpoint *api.Breakpoint) *api.Breakpoint + RemoveBreakpoint(id int) error + AmendBreakpoint(bp *api.Breakpoint) error + GetThread() *api.Thread + GetVariables() []api.Variable + GetFunctionArgs() []api.Variable + SetVariableInScope(scope api.EvalScope, symbol string, value string) error + Sources(filter string) ([]string, error) + Functions(filter string) ([]string, error) + FindLocation(scope api.EvalScope, loc string, lines bool) ([]api.Location, error) + ListPackagesBuildInfo(files bool) []api.PackageBuildInfo + State(blocking bool) (*api.DebuggerState, error) + Command(a *api.DebuggerCommand) (*api.DebuggerState, error) + LastModified() time.Time + Complete() error + GetGoRoutine() *api.Goroutine + Step() + Continue() + Halt() + IsStopped() bool + GetModules() []dap.Module + Eval(text string) string + GetArguments() []dap.Variable +} diff --git a/lisp/x/debugger/events/events.go b/lisp/x/debugger/events/events.go new file mode 100644 index 0000000..6ea486f --- /dev/null +++ b/lisp/x/debugger/events/events.go @@ -0,0 +1,13 @@ +package events + +type EventType string + +const EventTypeStarted = EventType("started") +const EventTypeContinued = EventType("continued") +const EventTypeExited = EventType("exited") +const EventTypeStoppedBreakpoint = EventType("stopped-breakpoint") +const EventTypeStoppedPaused = EventType("stopped-paused") +const EventTypeStoppedStep = EventType("stopped-step") +const EventTypeStoppedEntry = EventType("stopped-entry") +const EventTypeTerminated = EventType("terminated") +const EventTypeInitialized = EventType("initialized") diff --git a/lisp/x/profiler/callgrind.go b/lisp/x/profiler/callgrind.go index 26a615e..41b905f 100644 --- a/lisp/x/profiler/callgrind.go +++ b/lisp/x/profiler/callgrind.go @@ -196,7 +196,7 @@ func (p *callgrindProfiler) getFunctionParameters(function *lisp.LVal) (string, source = function.Source.File line = function.Source.Line } - fName := fmt.Sprintf("%s:%s", function.FunData().Package, getFunNameFromFID(p.runtime, function.FunData().FID)) + fName := fmt.Sprintf("%s:%s", function.FunData().Package, GetFunNameFromFID(p.runtime, function.FunData().FID)) return source, line, fName } diff --git a/lisp/x/profiler/go_annotator.go b/lisp/x/profiler/go_annotator.go index dd594fd..c4a3c6e 100644 --- a/lisp/x/profiler/go_annotator.go +++ b/lisp/x/profiler/go_annotator.go @@ -67,7 +67,7 @@ func (p *pprofAnnotator) Start(function *lisp.LVal) { // and doing so would have a negative effect on users not profiling - it would either be an extra stack entry // if we always ran inside a context, or a whole conditional code path and the added complication that brings // if we did it that way. - fName := fmt.Sprintf("%s:%s", function.FunData().Package, getFunNameFromFID(p.runtime, function.FunData().FID)) + fName := fmt.Sprintf("%s:%s", function.FunData().Package, GetFunNameFromFID(p.runtime, function.FunData().FID)) labels := pprof.Labels("function", fName) p.contexts.Push(p.currentContext) p.currentContext = pprof.WithLabels(p.currentContext, labels) diff --git a/lisp/x/profiler/opencensus_annotator.go b/lisp/x/profiler/opencensus_annotator.go index ca3abb1..297d2f8 100644 --- a/lisp/x/profiler/opencensus_annotator.go +++ b/lisp/x/profiler/opencensus_annotator.go @@ -70,7 +70,7 @@ func (p *ocAnnotator) Start(function *lisp.LVal) { // We don't need to profile these types. We could, but we're not that LISP :D return case lisp.LFun, lisp.LSymbol, lisp.LSExpr: - fName := fmt.Sprintf("%s:%s", function.FunData().Package, getFunNameFromFID(p.runtime, function.FunData().FID)) + fName := fmt.Sprintf("%s:%s", function.FunData().Package, GetFunNameFromFID(p.runtime, function.FunData().FID)) p.contexts.Push(p.currentContext) p.currentContext, p.currentSpan = trace.StartSpan(p.currentContext, fName) default: diff --git a/lisp/x/profiler/shared.go b/lisp/x/profiler/shared.go index c752137..497fc53 100644 --- a/lisp/x/profiler/shared.go +++ b/lisp/x/profiler/shared.go @@ -8,7 +8,7 @@ import ( var builtinRegex = regexp.MustCompile("\\<(?:builtin|special)-[a-z]+ \\`\\`(.*)\\'\\'\\>") // Gets a canonical version of the function name suitable for viewing in KCacheGrind -func getFunNameFromFID(rt *lisp.Runtime, in string) string { +func GetFunNameFromFID(rt *lisp.Runtime, in string) string { // Most of the time we can just look this up in FunNames if name, ok := rt.Package.FunNames[in]; ok { return name