#include "dispatcher.h"
#include "graph.h"
#include "lexer.h"
#include "params.h"
#include "router.h"
#include "symbol.h"
#include "terminal.h"
#include "token.h"
#include "wrouter.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
inline void router_retain(const wrouter_t *router, const wrouter_route_t *route)
{
if (router->retain != NULL)
router->retain(route->ctx);
}
/**
* Match a route in the dispatcher.
*
* @param dispatcher Request dispatcher.
*/
const wrouter_route_t *route_match(wrouter_dispatcher_t *d)
{
token_t tok = { 0 };
size_t symbol = 0;
const char *w_param = NULL;
uint16_t n_literals = 0;
wrouter_param_t *param = NULL;
const wrouter_t *router = d->router;
const uint16_t *g = router->graph;
const uint16_t *cursor = g;
const uint16_t *node = NULL;
const uint16_t *l_edge_base = NULL, *p_edge = NULL, *w_edge = NULL, *t_edge = NULL;
const uint16_t *p_sym = NULL;
// Check for an empty graph, which is valid, but will never match anything.
if (g == NULL)
goto not_found;
// Reset parameter count.
d->params.count = 0;
lexer_next:
// Consume next token from the lexer.
tok = lexer_next(&d->lx);
node = cursor++;
// PARAMETER.
// If the node has a special edge, then advance the base edge beyond it.
// The literal edges start after the special edge, if present. A special
// edge is either a parameter or a wildcard. They cannot coexist; that is,
// there is only ever one or zero special edges.
if (*node & NODE_FLAG_HAS_PARAM) {
p_sym = cursor++;
p_edge = cursor++;
}
// WILDCARD.
// Remember the most specific wildcard edge, if present.
if (*node & NODE_FLAG_HAS_WILDCARD) {
w_edge = cursor++;
w_param = tok.ptr;
}
// TRAILING.
// The trailing-slash edge is stored after the special edge if it exists,
// otherwise immediately after the node.
if (*node & NODE_FLAG_HAS_TRAILING)
t_edge = cursor++;
// LITERALS.
// Literal edges.
l_edge_base = cursor;
// PROCESS TOKEN.
// Process the segment token against the current node.
switch (tok.type) {
// Literal string.
case TOKEN_LITERAL:
// If this node has literals, try to resolve and match.
n_literals = *node & NODE_LITERALS_MASK;
if (n_literals) {
// Resolve the literal string to a symbol.
symbol = symbol_nresolve(&router->literals, tok.ptr, tok.length);
// If the symbol is resolved, try to match against an edge.
if (symbol) {
// Do a binary search if n > 16, otherwise do a linear scan.
if (n_literals > 16) {
// See binary search example from 6.4 Pointers to
// Structures, K&R C 2nd ed. (ANSI), page 137.
uint16_t low = 0, mid, high = n_literals;
ptrdiff_t cond;
while (low < high) {
mid = low + (high - low) / 2;
if ((cond = symbol - *(l_edge_base + mid * 2)) < 0)
high = mid;
else if (cond > 0)
low = mid + 1;
else {
cursor = g + *(l_edge_base + mid * 2 + 1);
goto lexer_next;
}
}
} else {
for (uint16_t i = 0; i < n_literals; i++) {
cursor = &l_edge_base[i * 2];
// Follow symbol.
if (*cursor == symbol) {
cursor = g + *(cursor + 1);
goto lexer_next;
}
}
}
}
}
// Check for parameter.
if (*node & NODE_FLAG_HAS_PARAM) {
// Record parameter name and value.
param = param_next(&d->params);
param->name = symbol_lookup(&router->params, *p_sym);
param->value = tok.ptr;
param->length = tok.length;
// Follow parameter.
cursor = g + *p_edge;
goto lexer_next;
}
// Terminate at most specific wildcard if one has been seen.
if (w_edge != NULL)
goto wildcard;
// Not found.
goto not_found;
// Trailing-slash token.
case TOKEN_TRAILING:
if (*node & NODE_FLAG_HAS_TRAILING)
goto trailing;
goto not_found;
// End token.
case TOKEN_END:
// Check for terminal.
if (*node & NODE_FLAG_TERMINAL) {
cursor = node;
goto terminal;
}
// Not found.
goto not_found;
}
goto not_found;
not_found:
// Not found; no parameters.
d->params.count = 0;
return NULL;
trailing:
// Follow the trailing-slash edge, and terminate.
cursor = (g + *t_edge);
goto terminal;
wildcard:
// Save the wildcard parameter.
param = param_next(&d->params);
param->name = WILDCARD_PARAM;
param->value = w_param;
param->length = d->lx.str + d->lx.length - w_param;
// Follow the wildcard edge and terminate.
cursor = (g + *w_edge);
terminal:
return terminal_lookup(&router->terminals, (ptrdiff_t)(cursor - g));
}
/**
* Number of terminal routes in the router.
*/
size_t wrouter_route_count(const wrouter_t *router)
{
return router->num_routes;
}
/**
* Free the router.
*/
void wrouter_free(wrouter_t *router)
{
if (router == NULL)
return;
// Release all route contexts.
if (router->release != NULL) {
router->release(router->fallback.ctx);
for (uint16_t i = 0; i < router->terminals.count; i++)
router->release(router->terminals.base[i].ctx);
}
// Free the router.
symbols_free(&router->literals);
symbols_free(&router->params);
terminals_free(&router->terminals);
free(router->graph);
free(router);
}
void wrouter_destroy(wrouter_t **rpp)
{
if (rpp == NULL || *rpp == NULL)
return;
wrouter_free(*rpp);
*rpp = NULL;
}