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  }