Join path and prefix
diff --git a/tree.go b/tree.go
index af95bd4..64269e0 100644
--- a/tree.go
+++ b/tree.go
@@ -6,7 +6,6 @@
import (
"strings"
- "unicode"
)
func min(a, b int) int {
@@ -30,6 +29,13 @@
return uint8(n)
}
+func toLower(b byte) byte {
+ if 'A' <= b && b <= 'Z' {
+ b += 'a' - 'A'
+ }
+ return b
+}
+
type nodeType uint8
const (
@@ -40,11 +46,10 @@
)
type node struct {
- path string
+ pfx string // first len(children) byte are indices, other are prefix
wildChild bool
nType nodeType
maxParams uint8
- indices string
children []*node
handle Handle
priority uint32
@@ -68,9 +73,9 @@
// build new index char string
if newPos != pos {
- n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
- n.indices[pos:pos+1] + // the index char we move
- n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
+ n.pfx = n.pfx[:newPos] + // unchanged prefix, might be empty
+ n.pfx[pos:pos+1] + // the index char we move
+ n.pfx[newPos:pos] + n.pfx[pos+1:] // rest without char at 'pos'
}
return newPos
@@ -84,7 +89,8 @@
numParams := countParams(path)
// non-empty tree
- if len(n.path) > 0 || len(n.children) > 0 {
+ if len(n.pfx) > 0 {
+ prefix := n.pfx[len(n.children):]
walk:
for {
// Update maxParams of the current node
@@ -96,17 +102,16 @@
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
i := 0
- max := min(len(path), len(n.path))
- for i < max && path[i] == n.path[i] {
+ max := min(len(path), len(prefix))
+ for i < max && path[i] == prefix[i] {
i++
}
// Split edge
- if i < len(n.path) {
+ if i < len(prefix) {
child := node{
- path: n.path[i:],
+ pfx: n.pfx[:len(n.children)] + prefix[i:],
wildChild: n.wildChild,
- indices: n.indices,
children: n.children,
handle: n.handle,
priority: n.priority - 1,
@@ -121,8 +126,7 @@
n.children = []*node{&child}
// []byte for proper unicode char conversion, see #65
- n.indices = string([]byte{n.path[i]})
- n.path = path[:i]
+ n.pfx = string([]byte{prefix[i]}) + path[:i]
n.handle = nil
n.wildChild = false
}
@@ -134,6 +138,7 @@
if n.wildChild {
n = n.children[0]
n.priority++
+ prefix = n.pfx[len(n.children):]
// Update maxParams of the child node
if numParams > n.maxParams {
@@ -142,15 +147,15 @@
numParams--
// Check if the wildcard matches
- if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
+ if len(path) >= len(prefix) && prefix == path[:len(prefix)] {
// check for longer wildcard, e.g. :name and :names
- if len(n.path) >= len(path) || path[len(n.path)] == '/' {
+ if len(prefix) >= len(path) || path[len(prefix)] == '/' {
continue walk
}
}
panic("path segment '" + path +
- "' conflicts with existing wildcard '" + n.path +
+ "' conflicts with existing wildcard '" + prefix +
"' in path '" + fullPath + "'")
}
@@ -160,14 +165,16 @@
if n.nType == param && c == '/' && len(n.children) == 1 {
n = n.children[0]
n.priority++
+ prefix = n.pfx[len(n.children):]
continue walk
}
// Check if a child with the next path byte exists
- for i := 0; i < len(n.indices); i++ {
- if c == n.indices[i] {
+ for i := 0; i < len(n.children); i++ {
+ if c == n.pfx[i] {
i = n.incrementChildPrio(i)
n = n.children[i]
+ prefix = n.pfx[len(n.children):]
continue walk
}
}
@@ -175,12 +182,12 @@
// Otherwise insert it
if c != ':' && c != '*' {
// []byte for proper unicode char conversion, see #65
- n.indices += string([]byte{c})
+ n.pfx = n.pfx[:len(n.children)] + string([]byte{c}) + n.pfx[len(n.children):]
child := &node{
maxParams: numParams,
}
n.children = append(n.children, child)
- n.incrementChildPrio(len(n.indices) - 1)
+ n.incrementChildPrio(len(n.children) - 1)
n = child
}
n.insertChild(numParams, path, fullPath, handle)
@@ -238,8 +245,10 @@
if c == ':' { // param
// split path at the beginning of the wildcard
if i > 0 {
- n.path = path[offset:i]
+ n.pfx = ":" + path[offset:i]
offset = i
+ } else {
+ n.pfx = ":" + n.pfx
}
child := &node{
@@ -255,7 +264,7 @@
// if the path doesn't end with the wildcard, then there
// will be another non-wildcard subpath starting with '/'
if end < max {
- n.path = path[offset:end]
+ n.pfx = "/" + path[offset:end]
offset = end
child := &node{
@@ -271,7 +280,7 @@
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
}
- if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
+ if len(n.pfx)-len(n.children) > 0 && n.pfx[len(n.pfx)-1] == '/' {
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
}
@@ -281,22 +290,22 @@
panic("no / before catch-all in path '" + fullPath + "'")
}
- n.path = path[offset:i]
+ n.pfx = string(path[i]) + path[offset:i]
// first node: catchAll node with empty path
child := &node{
+ pfx: "/",
wildChild: true,
nType: catchAll,
maxParams: 1,
}
n.children = []*node{child}
- n.indices = string(path[i])
n = child
n.priority++
// second node: node holding the variable
child = &node{
- path: path[i:],
+ pfx: path[i:],
nType: catchAll,
maxParams: 1,
handle: handle,
@@ -309,7 +318,7 @@
}
// insert remaining path part and handle to the leaf
- n.path = path[offset:]
+ n.pfx = n.pfx[:len(n.children)] + path[offset:]
n.handle = handle
}
@@ -321,16 +330,18 @@
func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
walk: // Outer loop for walking the tree
for {
- if len(path) > len(n.path) {
- if path[:len(n.path)] == n.path {
- path = path[len(n.path):]
+ prefix := n.pfx[len(n.children):]
+
+ if len(path) > len(prefix) {
+ if path[:len(prefix)] == prefix {
+ path = path[len(prefix):]
// If this node does not have a wildcard (param or catchAll)
- // child, we can just look up the next child node and continue
+ // child, we can just look up the next child node and continue
// to walk down the tree
if !n.wildChild {
c := path[0]
- for i := 0; i < len(n.indices); i++ {
- if c == n.indices[i] {
+ for i := 0; i < len(n.children); i++ { // TODO: cache max?
+ if c == n.pfx[i] { // TODO: cache indices?
n = n.children[i]
continue walk
}
@@ -361,7 +372,7 @@
}
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
- p[i].Key = n.path[1:]
+ p[i].Key = n.pfx[len(n.children)+1:]
p[i].Value = path[:end]
// we need to go deeper!
@@ -383,7 +394,7 @@
// No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation
n = n.children[0]
- tsr = (n.path == "/" && n.handle != nil)
+ tsr = (n.handle != nil && n.pfx[len(n.children):] == "/")
}
return
@@ -396,7 +407,7 @@
}
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
- p[i].Key = n.path[2:]
+ p[i].Key = n.pfx[2:]
p[i].Value = path
handle = n.handle
@@ -406,7 +417,7 @@
panic("invalid node type")
}
}
- } else if path == n.path {
+ } else if path == prefix {
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
if handle = n.handle; handle != nil {
@@ -420,10 +431,10 @@
// No handle found. Check if a handle for this path + a
// trailing slash exists for trailing slash recommendation
- for i := 0; i < len(n.indices); i++ {
- if n.indices[i] == '/' {
+ for i := 0; i < len(n.children); i++ {
+ if n.pfx[i] == '/' {
n = n.children[i]
- tsr = (len(n.path) == 1 && n.handle != nil) ||
+ tsr = (len(n.pfx)-len(n.children) == 1 && n.handle != nil) ||
(n.nType == catchAll && n.children[0].handle != nil)
return
}
@@ -435,8 +446,8 @@
// Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path
tsr = (path == "/") ||
- (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
- path == n.path[:len(n.path)-1] && n.handle != nil)
+ (len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
+ path == prefix[:len(prefix)-1] && n.handle != nil)
return
}
}
@@ -447,22 +458,23 @@
// was successful.
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
+ prefix := n.pfx[len(n.children):]
// Outer loop for walking the tree
- for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
- path = path[len(n.path):]
- ciPath = append(ciPath, n.path...)
+ for len(path) >= len(prefix) && strings.ToLower(path[:len(prefix)]) == strings.ToLower(prefix) {
+ path = path[len(prefix):]
+ ciPath = append(ciPath, prefix...)
if len(path) > 0 {
// If this node does not have a wildcard (param or catchAll) child,
// we can just look up the next child node and continue to walk down
// the tree
if !n.wildChild {
- r := unicode.ToLower(rune(path[0]))
- for i, index := range n.indices {
- // must use recursive approach since both index and
- // ToLower(index) could exist. We must check both.
- if r == unicode.ToLower(index) {
+ c := toLower(path[0])
+ for i := 0; i < len(n.children); i++ {
+ if c == toLower(n.pfx[i]) {
+ // must use recursive approach since both index and
+ // ToLower(index) could exist. We must check both.
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
if found {
return append(ciPath, out...), true
@@ -493,6 +505,7 @@
if len(n.children) > 0 {
path = path[k:]
n = n.children[0]
+ prefix = n.pfx[len(n.children):]
continue
}
@@ -509,7 +522,7 @@
// No handle found. Check if a handle for this path + a
// trailing slash exists
n = n.children[0]
- if n.path == "/" && n.handle != nil {
+ if n.pfx[len(n.children):] == "/" && n.handle != nil {
return append(ciPath, '/'), true
}
}
@@ -531,10 +544,10 @@
// No handle found.
// Try to fix the path by adding a trailing slash
if fixTrailingSlash {
- for i := 0; i < len(n.indices); i++ {
- if n.indices[i] == '/' {
+ for i := 0; i < len(n.children); i++ {
+ if n.pfx[i] == '/' {
n = n.children[i]
- if (len(n.path) == 1 && n.handle != nil) ||
+ if (len(n.pfx)-len(n.children) == 1 && n.handle != nil) ||
(n.nType == catchAll && n.children[0].handle != nil) {
return append(ciPath, '/'), true
}
@@ -552,10 +565,10 @@
if path == "/" {
return ciPath, true
}
- if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
- strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
+ if len(path)+1 == len(prefix) && prefix[len(path)] == '/' &&
+ strings.ToLower(path) == strings.ToLower(prefix[:len(path)]) &&
n.handle != nil {
- return append(ciPath, n.path...), true
+ return append(ciPath, prefix...), true
}
}
return
diff --git a/tree_test.go b/tree_test.go
index 46d3299..84ecda6 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -13,8 +13,10 @@
)
func printChildren(n *node, prefix string) {
- fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
- for l := len(n.path); l > 0; l-- {
+ indices := n.pfx[:len(n.children)]
+ path := n.pfx[len(n.children):]
+ fmt.Printf(" %02d:%02d %s%s [%d:%s] %v %t %d \r\n", n.priority, n.maxParams, prefix, path, len(n.children), indices, n.handle, n.wildChild, n.nType)
+ for l := len(path); l > 0; l-- {
prefix += " "
}
for _, child := range n.children {
@@ -74,7 +76,7 @@
if n.priority != prio {
t.Errorf(
"priority mismatch for node '%s': is %d, should be %d",
- n.path, n.priority, prio,
+ n.pfx[:len(n.children)], n.priority, prio,
)
}
@@ -96,7 +98,7 @@
if n.maxParams != maxParams {
t.Errorf(
"maxParams mismatch for node '%s': is %d, should be %d",
- n.path, n.maxParams, maxParams,
+ n.pfx[:len(n.children)], n.maxParams, maxParams,
)
}