-
Notifications
You must be signed in to change notification settings - Fork 12
/
match.go
145 lines (124 loc) · 3.67 KB
/
match.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package ast
import (
"fmt"
"reflect"
)
// MatchOpts provides options while walking the CST.
type MatchOpts struct {
// Filter is called to see if the node should be walked. If nil, then all
// nodes are walked.
Filter func(Node) bool
// AllowDuplicates is enabled to allow duplicate CST structures between
// arguments in the functions provided.
//
// For example, if a function is defined as `func(*FuncDecl, *CallStmt)`
// sequences like `[*FuncDecl, ..., *CallStmt, ..., *CallStmt]` are not
// matched by default. Allowing duplicates will match instead.
AllowDuplicates bool
}
type matcher struct {
opts MatchOpts
vs []reflect.Value
expects [][]reflect.Type
actuals [][]reflect.Value
indices []int
}
// Match walks a CST and invokes given functions if their arguments match a
// non-contiguous sequence of current path walked. This is useful when you want
// to walk to a specific type of Node, while having access to specific parents
// of the Node.
//
// The function arguments must all implement the Node interface, and may be
// a non-contiguous sequence. That is, you don't have to specify every CST
// structure.
//
// The sequence is matched right to left, from the deepest node first. The
// final argument will always be the current node being visited.
//
// When multiple functions are matched, they are invoked in the order given
// to Match. That way, you can write functions that positively match, and then
// provide a more general function as a catch all without walking the CST a
// second time.
//
// For example, you can invoke Match to find CallStmts inside FuncLits:
// ```go
// Match(root, MatchOpts{},
// func(lit *FuncLit, call *CallStmt) {
// fmt.Println(lit.Pos, call.Pos)
// },
// )
// ```
func Match(root Node, opts MatchOpts, funs ...interface{}) {
m := &matcher{
opts: opts,
vs: make([]reflect.Value, len(funs)),
expects: make([][]reflect.Type, len(funs)),
actuals: make([][]reflect.Value, len(funs)),
indices: make([]int, len(funs)),
}
for i, fun := range funs {
m.vs[i] = reflect.ValueOf(fun)
}
node := reflect.TypeOf((*Node)(nil)).Elem()
for i, v := range m.vs {
t := v.Type()
for j := 0; j < t.NumIn(); j++ {
arg := t.In(j)
if !arg.Implements(node) {
panic(fmt.Sprintf("%s has bad signature: %s does not implement Node", t, arg))
}
m.expects[i] = append(m.expects[i], arg)
}
}
for i, expect := range m.expects {
m.actuals[i] = make([]reflect.Value, len(expect))
}
Walk(root, m)
}
func (m *matcher) Visit(in Introspector, n Node) Visitor {
if n == nil {
return nil
}
if m.opts.Filter != nil {
if !m.opts.Filter(n) {
return nil
}
}
// Clear out indices from a previous visit.
for i := 0; i < len(m.expects); i++ {
m.indices[i] = len(m.expects[i]) - 1
}
for i := len(in.Path()) - 1; i >= 0; i-- {
p := in.Path()[i]
v := reflect.ValueOf(p)
for j, expect := range m.expects {
k := m.indices[j]
// Either the function has been matched or will never match.
if k < 0 {
continue
}
if v.Type() != expect[k] {
if i == len(in.Path())-1 {
// The final argument must always match the deepest node.
m.indices[j] = -2
} else if !m.opts.AllowDuplicates && v.Type() == expect[k+1] {
// Unless duplicates are allowed, the current node cannot be the same
// type as the previous matched node.
m.indices[j] = -2
}
continue
}
m.actuals[j][k] = v
m.indices[j] -= 1
}
}
// Invoke matched functions in the order they were given.
for i := 0; i < len(m.vs); i++ {
// Functions that will never match have an index of -2.
// Functions that matched have an index of -1.
if m.indices[i] == -1 {
m.vs[i].Call(m.actuals[i])
}
}
return m
}