-
Notifications
You must be signed in to change notification settings - Fork 0
/
trie.go
280 lines (241 loc) · 5.57 KB
/
trie.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package gouldian
import (
"fmt"
"strings"
)
/*
Node of trie
*/
type Node struct {
Path string // substring from the route "owned" by the node
Heir []*Node // heir nodes
Func Endpoint // end point associated with node
/*
TODO
- Wild *Node // special node that captures any path
- Type int // Node type
*/
}
// NewRoutes creates new routing table
func NewRoutes(seq ...Routable) *Node {
root := &Node{
Heir: []*Node{
{
Path: "/",
Heir: make([]*Node, 0),
},
},
}
for _, route := range seq {
root.appendEndpoint(route())
}
return root
}
/*
lookup is hot-path discovery of node at the path
*/
func (root *Node) lookup(path string, values *[]string) (at int, node *Node) {
node = root
lookup:
for {
// leaf node, no futher lookup is possible
// return current `node`` and position `at` path
if len(node.Heir) == 0 {
return
}
for _, heir := range node.Heir {
if len(path[at:]) < len(heir.Path) {
// No match, path cannot match node
continue
}
if path[at] != heir.Path[0] {
// No match, path cannot match node
// this is micro-optimization to reduce overhead of memequal
continue
}
// the node consumers entire path
if len(heir.Path) == 2 && heir.Path[1] == '*' {
*values = append(*values, path[at+1:])
at = len(path)
node = heir
return
}
if len(heir.Path) == 2 && (heir.Path[1] == ':' || heir.Path[1] == '_' || heir.Path[1] == '*') {
// the node is a wild-card that matches any path segment
// let's skip the path until next segment and re-call the value
p := 1
max := len(path[at:])
for p < max && path[at+p] != '/' {
p++
}
if heir.Path[1] == ':' {
*values = append(*values, path[at+1:at+p])
}
at = at + p
node = heir
continue lookup
}
if path[at:at+len(heir.Path)] == heir.Path {
// node matches the path, continue lookup
at = at + len(heir.Path)
node = heir
continue lookup
}
}
return
}
}
/*
appendEndpoint to trie under the path.
Input path is a collection of segments, each segment is either path literal or
wildcard symbol `:` reserved for lenses
*/
func (root *Node) appendEndpoint(path []string, endpoint Endpoint) {
if len(path) == 0 {
_, n := root.appendTo("/")
if n.Func == nil {
n.Func = endpoint
} else {
n.Func = n.Func.Or(endpoint)
}
return
}
at := 0
node := root
for i, segment := range path {
// `/` required to speed up lookup on the hot-path
segment = "/" + segment
at, node = node.appendTo(segment)
// split the node and add endpoint
if len(segment[at:]) != 0 {
split := &Node{
Path: segment[at:],
Heir: make([]*Node, 0),
}
node.Heir = append(node.Heir, split)
node = split
}
// the last segment needs to be enhanced with endpoint
if i == len(path)-1 {
if node.Func == nil {
node.Func = endpoint
} else {
node.Func = node.Func.Or(endpoint)
}
}
}
}
/*
appendTo finds the node in trie where to add path (or segment).
It returns the candidate node and length of "consumed" path
*/
func (root *Node) appendTo(path string) (at int, node *Node) {
node = root
lookup:
for {
if len(node.Heir) == 0 {
// leaf node, no futher lookup is possible
// return current `node`` and position `at` path
return
}
for _, heir := range node.Heir {
prefix := longestCommonPrefix(path[at:], heir.Path)
at = at + prefix
switch {
case prefix == 0:
// No common prefix, jump to next heir
continue
case prefix == len(heir.Path):
// Common prefix is the node itself, continue lookup into heirs
node = heir
continue lookup
default:
// Common prefix is shorter than node itself, split is required
if prefixNode := node.heirByPath(heir.Path[:prefix]); prefixNode != nil {
// prefix already exists, current node needs to be moved
// under existing one
node.Path = node.Path[prefix:]
prefixNode.Heir = append(prefixNode.Heir, node)
node = prefixNode
return
}
// prefix does not exist, current node needs to be split
// the list of heirs needs to be patched
for j := 0; j < len(node.Heir); j++ {
if node.Heir[j].Path == heir.Path {
n := heir
node.Heir[j] = &Node{
Path: heir.Path[:prefix],
Heir: []*Node{n},
}
n.Path = heir.Path[prefix:]
node = node.Heir[j]
return
}
}
}
}
// No heir is found return current node
return
}
}
func (root *Node) heirByPath(path string) *Node {
for i := 0; i < len(root.Heir); i++ {
if root.Heir[i].Path == path {
return root.Heir[i]
}
}
return nil
}
/*
Walk through trie, use for debug purposes only
*/
func (root *Node) Walk(f func(int, *Node)) {
walk(root, 0, f)
}
func walk(node *Node, level int, f func(int, *Node)) {
f(level, node)
for _, n := range node.Heir {
walk(n, level+1, f)
}
}
// Println outputs trie to console
func (root *Node) Println() {
root.Walk(
func(i int, n *Node) {
fmt.Println(strings.Repeat(" ", i), n.Path)
},
)
}
// Endpoint converts trie to Endpoint
func (root *Node) Endpoint() Endpoint {
return func(ctx *Context) (err error) {
if ctx.Request == nil {
return ErrNoMatch
}
path := ctx.Request.URL.Path
ctx.free()
ctx.values = ctx.values[:0]
i, node := root.lookup(path, &ctx.values)
if len(path) == i && node.Func != nil {
return node.Func(ctx)
}
return ErrNoMatch
}
}
//
// Utils
//
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func longestCommonPrefix(a, b string) (prefix int) {
max := min(len(a), len(b))
for prefix < max && a[prefix] == b[prefix] {
prefix++
}
return
}