You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
4.1 KiB

'use strict'
const HandlerStorage = require('./handler_storage')
const NODE_TYPES = {
STATIC: 0,
PARAMETRIC: 1,
WILDCARD: 2
}
class Node {
constructor () {
this.handlerStorage = new HandlerStorage()
}
}
class ParentNode extends Node {
constructor () {
super()
this.staticChildren = {}
}
findStaticMatchingChild (path, pathIndex) {
const staticChild = this.staticChildren[path.charAt(pathIndex)]
if (staticChild === undefined || !staticChild.matchPrefix(path, pathIndex)) {
return null
}
return staticChild
}
createStaticChild (path) {
if (path.length === 0) {
return this
}
let staticChild = this.staticChildren[path.charAt(0)]
if (staticChild) {
let i = 1
for (; i < staticChild.prefix.length; i++) {
if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
staticChild = staticChild.split(this, i)
break
}
}
return staticChild.createStaticChild(path.slice(i))
}
const label = path.charAt(0)
this.staticChildren[label] = new StaticNode(path)
return this.staticChildren[label]
}
}
class StaticNode extends ParentNode {
constructor (prefix) {
super()
this.prefix = prefix
this.wildcardChild = null
this.parametricChildren = []
this.kind = NODE_TYPES.STATIC
this._compilePrefixMatch()
}
createParametricChild (regex) {
const regexpSource = regex && regex.source
let parametricChild = this.parametricChildren.find(child => {
const childRegexSource = child.regex && child.regex.source
return childRegexSource === regexpSource
})
if (parametricChild) {
return parametricChild
}
parametricChild = new ParametricNode(regex)
if (regex) {
this.parametricChildren.unshift(parametricChild)
} else {
this.parametricChildren.push(parametricChild)
}
return parametricChild
}
createWildcardChild () {
if (this.wildcardChild) {
return this.wildcardChild
}
this.wildcardChild = new WildcardNode()
return this.wildcardChild
}
split (parentNode, length) {
const parentPrefix = this.prefix.slice(0, length)
const childPrefix = this.prefix.slice(length)
this.prefix = childPrefix
this._compilePrefixMatch()
const staticNode = new StaticNode(parentPrefix)
staticNode.staticChildren[childPrefix.charAt(0)] = this
parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode
return staticNode
}
getNextNode (path, pathIndex, nodeStack, paramsCount) {
let node = this.findStaticMatchingChild(path, pathIndex)
let parametricBrotherNodeIndex = 0
if (node === null) {
if (this.parametricChildren.length === 0) {
return this.wildcardChild
}
node = this.parametricChildren[0]
parametricBrotherNodeIndex = 1
}
if (this.wildcardChild !== null) {
nodeStack.push({
paramsCount,
brotherPathIndex: pathIndex,
brotherNode: this.wildcardChild
})
}
for (let i = this.parametricChildren.length - 1; i >= parametricBrotherNodeIndex; i--) {
nodeStack.push({
paramsCount,
brotherPathIndex: pathIndex,
brotherNode: this.parametricChildren[i]
})
}
return node
}
_compilePrefixMatch () {
if (this.prefix.length === 1) {
this.matchPrefix = () => true
return
}
const lines = []
for (let i = 1; i < this.prefix.length; i++) {
const charCode = this.prefix.charCodeAt(i)
lines.push(`path.charCodeAt(i + ${i}) === ${charCode}`)
}
this.matchPrefix = new Function('path', 'i', `return ${lines.join(' && ')}`) // eslint-disable-line
}
}
class ParametricNode extends ParentNode {
constructor (regex) {
super()
this.regex = regex || null
this.isRegex = !!regex
this.kind = NODE_TYPES.PARAMETRIC
}
getNextNode (path, pathIndex) {
return this.findStaticMatchingChild(path, pathIndex)
}
}
class WildcardNode extends Node {
constructor () {
super()
this.kind = NODE_TYPES.WILDCARD
}
getNextNode () {
return null
}
}
module.exports = { StaticNode, ParametricNode, WildcardNode, NODE_TYPES }