UNPKG

@jsonjoy.com/jit-router

Version:

High-performance JIT router for Node.js

203 lines 9.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RoutingTreeNode = void 0; const helpers_1 = require("@jsonjoy.com/util/lib/codegen/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(`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; //# sourceMappingURL=tree.js.map