...

Source file src/cmd/vendor/golang.org/x/tools/go/ast/inspector/inspector.go

     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) (prune 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) (prune 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		events := make([]event, 0, extent*33/100)
   154	
   155		var stack []event
   156		for _, f := range files {
   157			ast.Inspect(f, func(n ast.Node) bool {
   158				if n != nil {
   159					// push
   160					ev := event{
   161						node:  n,
   162						typ:   typeOf(n),
   163						index: len(events), // push event temporarily holds own index
   164					}
   165					stack = append(stack, ev)
   166					events = append(events, ev)
   167				} else {
   168					// pop
   169					ev := stack[len(stack)-1]
   170					stack = stack[:len(stack)-1]
   171	
   172					events[ev.index].index = len(events) + 1 // make push refer to pop
   173	
   174					ev.index = 0 // turn ev into a pop event
   175					events = append(events, ev)
   176				}
   177				return true
   178			})
   179		}
   180	
   181		return events
   182	}
   183	

View as plain text