=head1 NAME

Hustle::Table - Cached general purpose dispatch and routing table

=head1 SYNOPSIS

  use Hustle::Table;

Create a new table:

  my $table=Hustle::Table->new;

Add entry as hash ref:

  $table->add( { matcher => "regex (match)", value=> "a value"});

Add entry as array ref (3 elements required):

   $table->add( ["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("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 RegExp captures. Multiple
items can be tested in a single call

  my @pairs=$dispatch->("thing to match", "another thing", ...);	

  # @pairs contains pairs of entries and capture arrays

  # perl v5.36 
  for my($e, $c)(@pairs){
    $e->[1]; # The value 
    $c;      # Possible captures
  }


=head1 DESCRIPTION

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 RegExp, 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 needs to be
prepared/created. The dispatcher is an anonymous subroutine, which tests its
arguments against the matcher in each entry in the table.

B<NOTE:> From v0.7.0 The entries that matched the input are returned along with
an anonymous array of RegExp captures if applicable, as a pair. Multiple pairs
are returned if more than one match. 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.

=head2 API Change

B<From v0.8.0:> Matchers B<MUST> be input as strings or CODE refs only. Regexp
matchers are generated internally from the matcher string. This is aid
searching through the table, for modifications by other packages.  CODE
matchers B<MUST> specify the optional type argument as "code";

B<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.

B<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.

B<From v0.5.4 onwards> to optimise performance of non RegExp matching, this is
no longer the case. Only RegExp 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 C<$captures> variable above now will be C<undef> instead of C<[]>, for non
RegExp matching


=head1 CREATING A TABLE 

  Hustle::Table->new(...);

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 C<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);


=head1 ENTRIES

=head2 Structure

An entry is an anonymous array containing the following elements:
	
  [matcher, value, type]

=over 

=item matcher

C<matcher> can be a RegExp (as source string), a subroutine, a string or a
numeric value.

When C<matcher> treated as a RegExp, any captures are returned as the second
item of the pair when calling the dispatcher

When C<matcher> is a subroutine,  it is called with input to test and a
reference to the C<value> field in the entry as the two arguments. If it
returns a true value it matches. 

When  C<matcher> is string or numeric value, the last field C<type> specifies
how to perform the match. See C<type> below.

If no C<type> is specified or is C<undef>, the C<matcher> is always treated as
a RegExp.  B<From v0.8.0:> If treated as a RegExp, the C<type> field is
replaced with the compiled RexExp.

=item value

This is the data you want to retrieve from the table when entry matches the
input.

=item type

C<type> is used to adjust how the matcher is interpreted. The possible values
are:
	
  undef	  => 	matcher treated as a RegExp source stirng. 

  "code"  =>  uses code refernce to match argument

  "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

=back

=head2 Adding

  $table->add(...);

Entries are added in anonymous hash, anonymous array or flattened format, using
the C<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, and
will be treated as a RegExp match:

  $table->add(matcher=>$matcher, value=> $value);

Single simple format takes two elements and will be treated as RegExp match:

  $table->add("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,C<matcher> and C<value> are the only items which must be defined
for subroutine and RegExp matchers. String, numeric and code matching will need
the C<type> also specified.


=head2 Default Matcher

  $table->set_default($value)

Each list has a default matcher that will unconditionally match the input. It
is always in the table and B<is only tested when no other matcher matched>

If the default matcher matches it will return C<$value> on matching and an
empty capture array.


=head2 Manipulating Table Entries

There are no explicit manipulation methods. The table is just an array and it
can be accessed like an any other array e.g. accessing elements, C<splice>,
C<shift>, C<unshift>, C<pop>, C<push>.

Just keep in mind the last item in the table is always the default matcher.

After entires have been modified the dispatcher must be prepared again


=head1 PREPARING A DISPATCHER
  
  my $dispatcher=$table->prepare_dispatcher(%args);

Once all the entries are added to the table, the dispatcher can be constructed
by calling C<prepare_dispatcher>:

Arguments to this method include:

=over 

=item 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.

=back


When a dispatcher is prepared, the cache is emptied, any RegExp matchers are
compiled and the table is forced to always have at least one entry (the default
matcher).


=head1 USING A DISPATCHER

  my @pairs=$dispatcher->("input");

  # perl v5.36 
  for my($e, $c)(@pairs){
    $e->[1]; # The value 
    $c;      # Possible captures
  }


The dispatcher is simply a sub, which you call with the input to match against
the table entries:

The returned list are pairs of entries and captures

The first pair item 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 pair item is an anonymous array of any captures due to a matching
RegExp, or C<undef> otherwise

B<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 RegExp.


=head1 COMPARISON TO OTHER MODULES

Solid performance compared to other Perl routing/dispatch modules. Faster in
basic tests then other Perl modules: 

L<Smart::Dispatch>
L<Router::Simple>
L<Router::Boom>


If you need even more performance then checkout L<URI::Router>

TODO: make proper benchmark and comparison

=head1 AUTHOR

Ruben Westerberg, E<lt>drclaw@mac.comE<gt>

=head1 REPOSITORTY and BUGS

Please report any bugs via git hub: L<http://github.com/drclaw1394/perl5-hustle-table>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2025 by Ruben Westerberg

Licensed under MIT

=head1 DISCLAIMER OF WARRANTIES

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.

=cut
