Skip to content

Commit

Permalink
interp: extend dot debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
mpl committed May 26, 2020
1 parent 4a068ea commit 5d56bac
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 9 deletions.
5 changes: 5 additions & 0 deletions cmd/yaegi/yaegi.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ Debugging support (may be removed at any time):
Generate and display graphviz dot of AST with dotty(1)
YAEGI_CFG_DOT=1
Generate and display graphviz dot of CFG with dotty(1)
YAEGI_DOT_CMD='dot -Tsvg -ofoo.svg'
Defines how to process the dot code generated whenever YAEGI_AST_DOT and/or
YAEGI_CFG_DOT is enabled. If any of YAEGI_AST_DOT or YAEGI_CFG_DOT is set,
but YAEGI_DOT_CMD is not defined, the default is to write to a .dot file
next to the Go source file.
*/
package main

Expand Down
30 changes: 26 additions & 4 deletions interp/dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package interp
import (
"fmt"
"io"
"io/ioutil"
"log"
"os/exec"
"path/filepath"
"strings"
)

Expand Down Expand Up @@ -58,10 +60,19 @@ func (n *node) cfgDot(out io.Writer) {
fmt.Fprintf(out, "}\n")
}

// dotX returns an output stream to a dot(1) co-process where to write data in .dot format
func dotX() io.WriteCloser {
cmd := exec.Command("dotty", "-")
//cmd := exec.Command("dot", "-T", "xlib")
type nopCloser struct {
io.Writer
}

func (nopCloser) Close() error { return nil }

// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format
func dotWriter(dotCmd string) io.WriteCloser {
if dotCmd == "" {
return nopCloser{ioutil.Discard}
}
fields := strings.Fields(dotCmd)
cmd := exec.Command(fields[0], fields[1:]...)
dotin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
Expand All @@ -71,3 +82,14 @@ func dotX() io.WriteCloser {
}
return dotin
}

func defaultDotCmd(filePath, prefix string) string {
dir, fileName := filepath.Split(filePath)
ext := filepath.Ext(fileName)
if ext == "" {
fileName += ".dot"
} else {
fileName = strings.Replace(fileName, ext, ".dot", 1)
}
return "dot -Tdot -o" + dir + prefix + fileName
}
24 changes: 20 additions & 4 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ type imports map[string]map[string]*symbol

// opt stores interpreter options
type opt struct {
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
astDot bool // display AST graph (debug)
cfgDot bool // display CFG graph (debug)
// dotCmd is the command to process the dot graph produced when astDot and/or
// cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'.
dotCmd string
noRun bool // compile, but do not run
fastChan bool // disable cancellable chan operations
context build.Context // build context: GOPATH, build constraints
Expand Down Expand Up @@ -224,6 +227,11 @@ func New(options Options) *Interpreter {
// cfgDot activates AST graph display for the interpreter
i.opt.cfgDot, _ = strconv.ParseBool(os.Getenv("YAEGI_CFG_DOT"))

// dotCmd defines how to process the dot code generated whenever astDot and/or
// cfgDot is enabled. It defaults to 'dot -Tdot -o<filename>.dot' where filename
// is context dependent.
i.opt.dotCmd = os.Getenv("YAEGI_DOT_CMD")

// noRun disables the execution (but not the compilation) in the interpreter
i.opt.noRun, _ = strconv.ParseBool(os.Getenv("YAEGI_NO_RUN"))

Expand Down Expand Up @@ -328,7 +336,11 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
}

if interp.astDot {
root.astDot(dotX(), interp.Name)
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.Name, "yaegi-ast-")
}
root.astDot(dotWriter(dotCmd), interp.Name)
if interp.noRun {
return res, err
}
Expand Down Expand Up @@ -363,7 +375,11 @@ func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) {
interp.mutex.Unlock()

if interp.cfgDot {
root.cfgDot(dotX())
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(interp.Name, "yaegi-cfg-")
}
root.cfgDot(dotWriter(dotCmd))
}

if interp.noRun {
Expand Down
7 changes: 6 additions & 1 deletion interp/src.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,13 @@ func (interp *Interpreter) importSrc(rPath, path string) error {
if root == nil {
continue
}

if interp.astDot {
root.astDot(dotX(), name)
dotCmd := interp.dotCmd
if dotCmd == "" {
dotCmd = defaultDotCmd(name, "yaegi-ast-")
}
root.astDot(dotWriter(dotCmd), name)
}
if pkgName == "" {
pkgName = pname
Expand Down

0 comments on commit 5d56bac

Please sign in to comment.