@jsonjoy.com/jit-router
Version:
High-performance HTTP router with JIT compilation.
206 lines (205 loc) • 9.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RoutingTreeNode = void 0;
const helpers_1 = require("@jsonjoy.com/codegen/lib/util/helpers");
const printTree_1 = require("sonic-forest/lib/print/printTree");
const RadixTree_1 = require("sonic-forest/lib/radix/RadixTree");
const router_1 = require("./router");
const steps_1 = require("./steps");
const genExactMatchCondition = (text, opts) => {
return (0, helpers_1.emitStringMatch)('str', opts.offset, text);
};
class RoutingTreeNode {
constructor() {
this.exact = new Map();
this.start = new RadixTree_1.RadixTree();
this.until = [];
this.regex = [];
}
add(route, step, destination) {
const isLast = step === route.steps.length - 1;
const match = route.steps[step];
if (match instanceof steps_1.ExactStep) {
if (isLast) {
const exact = this.exact.get(match.text.length) ?? [];
exact.push([match, destination]);
this.exact.set(match.text.length, exact);
}
else {
const child = this.start.get(match.text);
if (child)
child.add(route, step + 1, destination);
else {
const node = new RoutingTreeNode();
this.start.set(match.text, node);
node.add(route, step + 1, destination);
}
}
}
else if (match instanceof steps_1.UntilStep) {
let until = this.until.find(([step, dest]) => step.until === match.until && step.name === match.name && dest instanceof RoutingTreeNode);
if (!until || !(until[1] instanceof RoutingTreeNode)) {
until = [match, new RoutingTreeNode()];
this.until.push(until);
}
if (isLast) {
until[1].end = destination;
}
else {
until[1].add(route, step + 1, destination);
}
}
else if (match instanceof steps_1.RegexStep) {
if (isLast) {
this.regex.push([match, destination]);
}
else {
const regex = this.regex.find(([step, dest]) => step.regex === match.regex &&
step.until === match.until &&
step.name === match.name &&
dest instanceof RoutingTreeNode);
if (regex && regex[1] instanceof RoutingTreeNode) {
regex[1].add(route, step + 1, destination);
}
else {
const node = new RoutingTreeNode();
this.regex.push([match, node]);
node.add(route, step + 1, destination);
}
}
}
}
codegen(ctx, opts) {
const code = ctx.codegen;
if (this.exact.size) {
if (!opts.depth) {
code.switch('len', [...this.exact].map(([length, destinations]) => [
length,
() => {
code.switch('str', destinations.map(([step, destination]) => {
const m = code.linkDependency(destination.match);
return [JSON.stringify(step.text), () => code.return(`${m}.params = null, ${m}`), true];
}));
},
]));
}
else {
for (const destinations of this.exact.values()) {
for (const [step, destination] of destinations) {
const m = code.linkDependency(destination.match);
code.if(`(${step.text.length} + ${opts.offset} === len) && ${genExactMatchCondition(step.text, opts)}`, () => code.return(`${m}.params = params, ${m}`));
}
}
}
}
if (!opts.depth) {
code.js('var params = [];');
}
const emitRadixTreeNode = (node, opts) => {
const text = node.k;
const length = text.length;
const block = () => {
const offset = length ? code.var(`${opts.offset} + ${length}`) : opts.offset;
node.forChildren((child) => {
emitRadixTreeNode(child, opts.create(offset));
});
const routingNode = node.v;
if (routingNode) {
routingNode.codegen(ctx, opts.create(offset));
}
};
if (text) {
code.if(genExactMatchCondition(text, opts), block);
}
else
block();
};
emitRadixTreeNode(this.start, opts);
if (this.until.length) {
for (const [step, destination] of this.until) {
if (destination.end && step.until === '\n') {
const m = code.linkDependency(destination.end.match);
if (step.name)
code.js(`params.push(str.slice(${opts.offset}, len));`);
code.return(`${m}.params = params, ${m}`);
}
else {
// const ri = code.var(opts.offset);
// code.js(`for(; ${ri} < len && str[${ri}] !== ${JSON.stringify(step.until)}; ${ri}++);`);
// code.js(`for(; ${ri} < len && str.charCodeAt(${ri}) !== ${step.until.charCodeAt(0)}; ${ri}++);`);
const ri = code.var(`str.indexOf(${JSON.stringify(step.until)}, ${opts.offset})`);
if (destination.end) {
const m = code.linkDependency(destination.end.match);
code.if(`${ri} === -1`, () => {
if (step.name)
code.js(`params.push(str.slice(${opts.offset}, len));`);
code.return(`${m}.params = params, ${m}`);
});
}
if (destination.exact.size ||
destination.start.size ||
destination.until.length ||
destination.regex.length) {
code.if(`${ri} > ${opts.offset}`, () => {
if (step.name)
code.js(`params.push(str.slice(${opts.offset}, ${ri}));`);
destination.codegen(ctx, opts.create(ri));
if (step.name)
code.js(`params.pop();`);
});
}
}
}
}
if (this.regex.length) {
for (const [step, destination] of this.regex) {
const isDestination = destination instanceof router_1.Destination;
const r = code.var(`str.slice(${opts.offset})`);
const regex = new RegExp('^' + step.regex + step.until + (isDestination ? '$' : ''));
const reg = code.linkDependency(regex);
const match = code.var(`${r}.match(${reg})`);
if (isDestination) {
code.if(match, () => {
const val = code.var(`${match}[1] || ${match}[0]`);
const m = code.linkDependency(destination.match);
if (step.name)
code.js(`params.push(${val});`);
code.return(`${m}.params = params, ${m}`);
});
}
else {
code.if(match, () => {
const val = code.var(`${match}[1] || ${match}[0]`);
const offset = code.var(`${opts.offset} + ${val}.length`);
if (step.name)
code.js(`params.push(${val});`);
destination.codegen(ctx, opts.create(offset));
if (step.name)
code.js(`params.pop();`);
});
}
}
}
}
toString(tab) {
return (`${this.constructor.name} ` +
(0, printTree_1.printTree)(tab, [
!this.exact.size
? null
: (tab) => 'exact ' +
(0, printTree_1.printTree)(tab, [...this.exact].map(([length, destinations]) => (tab) => `${length} ` +
(0, printTree_1.printTree)(tab, destinations.map(([step, destination]) => (tab) => `${JSON.stringify(step.toText())}${destination instanceof router_1.Destination ? ' →' : ''} ${destination.toString(tab + ' ')}`)))),
!this.end ? null : (tab) => 'end → ' + this.end.toString(tab),
!this.start.size ? null : (tab) => 'start ' + this.start.toString(tab),
!this.until.length
? null
: (tab) => 'until ' +
(0, printTree_1.printTree)(tab, this.until.map(([step, destination]) => (tab) => `${step.toText()}${destination instanceof router_1.Destination ? ' →' : ''} ${destination.toString(tab)}`)),
!this.regex.length
? null
: (tab) => 'regex ' +
(0, printTree_1.printTree)(tab, this.regex.map(([step, destination]) => (tab) => `${step.toText()}${destination instanceof router_1.Destination ? ' →' : ''} ${destination.toString(tab)}`)),
]));
}
}
exports.RoutingTreeNode = RoutingTreeNode;