Skip to content

Commit

Permalink
feat(gnovm): add /debug command in REPL (#2567)
Browse files Browse the repository at this point in the history
![gno-repl-dbg](https://github.com/gnolang/gno/assets/5792239/1bab47bc-42b3-4378-b99d-58f4a22b43c6)

The REPL /debug command activates the gnovm debugger on the next
evaluation, allowing interactive debugging of interactive sessions.

One interest of this feature is the capability to run the debugger at no
other cost also from the Gno playground.

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [*] Added new tests, or not needed, or not feasible
- [*] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [*] Updated the official documentation or not needed
- [*] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [*] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
  • Loading branch information
mvertes committed Jul 24, 2024
1 parent 294ba90 commit 5f0dc50
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 9 deletions.
3 changes: 3 additions & 0 deletions gnovm/cmd/gno/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func execRepl(cfg *replCfg, args []string) error {
// gno> import "gno.land/p/demo/avl" // import the p/demo/avl package
// gno> func a() string { return "a" } // declare a new function named a
// gno> /src // print current generated source
// gno> /debug // activate the GnoVM debugger
// gno> /editor // enter in multi-line mode, end with ';'
// gno> /reset // remove all previously inserted code
// gno> println(a()) // print the result of calling a()
Expand Down Expand Up @@ -141,6 +142,8 @@ func handleInput(r *repl.Repl, input string) error {
switch strings.TrimSpace(input) {
case "/reset":
r.Reset()
case "/debug":
r.Debug()
case "/src":
fmt.Fprintln(os.Stdout, r.Src())
case "/exit":
Expand Down
39 changes: 30 additions & 9 deletions gnovm/pkg/gnolang/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,29 @@ type Debugger struct {
out io.Writer // debugger output, defaults to Stdout
scanner *bufio.Scanner // to parse input per line

state DebugState // current state of debugger
lastCmd string // last debugger command
lastArg string // last debugger command arguments
loc Location // source location of the current machine instruction
prevLoc Location // source location of the previous machine instruction
breakpoints []Location // list of breakpoints set by user, as source locations
call []Location // for function tracking, ideally should be provided by machine frame
frameLevel int // frame level of the current machine instruction
state DebugState // current state of debugger
lastCmd string // last debugger command
lastArg string // last debugger command arguments
loc Location // source location of the current machine instruction
prevLoc Location // source location of the previous machine instruction
breakpoints []Location // list of breakpoints set by user, as source locations
call []Location // for function tracking, ideally should be provided by machine frame
frameLevel int // frame level of the current machine instruction
getSrc func(string) string // helper to access source from repl or others
}

// Enable makes the debugger d active, using in as input reader, out as output writer and f as a source helper.
func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string) string) {
d.in = in
d.out = out
d.enabled = true
d.state = DebugAtInit
d.getSrc = f
}

// Disable makes the debugger d inactive.
func (d *Debugger) Disable() {
d.enabled = false
}

type debugCommand struct {
Expand Down Expand Up @@ -484,7 +499,13 @@ func debugList(m *Machine, arg string) (err error) {
}
src, err := fileContent(m.Store, loc.PkgPath, loc.File)
if err != nil {
return err
// Use optional getSrc helper as fallback to get source.
if m.Debugger.getSrc != nil {
src = m.Debugger.getSrc(loc.File)
}
if src == "" {
return err
}
}
lines, offset := linesAround(src, loc.Line, 10)
for i, l := range lines {
Expand Down
15 changes: 15 additions & 0 deletions gnovm/pkg/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go/printer"
"go/token"
"io"
"os"
"text/template"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
Expand Down Expand Up @@ -106,6 +107,7 @@ type Repl struct {
stdout io.Writer
stderr io.Writer
stdin io.Reader
debug bool
}

// NewRepl creates a Repl struct. It is able to process input source code and eventually run it.
Expand Down Expand Up @@ -151,6 +153,14 @@ func (r *Repl) Process(input string) (out string, err error) {
}()
r.state.id++

if r.debug {
r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(file string) string {
return r.state.files[file]
})
r.debug = false
defer r.state.machine.Debugger.Disable()
}

decl, declErr := r.parseDeclaration(input)
if declErr == nil {
return r.handleDeclarations(decl)
Expand Down Expand Up @@ -317,3 +327,8 @@ func (r *Repl) Src() string {

return b.String()
}

// Debug activates the GnoVM debugger for the next evaluation.
func (r *Repl) Debug() {
r.debug = true
}

0 comments on commit 5f0dc50

Please sign in to comment.