github.com/gmemcc/yaegi@v0.12.1-0.20221128122509-aa99124c5d16/interp/dot.go (about) 1 package interp 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 ) 11 12 // astDot displays an AST in graphviz dot(1) format using dotty(1) co-process. 13 func (n *node) astDot(out io.Writer, name string) { 14 fmt.Fprintf(out, "digraph ast {\n") 15 fmt.Fprintf(out, "labelloc=\"t\"\n") 16 fmt.Fprintf(out, "label=\"%s\"\n", name) 17 n.Walk(func(n *node) bool { 18 var label string 19 switch n.kind { 20 case basicLit, identExpr: 21 label = strings.ReplaceAll(n.ident, "\"", "\\\"") 22 default: 23 if n.action != aNop { 24 label = n.action.String() 25 } else { 26 label = n.kind.String() 27 } 28 } 29 fmt.Fprintf(out, "%d [label=\"%d: %s\"]\n", n.index, n.index, label) 30 if n.anc != nil { 31 fmt.Fprintf(out, "%d -> %d\n", n.anc.index, n.index) 32 } 33 return true 34 }, nil) 35 fmt.Fprintf(out, "}\n") 36 } 37 38 // cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process. 39 func (n *node) cfgDot(out io.Writer) { 40 fmt.Fprintf(out, "digraph cfg {\n") 41 n.Walk(nil, func(n *node) { 42 if n.kind == basicLit || n.tnext == nil { 43 return 44 } 45 var label string 46 if n.action == aNop { 47 label = "nop: end_" + n.kind.String() 48 } else { 49 label = n.action.String() 50 } 51 fmt.Fprintf(out, "%d [label=\"%d: %v %d\"]\n", n.index, n.index, label, n.findex) 52 if n.fnext != nil { 53 fmt.Fprintf(out, "%d -> %d [color=green]\n", n.index, n.tnext.index) 54 fmt.Fprintf(out, "%d -> %d [color=red]\n", n.index, n.fnext.index) 55 } else if n.tnext != nil { 56 fmt.Fprintf(out, "%d -> %d\n", n.index, n.tnext.index) 57 } 58 }) 59 fmt.Fprintf(out, "}\n") 60 } 61 62 type nopCloser struct { 63 io.Writer 64 } 65 66 func (nopCloser) Close() error { return nil } 67 68 // dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format. 69 func dotWriter(dotCmd string) io.WriteCloser { 70 if dotCmd == "" { 71 return nopCloser{io.Discard} 72 } 73 fields := strings.Fields(dotCmd) 74 cmd := exec.Command(fields[0], fields[1:]...) 75 dotin, err := cmd.StdinPipe() 76 if err != nil { 77 log.Fatal(err) 78 } 79 if err = cmd.Start(); err != nil { 80 log.Fatal(err) 81 } 82 return dotin 83 } 84 85 func defaultDotCmd(filePath, prefix string) string { 86 dir, fileName := filepath.Split(filePath) 87 ext := filepath.Ext(fileName) 88 if ext == "" { 89 fileName += ".dot" 90 } else { 91 fileName = strings.Replace(fileName, ext, ".dot", 1) 92 } 93 return "dot -Tdot -o" + dir + prefix + fileName 94 }