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