router v2 proof of concept implemented
This commit is contained in:
parent
1ed5b49aee
commit
a3cf59fbc8
8 changed files with 844 additions and 3 deletions
|
@ -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(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
|
|
@ -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
148
benchmark/ifs.mjs
Normal 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
85
benchmark/ifs_compile.mjs
Normal 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
106
benchmark/map_query.mjs
Normal 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()
|
||||||
|
});
|
96
benchmark/router_flaska_v2.mjs
Normal file
96
benchmark/router_flaska_v2.mjs
Normal 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
33
benchmark/test.mjs
Normal 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
356
router_v2.mjs
Normal 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)],
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue