github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/whyreflect/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "os" 8 "strconv" 9 "strings" 10 11 "golang.org/x/exp/maps" 12 "golang.org/x/exp/slices" 13 ) 14 15 func main() { 16 deps := Parse(os.Stdin) 17 18 all := New(deps...) 19 reflect := Filter(all, func(b *Node) bool { 20 return b.Flags&ReflectMethod != 0 21 }) 22 reach := Reach(all, reflect) 23 24 // sort output 25 nodes := maps.Keys(reach) 26 slices.SortFunc(nodes, func(a, b *Node) bool { return a.Sym.Name < b.Sym.Name }) 27 for _, n := range nodes { 28 slices.SortFunc(n.Needs, func(a, b *Node) bool { return a.Sym.Name < b.Sym.Name }) 29 } 30 31 fmt.Print("digraph G {\n") 32 defer fmt.Print("}\n") 33 34 fmt.Print(" node [fontsize=10 shape=rectangle target=\"_graphviz\"];\n") 35 fmt.Print(" edge [tailport=e];\n") 36 fmt.Print(" compound=true;\n") 37 fmt.Print(" rankdir=LR;\n") 38 fmt.Print(" newrank=true;\n") 39 fmt.Print(" ranksep=\"1.5\";\n") 40 fmt.Print(" quantum=\"0.5\";\n") 41 42 fmt.Print("\n") 43 for n := range reach { 44 if n.Flags&ReflectMethod != 0 { 45 fmt.Printf(" %v [label=%q, style=filled, fillcolor=lightgray]\n", nodeID(n), n.Name) 46 } else { 47 fmt.Printf(" %v [label=%q]\n", nodeID(n), n.Name) 48 } 49 } 50 for n := range reach { 51 for _, x := range n.Needs { 52 if _, ok := reach[x]; !ok { 53 continue 54 } 55 fmt.Printf(" %v -> %v\n", nodeID(n), nodeID(x)) 56 } 57 } 58 } 59 60 func nodeID(node *Node) string { 61 return strconv.Quote(node.Name) 62 } 63 64 type Dep struct { 65 From Sym 66 To Sym 67 } 68 69 type Sym struct { 70 Name string 71 Flags Flag 72 } 73 74 type Flag byte 75 76 const ( 77 UsedInIface = 1 78 ReflectMethod = 2 79 ) 80 81 func Parse(r io.Reader) (deps []Dep) { 82 scanner := bufio.NewScanner(r) 83 for scanner.Scan() { 84 line := scanner.Text() 85 if line == "" { 86 continue 87 } 88 89 from, to, ok := strings.Cut(line, " -> ") 90 if !ok { 91 continue 92 } 93 94 deps = append(deps, Dep{ 95 From: ParseSym(from), 96 To: ParseSym(to), 97 }) 98 } 99 return deps 100 } 101 102 func ParseSym(t string) (sym Sym) { 103 old := "" 104 for old != t { 105 old = t 106 if strings.HasSuffix(t, " <UsedInIface>") { 107 t = strings.TrimSuffix(t, " <UsedInIface>") 108 sym.Flags |= UsedInIface 109 } 110 if strings.HasSuffix(t, " <ReflectMethod>") { 111 t = strings.TrimSuffix(t, " <ReflectMethod>") 112 sym.Flags |= ReflectMethod 113 } 114 } 115 sym.Name = t 116 return sym 117 } 118 119 var token struct{} 120 121 type Node struct { 122 Sym 123 Needs []*Node 124 } 125 126 type Graph map[*Node]struct{} 127 128 func New(deps ...Dep) Graph { 129 nodes := make(map[Sym]*Node) 130 set := make(Graph) 131 132 ensure := func(sym Sym) *Node { 133 node, ok := nodes[sym] 134 if !ok { 135 node = &Node{Sym: sym} 136 nodes[sym] = node 137 set[node] = token 138 } 139 return node 140 } 141 142 for _, dep := range deps { 143 src := ensure(dep.From) 144 dst := ensure(dep.To) 145 146 src.Needs = append(src.Needs, dst) 147 } 148 149 return set 150 } 151 152 func Filter(a Graph, fn func(b *Node) bool) Graph { 153 result := New() 154 for n := range a { 155 if fn(n) { 156 result[n] = token 157 } 158 } 159 return result 160 } 161 162 // Reach returns packages in a that terminate in b. 163 func Reach(a, b Graph) Graph { 164 result := maps.Clone(b) 165 candidates := maps.Clone(a) 166 167 for x := range result { 168 delete(candidates, x) 169 } 170 171 for modified := true; modified; { 172 modified = false 173 for candidate := range candidates { 174 for _, dep := range candidate.Needs { 175 if _, ok := result[dep]; ok { 176 result[candidate] = token 177 delete(candidates, candidate) 178 modified = true 179 } 180 } 181 } 182 } 183 184 return result 185 }