Skip to content

Commit

Permalink
backtrack by calculating next node instead of poping from stack
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Feb 16, 2021
1 parent 3e817bc commit 8b07552
Showing 1 changed file with 29 additions and 49 deletions.
78 changes: 29 additions & 49 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,52 +328,43 @@ func (n *node) checkMethodNotAllowed() HandlerFunc {
// - Reset it `Context#Reset()`
// - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, c Context) {
const backTrackingDepth = 10

ctx := c.(*context)
ctx.path = path
cn := r.tree // Current node as root

var (
search = path
n int // Param counter
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice

// Backtracking Information
state [backTrackingDepth]struct {
nk kind
nn *node
ns string
np int
}
stateIndex int = -1
search = path
searchIndex = 0
n int // Param counter
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
)

pushNext := func(nodeKind kind) {
stateIndex++
if stateIndex >= backTrackingDepth {
panic("Max backtracking depth reached. TODO: this must be detected during registering the paths")
}
backtrackNode := func(fromKind kind) (nodeKind kind, valid bool) {
previous := cn
cn = previous.parent
valid = cn != nil

state[stateIndex].nk = nodeKind
state[stateIndex].nn = cn
state[stateIndex].ns = search
state[stateIndex].np = n
}
// next node type by priority
switch previous.kind {
case akind:
nodeKind = skind
default:
nodeKind = previous.kind + 1
}

popNext := func() (nodeKind kind, valid bool) {
if stateIndex < 0 {
if fromKind == skind {
// when backtracking is done from static kind block we did not change search so nothing to restore
return
}

last := state[stateIndex]
stateIndex--

nodeKind = last.nk
cn = last.nn
search = last.ns
n = last.np
valid = cn != nil
if previous.kind == skind {
searchIndex -= len(previous.prefix)
search = path[searchIndex:]
} else {
n--
searchIndex -= len(pvalues[n])
search = path[searchIndex:]
}
return
}

Expand All @@ -397,7 +388,7 @@ func (r *Router) Find(method, path string, c Context) {

if l != pl {
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
nk, ok := popNext()
nk, ok := backtrackNode(skind)
if !ok {
return // No other possibilities on the decision path
} else if nk == pkind {
Expand All @@ -412,6 +403,7 @@ func (r *Router) Find(method, path string, c Context) {

// The full prefix has matched, remove the prefix from the remaining search
search = search[l:]
searchIndex = searchIndex + l

// Finish routing if no remaining search and we are on an leaf node
if search == "" && cn.ppath != "" {
Expand All @@ -421,12 +413,6 @@ func (r *Router) Find(method, path string, c Context) {
// Static node
if search != "" {
if child := cn.findStaticChild(search[0]); child != nil {
if cn.paramChildren != nil || cn.anyChildren != nil {
// Push a new entry into the decision path, if we don't find anything downtree
// try the current node again searching for a param or any node
// Optimization: The node is only pushed for backtracking if there's an praramChildren or an anyChildren
pushNext(pkind)
}
cn = child
continue
}
Expand All @@ -435,20 +421,14 @@ func (r *Router) Find(method, path string, c Context) {
Param:
// Param node
if child := cn.paramChildren; search != "" && child != nil {
if cn.anyChildren != nil {
// Push a new entry into the decision path, if we have nothing found downtree try the current node again
// searching for an any node.
// Optimization: The node is only pushed for backtracking if there's an anyChildren
pushNext(akind)
}

cn = child
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
pvalues[n] = search[:i]
n++
search = search[i:]
searchIndex = searchIndex + i
continue
}

Expand All @@ -462,7 +442,7 @@ func (r *Router) Find(method, path string, c Context) {
}

// Let's backtrack to the first possible alternative node of the decision path
nk, ok := popNext()
nk, ok := backtrackNode(akind)
if !ok {
return // No other possibilities on the decision path
} else if nk == pkind {
Expand Down

0 comments on commit 8b07552

Please sign in to comment.