golang.org/x/arch@v0.17.0/internal/unify/dot.go (about) 1 // Copyright 2025 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package unify 6 7 import ( 8 "bytes" 9 "fmt" 10 "html" 11 "io" 12 "os" 13 "os/exec" 14 "strings" 15 ) 16 17 const maxNodes = 30 18 19 type dotEncoder struct { 20 w *bytes.Buffer 21 22 idGen int // Node name generation 23 valLimit int // Limit the number of Values in a subgraph 24 25 idp identPrinter 26 } 27 28 func newDotEncoder() *dotEncoder { 29 return &dotEncoder{ 30 w: new(bytes.Buffer), 31 } 32 } 33 34 func (enc *dotEncoder) clear() { 35 enc.w.Reset() 36 enc.idGen = 0 37 } 38 39 func (enc *dotEncoder) writeTo(w io.Writer) { 40 fmt.Fprintln(w, "digraph {") 41 // Use the "new" ranking algorithm, which lets us put nodes from different 42 // clusters in the same rank. 43 fmt.Fprintln(w, "newrank=true;") 44 fmt.Fprintln(w, "node [shape=box, ordering=out];") 45 46 w.Write(enc.w.Bytes()) 47 fmt.Fprintln(w, "}") 48 } 49 50 func (enc *dotEncoder) writeSvg(w io.Writer) error { 51 cmd := exec.Command("dot", "-Tsvg") 52 in, err := cmd.StdinPipe() 53 if err != nil { 54 return err 55 } 56 var out bytes.Buffer 57 cmd.Stdout = &out 58 cmd.Stderr = os.Stderr 59 if err := cmd.Start(); err != nil { 60 return err 61 } 62 enc.writeTo(in) 63 in.Close() 64 if err := cmd.Wait(); err != nil { 65 return err 66 } 67 // Trim SVG header so the result can be embedded 68 // 69 // TODO: In Graphviz 10.0.1, we could use -Tsvg_inline. 70 svg := out.Bytes() 71 if i := bytes.Index(svg, []byte("<svg ")); i >= 0 { 72 svg = svg[i:] 73 } 74 _, err = w.Write(svg) 75 return err 76 } 77 78 func (enc *dotEncoder) newID(f string) string { 79 id := fmt.Sprintf(f, enc.idGen) 80 enc.idGen++ 81 return id 82 } 83 84 func (enc *dotEncoder) node(label, sublabel string) string { 85 id := enc.newID("n%d") 86 l := html.EscapeString(label) 87 if sublabel != "" { 88 l += fmt.Sprintf("<BR ALIGN=\"CENTER\"/><FONT POINT-SIZE=\"10\">%s</FONT>", html.EscapeString(sublabel)) 89 } 90 fmt.Fprintf(enc.w, "%s [label=<%s>];\n", id, l) 91 return id 92 } 93 94 func (enc *dotEncoder) edge(from, to string, label string, args ...any) { 95 l := fmt.Sprintf(label, args...) 96 fmt.Fprintf(enc.w, "%s -> %s [label=%q];\n", from, to, l) 97 } 98 99 func (enc *dotEncoder) subgraph(v *Value) (vID, cID string) { 100 enc.valLimit = maxNodes 101 cID = enc.newID("cluster_%d") 102 fmt.Fprintf(enc.w, "subgraph %s {\n", cID) 103 fmt.Fprintf(enc.w, "style=invis;") 104 vID = enc.value(v) 105 fmt.Fprintf(enc.w, "}\n") 106 return 107 } 108 109 func (enc *dotEncoder) value(v *Value) string { 110 if enc.valLimit <= 0 { 111 id := enc.newID("n%d") 112 fmt.Fprintf(enc.w, "%s [label=\"...\", shape=triangle];\n", id) 113 return id 114 } 115 enc.valLimit-- 116 117 switch vd := v.Domain.(type) { 118 default: 119 panic(fmt.Sprintf("unknown domain type %T", vd)) 120 121 case nil: 122 return enc.node("_|_", "") 123 124 case Top: 125 return enc.node("_", "") 126 127 // TODO: Like in YAML, figure out if this is just a sum. In dot, we 128 // could say any unentangled variable is a sum, and if it has more than 129 // one reference just share the node. 130 131 // case Sum: 132 // node := enc.node("Sum", "") 133 // for i, elt := range vd.vs { 134 // enc.edge(node, enc.value(elt), "%d", i) 135 // if enc.valLimit <= 0 { 136 // break 137 // } 138 // } 139 // return node 140 141 case Def: 142 node := enc.node("Def", "") 143 for k, v := range vd.All() { 144 enc.edge(node, enc.value(v), "%s", k) 145 if enc.valLimit <= 0 { 146 break 147 } 148 } 149 return node 150 151 case Tuple: 152 if vd.repeat == nil { 153 label := "Tuple" 154 node := enc.node(label, "") 155 for i, elt := range vd.vs { 156 enc.edge(node, enc.value(elt), "%d", i) 157 if enc.valLimit <= 0 { 158 break 159 } 160 } 161 return node 162 } else { 163 // TODO 164 return enc.node("TODO: Repeat", "") 165 } 166 167 case String: 168 switch vd.kind { 169 case stringExact: 170 return enc.node(fmt.Sprintf("%q", vd.exact), "") 171 case stringRegex: 172 var parts []string 173 for _, re := range vd.re { 174 parts = append(parts, fmt.Sprintf("%q", re)) 175 } 176 return enc.node(strings.Join(parts, "&"), "") 177 } 178 panic("bad String kind") 179 180 case Var: 181 return enc.node(fmt.Sprintf("Var %s", enc.idp.unique(vd.id)), "") 182 } 183 }