router v2 proof of concept implemented

This commit is contained in:
Jonatan Nilsson 2024-10-27 05:12:19 +00:00
parent 1ed5b49aee
commit a3cf59fbc8
8 changed files with 844 additions and 3 deletions

View file

@ -7,9 +7,15 @@ export function printTree(children, indent = 0) {
} }
} }
export function printIfNotEightyOne(fn) {
let opt = %GetOptimizationStatus(fn)
if (opt === 81) return
printCurrentStatus()
}
export function printCurrentStatus(fn) { export function printCurrentStatus(fn) {
let opt = %GetOptimizationStatus(fn) let opt = %GetOptimizationStatus(fn)
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')}`) console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt})`)
} }
export function printStatusHelperText() { export function printStatusHelperText() {
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬

View file

@ -6,6 +6,15 @@ export const overrideDummy = function(override) {
export const dummy = function() { callItem() } export const dummy = function() { callItem() }
export const testRoutes = [
// '/api/articles/:id/file/:fileId',
'/',
// '/api/articles',
// '/api/articles/:id/file',
// '/api/articles/:id',
'/::rest',
]
export const allRoutes = [ export const allRoutes = [
'/', '/',
'/api/articles', '/api/articles',
@ -23,6 +32,7 @@ export const allRoutes = [
'/api/pages/:id/articles/public', '/api/pages/:id/articles/public',
'/api/staff', '/api/staff',
'/api/staff/:id', '/api/staff/:id',
'/::rest',
] ]
export const allManyRoutes = [ export const allManyRoutes = [
@ -37,7 +47,7 @@ export const allManyRoutes = [
'/api/categories/:categoryId/properties', '/api/categories/:categoryId/properties',
'/api/categories/:categoryId/values/:props', '/api/categories/:categoryId/values/:props',
'/api/categories/:categoryId', '/api/categories/:categoryId',
'/api/categories/:categoryId/products/:productId', //'/api/categories/:categoryId/products/:productId',
'/api/categories/:categoryId/products/:productId', '/api/categories/:categoryId/products/:productId',
'/api/customers', '/api/customers',
'/api/customers/:id', '/api/customers/:id',
@ -61,7 +71,7 @@ export const allManyRoutes = [
'/api/products/:id', '/api/products/:id',
'/api/products/:id/movement', '/api/products/:id/movement',
'/api/products/:id/sub_products/:productId', '/api/products/:id/sub_products/:productId',
'/api/products/:id/sub_products/:productId', //'/api/products/:id/sub_products/:productId',
'/api/products/code/:code', '/api/products/code/:code',
'/api/products/property/:propertyId', '/api/products/property/:propertyId',
'/api/properties', '/api/properties',
@ -84,4 +94,5 @@ export const allManyRoutes = [
'/api/works/public', '/api/works/public',
'/api/staff', '/api/staff',
'/api/staff/:id', '/api/staff/:id',
'/::rest',
] ]

148
benchmark/ifs.mjs Normal file
View file

@ -0,0 +1,148 @@
import { summary, run, bench } from 'mitata';
function printCurrentStatus(fn) {
let opt = %GetOptimizationStatus(fn)
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
}
function printStatusHelperText() {
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
is function
is never optimized
is always optimized
is maybe deoptimized
is optimized
is optimized by TurboFan
is interpreted
is marked for optimization
is marked for concurrent optimization
is optimizing concurrently
is executing
topmost frame is turbo fanned
lite mode
marked for deoptimization
baseline
topmost frame is interpreted
topmost frame is baseline`)
}
// Warmup (de-optimize `bench()` calls)
bench('noop', () => { });
bench('noop2', () => { });
function ifSingle(a) {
if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
return 100
}
return 10
}
function ifChain(a) {
if (a[0] === 97)
if (a[1] === 97)
if (a[2] === 97)
if (a[3] === 97)
if (a.length === 4) {
return 100
}
return 10
}
function ifSingleOpt(a) {
if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
return 100
}
return 10
}
function ifChainOpt(a) {
if (a.length >= 4)
if (a[0] === 97)
if (a[1] === 97)
if (a[2] === 97)
if (a[3] === 97)
if (a.length === 4) {
return 100
}
return 10
}
function ifSingleOptAlt(a) {
if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) {
return 100
}
return 10
}
function ifChainOptAlt(a) {
if (a.length >= 4)
if (a[0] == 97)
if (a[1] == 97)
if (a[2] == 97)
if (a[3] == 97)
if (a.length == 4) {
return 100
}
return 10
}
let paths = [
[97, 97, 97, 97],
[97, 97, 97, 97, 97],
[97, 97, 96, 97],
[97, 96, 97, 97],
[96, 97, 97, 97],
[],
[98],
[97, 97, 97],
[97, 97],
[97],
[97, 97, 96],
[97, 96],
[96],
]
let paths2 = [
[97, 97, 97, 97],
[97, 97, 97, 97, 97],
[97, 97, 96, 97],
[97, 96, 97, 97],
[96, 97, 97, 97],
[97, 97, 97, 97],
[97, 97, 97, 97, 97],
[97, 97, 96, 97],
[97, 96, 97, 97],
[96, 97, 97, 97],
[97, 97, 97, 97, 97, 97],
[97, 97, 97, 97, 96, 97],
[97, 97, 97, 97, 97, 96],
]
let func1 = [ifSingle, ifChain, ifSingleOpt, ifChainOpt, ifSingleOptAlt, ifChainOptAlt];
for (let fun of func1) {
console.log('-- begin', fun.name)
for (var i = 0; i < 1000000; i++) {
paths.map(fun)
}
printCurrentStatus(fun);
}
printStatusHelperText()
summary(() => {
func1.forEach(function(fun) {
bench(fun.name + ' first', function() {
return paths.map(fun)
})
})
func1.forEach(function(fun) {
bench(fun.name + ' second', function() {
return paths2.map(fun)
})
})
})
run().then(function() {
for (let fun of func1) {
printCurrentStatus(fun);
}
printStatusHelperText()
});

85
benchmark/ifs_compile.mjs Normal file
View file

@ -0,0 +1,85 @@
import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs";
function ifSingle(a) {
if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
return 100
}
return 10
}
function ifChain(a) {
if (a[0] === 97)
if (a[1] === 97)
if (a[2] === 97)
if (a[3] === 97)
if (a.length === 4) {
return 100
}
return 10
}
function ifSingleOptimized(a) {
if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
return 100
}
return 10
}
function ifChainOptimized(a) {
if (a.length >= 4)
if (a[0] === 97)
if (a[1] === 97)
if (a[2] === 97)
if (a[3] === 97)
if (a.length === 4) {
return 100
}
return 10
}
function ifSingleOptimizedAlt(a) {
if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) {
return 100
}
return 10
}
function ifChainOptimizedAlt(a) {
if (a.length >= 4)
if (a[0] == 97)
if (a[1] == 97)
if (a[2] == 97)
if (a[3] == 97)
if (a.length == 4) {
return 100
}
return 10
}
let paths = [
[97, 97, 97, 97],
[97, 97, 97, 97, 97],
[97, 97, 96, 97],
[97, 96, 97, 97],
[96, 97, 97, 97],
[],
[98],
[97, 97, 97],
[97, 97],
[97],
[97, 97, 96],
[97, 96],
[96],
]
let func1 = [ifSingle, ifChain, ifSingleOptimized, ifChainOptimized, ifSingleOptimizedAlt, ifChainOptimizedAlt];
for (let fun of func1) {
console.log('-- begin', fun.name)
for (var i = 0; i < 1000000; i++) {
paths.map(x => fun(x))
}
printCurrentStatus(fun);
}
printStatusHelperText()

106
benchmark/map_query.mjs Normal file
View file

@ -0,0 +1,106 @@
import { summary, run, bench } from 'mitata';
function printCurrentStatus(fn) {
let opt = %GetOptimizationStatus(fn)
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
}
function printStatusHelperText() {
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
is function
is never optimized
is always optimized
is maybe deoptimized
is optimized
is optimized by TurboFan
is interpreted
is marked for optimization
is marked for concurrent optimization
is optimizing concurrently
is executing
topmost frame is turbo fanned
lite mode
marked for deoptimization
baseline
topmost frame is interpreted
topmost frame is baseline`)
}
// Warmup (de-optimize `bench()` calls)
bench('noop', () => { });
bench('noop2', () => { });
function mapGetAndCheck(map, t) {
let x = map.get(t)
if (x) return x
return t
}
function mapCheckBeforeGet(map, t) {
if (map.has(t)) {
return map.get(t)
}
return t
}
let paths = [
'a',
'aa',
'aaa',
'aaaa',
'aaaaa',
'aaaaaa',
'aaaaaaa',
'aaaaaaaa',
]
let tests = [
'a',
'aa',
'aaa',
'aaaa',
'aaaaa',
'aaaaaa',
'aaaaaaa',
'aaaaaaaa',
'b',
'bb',
'bbb',
'bbbb',
'bbbbb',
'bbbbbb',
'bbbbbbb',
'bbbbbbbb',
'c',
'cc',
'ccc',
'cccc',
'ccccc',
'cccccc',
'ccccccc',
'cccccccc',
]
let map = new Map(paths.map(x => [x, { a: x }]))
let func1 = [mapGetAndCheck, mapCheckBeforeGet];
for (let fun of func1) {
console.log('-- begin', fun.name)
for (var i = 0; i < 1000000; i++) {
tests.map(t => fun(map, t))
}
printCurrentStatus(fun);
}
printStatusHelperText()
summary(() => {
func1.forEach(function(fun) {
bench(fun.name, function() {
return paths.map(t => fun(map, t))
})
})
})
run().then(function() {
for (let fun of func1) {
printCurrentStatus(fun);
}
printStatusHelperText()
});

View file

@ -0,0 +1,96 @@
import { summary, run, bench } from 'mitata';
import assert from 'assert'
import { compilePaths } from "../router_v2.mjs"
import * as consts from './const.js'
function printCurrentStatus(fn) {
// console.log(`--- checking optimizations status on ${fn.name} ---`)
let opt = %GetOptimizationStatus(fn)
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
}
function printStatusHelperText() {
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
is function
is never optimized
is always optimized
is maybe deoptimized
is optimized
is optimized by TurboFan
is interpreted
is marked for optimization
is marked for concurrent optimization
is optimizing concurrently
is executing
topmost frame is turbo fanned
lite mode
marked for deoptimization
baseline
topmost frame is interpreted
topmost frame is baseline`)
}
// Warmup (de-optimize `bench()` calls)
bench('noop', () => { });
bench('noop2', () => { });
let paths = [
{ path: '/aa/aa', },
{ path: '/aa/:blabla', },
{ path: '/aa/:blabla/aa', },
{ path: '/aa/:blabla/ab', },
{ path: '/aa/:blabla/bb', },
{ path: '/::rest', },
]
paths = consts.allManyRoutes.map(x => ({ path: x }))
let tests = [
['/', paths[5]],
['/aa', paths[5]],
['/aa/aa', paths[0]],
['/aa/_', paths[1]],
['/aa/_/aa', paths[2]],
['/aa/_/ab', paths[3]],
['/aa/_/bb', paths[4]],
]
tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p]))
let testStrings = tests.map(x => x[0])
let func = compilePaths(paths)
for (let [_, fun] of func) {
console.log(`--- Sanity checking ${fun.name} ---`)
for (let test of tests) {
let check = fun(test[0])
// console.log(test[0], check)
assert.strictEqual(check.path, test[1])
}
}
for (let [_, fun] of func) {
console.log(`--- warming up ${fun.name} ---`)
for (var i = 0; i < 10000; i++) {
testStrings.forEach(fun)
}
}
console.log('--- sleeping ---')
await new Promise(res => setTimeout(res, 1000))
for (let [org] of func) {
printCurrentStatus(org);
}
printStatusHelperText()
summary(() => {
func.forEach(function([_, fun]) {
bench(fun.name.slice(6), function() {
return testStrings.map(fun)
})
})
})
run().then(function() {
for (let [check] of func) {
printCurrentStatus(check);
}
printStatusHelperText()
});

33
benchmark/test.mjs Normal file
View file

@ -0,0 +1,33 @@
import assert from 'assert'
import { compilePaths } from "../router_v2.mjs"
import * as consts from './const.js'
let paths = [
{ path: '/aa/aa', },
{ path: '/aa/:blabla', },
{ path: '/::rest', },
]
// paths = consts.allManyRoutes.map(x => ({ path: x }))
let tests = [
['/', paths[5]],
['/aa', paths[5]],
['/aa/aa', paths[0]],
['/aa/_', paths[1]],
['/aa/_/aa', paths[2]],
['/aa/_/ab', paths[3]],
['/aa/_/bb', paths[4]],
]
tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p]))
let func = compilePaths(paths)
for (let [_, fun] of func) {
console.log(`--- ${fun.name} ---`)
for (let test of tests) {
let check = fun(test[0])
console.log(test[0], check)
assert.strictEqual(check.path, test[1])
}
}

356
router_v2.mjs Normal file
View file

@ -0,0 +1,356 @@
const Debug = true
export function printTree(children, indent = 0) {
if (!children.length || !Debug) return
for (let child of children) {
console.log(''.padStart(indent * 2) + (child.char || '*')
+ ' ' + (child.isParams ? `{ params = ${child.isParams} }` : '')
+ (child.isFullParams ? `{ fullParams = ${child.isFullParams} }` : '')
+ ' ' + (child.path ? `{ handler = ${child.path.path} }` : ''))
printTree(child.children, indent + 1)
}
}
class RouterError extends Error {
constructor(route1, route2, ...params) {
// Pass remaining arguments (including vendor specific ones) to parent constructor
super(...params);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, RouterError);
}
this.name = "RouterError";
this.routeA = route1
this.routeB = route2
}
}
function Child(split, x, i) {
this.path = null
this.isParams = split[x].isParams ? split[x].word : null
this.isFullParams = split[x].isFullParams ? split[x].word : null
this.paramVarName = split[x].paramVarName ?? null
this.char = !this.isParams && !this.isFullParams ? split[x].word[i] || '/' : null
this.count = 0
this.children = []
}
function buildChild(x, i, splitPaths) {
let splitPath = splitPaths[0]
let letter = new Child(splitPath.split, x, i)
let consume = []
if (splitPath.split.length === x + 1
&& (splitPath.split[x].isParams
|| splitPath.split[x].isFullParams
|| splitPath.split[x].word.length === i + 1)) {
letter.path = splitPath.entry
letter.count += 1
} else {
consume = [splitPath]
}
for (let y = 1; y < splitPaths.length; y++) {
let checkPath = splitPaths[y]
if (!checkPath.split[x]
|| checkPath.split[x].isParams !== splitPath.split[x].isParams
|| checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams
|| !checkPath.split[x].isParams
&& !checkPath.split[x].isFullParams
&& (checkPath.split[x].word[i] || '/') !== letter.char) break
consume.push(checkPath)
}
letter.count += consume.length
if (splitPath.split[x].word.length === i || splitPath.split[x].isParams || splitPath.split[x].isFullParams) {
x++
i = -1
}
while (consume.length) {
letter.children.push(buildChild(x, i + 1, consume))
consume.splice(0, letter.children[letter.children.length - 1].count)
}
return letter
}
export function buildTree(splitPaths) {
let builder = []
while (splitPaths.length) {
builder.push(buildChild(0, 0, splitPaths))
splitPaths.splice(0, builder[builder.length - 1].count)
}
return builder
}
const regParamPrefix = /^::?/
const regCleanNonAschii = /(?![a-zA-Z_])./g
const regCleanRest = /_+/g
function splitAndSortPaths(paths, separateStatic = true) {
let staticPaths = new Map()
let paramsPaths = []
let collator = new Intl.Collator('en', { sensitivity: 'accent' });
paths.forEach(function(entry) {
if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start')
// Collect static paths separately
if (entry.path.indexOf('/:') < 0 && separateStatic) {
return staticPaths.set(entry.path, {
path: entry,
params: {}
})
}
// Collect params path separately
paramsPaths.push({
split: entry.path.slice(1).split(/\//g).map(function(word) {
let actualWord = word.replace(regParamPrefix, '')
return {
word: actualWord,
isParams: word[0] === ':' && word[1] !== ':',
isFullParams: word[0] === ':' && word[1] === ':',
paramVarName: word[0] === ':'
? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_')
: null
}
}),
entry,
})
})
paramsPaths.sort(function(aGroup, bGroup) {
let length = Math.max(aGroup.split.length, bGroup.split.length)
for (let x = 0; x < length; x++) {
let a = aGroup.split[x]
let b = bGroup.split[x]
if (!a) return -1
if (!b) return 1
// Full params go last
if (a.isFullParams && b.isFullParams) throw new RouterError(aGroup.entry, bGroup.entry, 'Two full path routes found on same level')
if (a.isFullParams) return 1
if (b.isFullParams) return -1
// Params go second last
if (a.isParams && !b.isParams) return 1
if (!a.isParams && b.isParams) return -1
// otherwise sort alphabetically if not identical
if (a.word !== b.word) return collator.compare(a.word, b.word)
}
throw new RouterError(aGroup, bGroup, 'Two identical paths were found')
})
return {
staticPaths,
paramsPaths,
}
}
const SlashCode = '/'.charCodeAt(0)
function getIndex(offset, additions, params) {
return (offset + additions)
+ (params.length
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
: '')
}
function treeIntoCompiledTreeBufferReturnPath(indentString, paths, branch, params) {
let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
}
let output = ''
output += '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],`
if (params.length) {
output += '\n' + indentString + ` params: {`
for (let param of params) {
output += '\n' + indentString + ` ${param[0]}: s${param[1]}.toString(),`
}
output += '\n' + indentString + ` },`
} else {
output += '\n' + indentString + ` params: {},`
}
output += '\n' + indentString + `}`
return output
}
function treeIntoCompiledTreeBufferBranch(paths, branches, indent = 0, params = []) {
let output = ''
let indentation = ''.padStart((indent - params.length) * 2)
let addEndBracket = true
for (let i = 0; i < branches.length; i++) {
let branch = branches[i]
if (i > 0) {
if (!branch.isParams && !branch.isFullParams) {
output += ' else '
} else {
// output += '} //'
output += '\n' + indentation
}
}
if (!branch.isParams && !branch.isFullParams) {
output += `if (buf[${getIndex(indent, 0, params)}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
if (branch.path) {
output += '\n' + indentation + ` if (buf.length === ${getIndex(indent, 1, params)}) {`
output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + ` }`
}
} else {
addEndBracket = false
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
output += `let s${paramVarName} = buf.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})`
output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length`
output += '\n' + indentation
params.push([branch.isParams || branch.isFullParams, paramVarName])
if (branch.isFullParams) {
output += treeIntoCompiledTreeBufferReturnPath(indentation, paths, branch, params)
} else if (branch.path) {
output += '\n' + indentation + `if (buf.length === ${getIndex(indent, 0, params)}) {`
output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + `}`
}
}
if (branch.children.length) {
if (branch.path) {
output += ' else '
} else {
output += '\n' + indentation + ' '
}
output += treeIntoCompiledTreeBufferBranch(paths, branch.children, indent + 1, params.slice())
}
if (addEndBracket) {
output += '\n' + indentation + '} '
}
}
return output
}
export function treeIntoCompiledTreeBuffer(paths, tree) {
let output = 'return function RouteResolverBuffer(paths, static, str) {'
output += '\n let checkStatic = static.get(str)'
output += '\n if(checkStatic) {'
output += '\n return checkStatic'
output += '\n }'
output += '\n var buf = Buffer.from(str)'
output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1)
output += '\n return null'
output += '\n}'
if (Debug) {
console.log(output)
}
return new Function(output)()
}
function treeIntoCompiledTreeStringReturnPath(indentString, paths, branch, params) {
let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
}
let output = ''
output += '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],`
if (params.length) {
output += '\n' + indentString + ` params: {`
for (let param of params) {
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
}
output += '\n' + indentString + ` },`
} else {
output += '\n' + indentString + ` params: {},`
}
output += '\n' + indentString + `}`
return output
}
function treeIntoCompiledTreeStringBranch(paths, branches, indent = 0, params = []) {
let output = ''
let indentation = ''.padStart((indent - params.length) * 2)
let addEndBracket = true
for (let i = 0; i < branches.length; i++) {
let branch = branches[i]
if (i > 0) {
if (!branch.isParams && !branch.isFullParams) {
output += ' else '
} else {
// output += '} //'
output += '\n' + indentation
}
}
if (!branch.isParams && !branch.isFullParams) {
output += `if (str.charCodeAt(${getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
if (branch.path) {
output += '\n' + indentation + ` if (str.length === ${getIndex(indent, 1, params)}) {`
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + ` }`
}
} else {
addEndBracket = false
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${getIndex(indent, 0, params)}) >>> 0`})`
output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length`
output += '\n' + indentation
params.push([branch.isParams || branch.isFullParams, paramVarName])
if (branch.isFullParams) {
output += treeIntoCompiledTreeStringReturnPath(indentation, paths, branch, params)
} else if (branch.path) {
output += '\n' + indentation + `if (str.length === ${getIndex(indent, 0, params)}) {`
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + `}`
}
}
if (branch.children.length) {
if (branch.path) {
output += ' else '
} else {
output += '\n' + indentation + ' '
}
output += treeIntoCompiledTreeStringBranch(paths, branch.children, indent + 1, params.slice())
}
if (addEndBracket) {
output += '\n' + indentation + '} '
}
}
return output
}
export function treeIntoCompiledTreeString(paths, tree) {
let output = 'return function RouteResolverString(paths, static, str) {'
output += '\n let checkStatic = static.get(str)'
output += '\n if(checkStatic) {'
output += '\n return checkStatic'
output += '\n }'
output += '\n ' + treeIntoCompiledTreeStringBranch(paths, tree, 1)
output += '\n return null'
output += '\n}'
if (Debug) {
console.log(output)
}
return new Function(output)()
}
export function compilePaths(paths) {
let splitPaths = splitAndSortPaths(paths)
let tree = buildTree(splitPaths.paramsPaths.slice())
printTree(tree, 0)
let compiled = treeIntoCompiledTreeBuffer(paths, tree)
let compiledString = treeIntoCompiledTreeString(paths, tree)
return [
[compiled, compiled.bind(null, paths, splitPaths.staticPaths)],
[compiledString, compiledString.bind(null, paths, splitPaths.staticPaths)],
]
}