github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/ast/inspector/inspector.go (about) 1 // Copyright 2018 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 inspector provides helper functions for traversal over the 6 // syntax trees of a package, including node filtering by type, and 7 // materialization of the traversal stack. 8 // 9 // During construction, the inspector does a complete traversal and 10 // builds a list of push/pop events and their node type. Subsequent 11 // method calls that request a traversal scan this list, rather than walk 12 // the AST, and perform type filtering using efficient bit sets. 13 // 14 // Experiments suggest the inspector's traversals are about 2.5x faster 15 // than ast.Inspect, but it may take around 5 traversals for this 16 // benefit to amortize the inspector's construction cost. 17 // If efficiency is the primary concern, do not use Inspector for 18 // one-off traversals. 19 package inspector 20 21 // There are four orthogonal features in a traversal: 22 // 1 type filtering 23 // 2 pruning 24 // 3 postorder calls to f 25 // 4 stack 26 // Rather than offer all of them in the API, 27 // only a few combinations are exposed: 28 // - Preorder is the fastest and has fewest features, 29 // but is the most commonly needed traversal. 30 // - Nodes and WithStack both provide pruning and postorder calls, 31 // even though few clients need it, because supporting two versions 32 // is not justified. 33 // More combinations could be supported by expressing them as 34 // wrappers around a more generic traversal, but this was measured 35 // and found to degrade performance significantly (30%). 36 37 import ( 38 "go/ast" 39 ) 40 41 // An Inspector provides methods for inspecting 42 // (traversing) the syntax trees of a package. 43 type Inspector struct { 44 events []event 45 } 46 47 // New returns an Inspector for the specified syntax trees. 48 func New(files []*ast.File) *Inspector { 49 return &Inspector{traverse(files)} 50 } 51 52 // An event represents a push or a pop 53 // of an ast.Node during a traversal. 54 type event struct { 55 node ast.Node 56 typ uint64 // typeOf(node) 57 index int // 1 + index of corresponding pop event, or 0 if this is a pop 58 } 59 60 // Preorder visits all the nodes of the files supplied to New in 61 // depth-first order. It calls f(n) for each node n before it visits 62 // n's children. 63 // 64 // The types argument, if non-empty, enables type-based filtering of 65 // events. The function f if is called only for nodes whose type 66 // matches an element of the types slice. 67 func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { 68 // Because it avoids postorder calls to f, and the pruning 69 // check, Preorder is almost twice as fast as Nodes. The two 70 // features seem to contribute similar slowdowns (~1.4x each). 71 72 mask := maskOf(types) 73 for i := 0; i < len(in.events); { 74 ev := in.events[i] 75 if ev.typ&mask != 0 { 76 if ev.index > 0 { 77 f(ev.node) 78 } 79 } 80 i++ 81 } 82 } 83 84 // Nodes visits the nodes of the files supplied to New in depth-first 85 // order. It calls f(n, true) for each node n before it visits n's 86 // children. If f returns true, Nodes invokes f recursively for each 87 // of the non-nil children of the node, followed by a call of 88 // f(n, false). 89 // 90 // The types argument, if non-empty, enables type-based filtering of 91 // events. The function f if is called only for nodes whose type 92 // matches an element of the types slice. 93 func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) { 94 mask := maskOf(types) 95 for i := 0; i < len(in.events); { 96 ev := in.events[i] 97 if ev.typ&mask != 0 { 98 if ev.index > 0 { 99 // push 100 if !f(ev.node, true) { 101 i = ev.index // jump to corresponding pop + 1 102 continue 103 } 104 } else { 105 // pop 106 f(ev.node, false) 107 } 108 } 109 i++ 110 } 111 } 112 113 // WithStack visits nodes in a similar manner to Nodes, but it 114 // supplies each call to f an additional argument, the current 115 // traversal stack. The stack's first element is the outermost node, 116 // an *ast.File; its last is the innermost, n. 117 func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) { 118 mask := maskOf(types) 119 var stack []ast.Node 120 for i := 0; i < len(in.events); { 121 ev := in.events[i] 122 if ev.index > 0 { 123 // push 124 stack = append(stack, ev.node) 125 if ev.typ&mask != 0 { 126 if !f(ev.node, true, stack) { 127 i = ev.index 128 stack = stack[:len(stack)-1] 129 continue 130 } 131 } 132 } else { 133 // pop 134 if ev.typ&mask != 0 { 135 f(ev.node, false, stack) 136 } 137 stack = stack[:len(stack)-1] 138 } 139 i++ 140 } 141 } 142 143 // traverse builds the table of events representing a traversal. 144 func traverse(files []*ast.File) []event { 145 // Preallocate approximate number of events 146 // based on source file extent. 147 // This makes traverse faster by 4x (!). 148 var extent int 149 for _, f := range files { 150 extent += int(f.End() - f.Pos()) 151 } 152 // This estimate is based on the net/http package. 153 capacity := extent * 33 / 100 154 if capacity > 1e6 { 155 capacity = 1e6 // impose some reasonable maximum 156 } 157 events := make([]event, 0, capacity) 158 159 var stack []event 160 for _, f := range files { 161 ast.Inspect(f, func(n ast.Node) bool { 162 if n != nil { 163 // push 164 ev := event{ 165 node: n, 166 typ: typeOf(n), 167 index: len(events), // push event temporarily holds own index 168 } 169 stack = append(stack, ev) 170 events = append(events, ev) 171 } else { 172 // pop 173 ev := stack[len(stack)-1] 174 stack = stack[:len(stack)-1] 175 176 events[ev.index].index = len(events) + 1 // make push refer to pop 177 178 ev.index = 0 // turn ev into a pop event 179 events = append(events, ev) 180 } 181 return true 182 }) 183 } 184 185 return events 186 }