Hustle::Table - Cached general purpose dispatch and routing table
use Hustle::Table;
Create a new table:
my $table=Hustle::Table->new;
Add entry as hash ref:
$table->add( { matcher => qr/regex (match)/, value=> "a value"});
Add entry as array ref (3 elements required):
$table->add( [qr/another/, "another value", undef])
Add entry as flat key value pairs:
$table->add(matcher=>"jones", value=> sub {"one more"}, type=>"begin");
Add entry as tuple
$table->add(qr|magic matcher| => "to a value");
Set the default entry:
$table->set_default("default value");
Prepare a dispatcher external cache:
my %cache;
my $dispatch = $table->prepare_dispatcher(cache=>\%cache);
Call dispatcher to return the matching entries and any regex captures. Multiple items can be tested in a single call
my @results=$dispatch->("thing to match", "another thing", ...);
# @results contains pairs of entries and capture arrays
This module provides a class to construct a routing table and build a high performance dispatcher from it.
A table can have any combination of regex, subroutine, exact string, begin string, end string or numeric matching of entries. The order in which the entries are added defines their precedence. First in, first tested.
In the case of no entries matching the input, a default/fallback entry always matches.
Once all the entries have been added to the table, a dispatcher is prepared/created. The dispatcher is an anonymous subroutine, which tests its arguments against the matcher in each entry in the table.
**NOTE:**From v0.7.0 results are returned a list containing pairs of matching entries and an anonymous array of any captures from regex matching if applicable.
Prior to v0.7.0, testing would stop after the first match.
If more entries are required to be added to the table, the dispatcher must be prepared again.
A cache (hash) is used to drastically improve table lookup performance. Entries are automatically added to the cache. Removal of cache entries is up to the user to implement on a application basis.
From v0.6.0: Regexp from non core Regexp engines are now usable as a matcher directly. In previous versions, these where not detected and processed as a string to be converted into a Perl core Regexp internally.
In version v0.5.3 and earlier, the dispatcher would always return a two element list. The first being the match entry, and the second array ref of any captures from a regexp match. If the matcher type was 'begin', 'end', 'exact', or 'numeric', the second element would always be an reference to an empty array.
From v0.5.4 onwards to optimise performance of non regex matching, this is no longer the case. Only regex type matching will generate this second element. Other matching types will not.
In other words when calling the dispatcher:
my ($entry, $captures)=$dispatcher->($input)
The $captures
variable above now will be undef
instead of []
, for non
regex matching
Calling the class constructor returns a new table. There are no required arguments:
my $table=Hustle::Table->new;
In this case, a default catch all entry (an undef value) is added automatically.
If an argument is provided, it is the value used in the default/catch all entry:
my $table=Hustle::Table->new($default);
An entry is an anonymous array containing the following elements:
[matcher, value, type, default]
-
matcher
matcher
can be a regex, a subroutine, a string or a numeric value.When
matcher
is a regex, any captures are returned as the second item when calling the dispatcherWhen
matcher
is a subroutine, it is called with input to test and a reference to thevalue
field in the entry as the two arguments. If it returns a true value it matches.When
matcher
is string or numeric value, the last fieldtype
specifies how to perform the match. Seetype
below.If no
type
is specified or isundef
, thematcher
is always treated as a regex -
value
This is the data you want to retrieve from the table when the matches.
-
type
type
is used to adjust how the matcher is interpreted. The possible values are:undef => matcher treated as a regex or subroutine if possible forces basic scalars to become a regexp "begin" => matcher string matches the begining of input string "end" => matcher string matches the end of input string "exact" => matcher string matches string equality "numeric" => matcher number matches numeric equality
If
matcher
is a precompiled regex (i.e.qr{}
), or a subroutine (i.e. CODE reference),type
is ignored.If
matcher
is a string or number, it is treated as a regex unlesstype
is as above. -
default
This is a flag indicating if the entry was the default entry. This can not be set
Entries are added in anonymous hash, anonymous array or flattened format, using
the add
method.
Anonymous array entries must contain 3 elements, in the order of:
$table->add([$matcher, $value, $type]);
Anonymous hashes format only need to specify the matcher and value pairs
$table->add({matcher=>$matcher, value=>$value, type=>$type});
Single flattened format takes a list directly. It must contain 4 elements
$table->add(matcher=>$matcher, value=> $value);
Single simple format takes two elements
$table->add(qr{some matcher}=>$value);
Or add multiple at once using mixed formats together
$table->add(
[$matcher, $value, $type],
{matcher=> $matcher, value=>$value},
matcher=>$matcher, value=>$value
);
In any case,matcher
and value
are the only items which must be defined
for subroutine and regex matchers. String matching will need the type
also
specified.
Each list has a default matcher that will unconditionally match the input. This
entry is specified by using undef
as the matcher when adding an entry.
To make it more explicit, the it can also be changed via the set_default
method.
The default value
of the 'default' entry is undef
Once all the entries are added to the table, the dispatcher can be
constructed by calling prepare_dispatcher
:
my $dispatcher=$table->prepare_dispatcher(%args);
Arguments to this method include:
-
cache
The hash ref to use as the dispatchers cache. Specifying a hash allows external management. If no cache is specified an internal cache is used.
The dispatcher is simply a sub, which you call with the input to match against the table entries:
my ($entry, $captures)=$dispatcher->("input");
my $value=$entry->[1];
The return from the dispatcher is a list of up to two elements.
The first is the array reference to the table entry that matched (or the default entry if no match was found). The value associated with the table entry is located in position 1
The second item, if present, is an anonymous array of any captures due to a matching regex.
NOTE In version 0.5.3 and earlier: the second element was returned as a ref to an empty array even if the matcher was not a regex.
Solid performance compared to other Perl routing/dispatch modules. Faster in basic tests then other Perl modules:
Smart::Dispatch Router::Simple Router::Boom
If you need even more performance then checkout URI::Router
TODO: make proper benchmark and comparison
Ruben Westerberg, drclaw@mac.com
Please report any bugs via git hub: http://github.com/drclaw1394/perl5-hustle-table
Copyright (C) 2025 by Ruben Westerberg
Licensed under MIT
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.