File Coverage

blib/lib/Criteria/Compile.pm
Criterion Covered Total %
statement 151 166 90.9
branch 41 72 56.9
condition 9 29 31.0
subroutine 34 39 87.1
pod 9 10 90.0
total 244 316 77.2


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # Copyright (C) 2011 - Anthony J. Lucas - kaoyoriketsu@ansoni.com
5              
6              
7              
8             package Criteria::Compile;
9              
10              
11              
12 5     5   4339 use strict;
  5         11  
  5         203  
13 5     5   29 use warnings;
  5         11  
  5         187  
14 5     5   34 no warnings 'uninitialized';
  5         10  
  5         371  
15              
16              
17              
18             our $VERSION = '0.04__7';
19              
20              
21              
22 5     5   5261 use UNIVERSAL ( );
  5         72  
  5         112  
23 5     5   4582 use Tie::IxHash ( );
  5         30271  
  5         145  
24              
25              
26              
27             #INIT CONFIG / VARS
28              
29              
30 5     5   52 use constant HANDLER_DIE_MSG => 'Failed to compile `%s`. %s';
  5         8  
  5         449  
31              
32             use constant {
33 5         703 TYPE_STATIC => 10,
34             TYPE_CHAINED => 20,
35             TYPE_DYNAMIC => 30
36 5     5   28 };
  5         11  
37              
38             my $DEFAULT_CRITERIA_DISPATCH_TBL = {
39             TYPE_STATIC() => {},
40             TYPE_CHAINED() => {},
41             TYPE_DYNAMIC() => {
42             qw/^(.*)_like$/ => qw/_gen_like_sub/,
43             qw/^(.*)_matches$/ => qw/_gen_matches_sub/,
44             qw/^(.*)_is$/ => qw/_gen_is_sub/,
45             qw/^(.*)_in$/ => qw/_gen_in_sub/,
46             qw/^(.*)_less_than$/ => qw/_gen_less_than_sub/,
47             qw/^(.*)_greater_than$/ => qw/_gen_greater_than_sub/,
48             }
49             };
50              
51             use constant {
52 5         13825 ACC_HASH => 'HASH',
53             ACC_OBJECT => 'OBJECT'
54 5     5   30 };
  5         9  
55              
56             my $DEFAULT_ACCESS_MODE_TBL = {
57             ACC_OBJECT() => sub {
58             my ($ob, $attr) = @_;
59             return &UNIVERSAL::can($ob, $attr)
60             ? $ob->$attr()
61             : undef;
62             },
63             ACC_HASH() => sub {
64             return $_[0]->{$_[1]};
65             }
66             };
67              
68              
69              
70             #INITIALISATION ROUTINES
71              
72              
73             sub new {
74              
75 16     16 1 4871 my ($class, %crit) = @_;
76             my $self = {
77             dispatch_tbl => {},
78             access_tbl => {},
79 0     0   0 exec_sub => sub { 1 },
80 16         137 criteria => []
81             };
82              
83 16         54 $self = bless($self, $class);
84 16         786 $self->_init(\%crit);
85 16         70 return $self;
86             }
87              
88              
89             sub _init {
90              
91 16     16   30 my ($self, $crit, $nocomp) = @_;
92              
93             #initialise default criteria dispatch tbls
94 16         68 my $ordered_dt = ($self->{dispatch_tbl} = {});
95 16         70 foreach (keys(%$DEFAULT_CRITERIA_DISPATCH_TBL)) {
96             #perserve order
97 48         719 tie(my %dt, 'Tie::IxHash')
98 48         212 ->Push(%{$DEFAULT_CRITERIA_DISPATCH_TBL->{$_}});
99 48         1273 $ordered_dt->{$_} = \%dt;
100             }
101              
102             #initialise default access mode tbl
103 16         83 $self->{access_tbl} = {%$DEFAULT_ACCESS_MODE_TBL};
104 16         66 $self->access_mode(ACC_OBJECT);
105              
106             #validate any criteria supplied
107 16 50       43 if ($crit) {
108 16 50       63 die('Error: The supplied criteria could not be initialised')
109             unless($self->add_criteria(%$crit));
110 16 100       2663 if (!$nocomp) {
111 10 50       32 die('Error: Failed to compile criteria.')
112             unless ($self->compile());
113             }
114             }
115 16         34 return 1;
116             }
117              
118              
119             sub _define_grammar_dtbl {
120            
121 12     12   23 my ($self, $dtbl) = @_;
122 12         28 foreach my $token (keys(%$dtbl)) {
123 12         25 my $map = $dtbl->{$token};
124 12         35 foreach (keys(%$map)) {
125 48         130 $self->define_grammar($_, $map->{$_}, $token);
126             }
127             }
128 12         33 return 1;
129             }
130              
131              
132              
133             #CRTIERIA COMPILATION ROUTINES
134              
135              
136             sub export_sub {
137 0     0 1 0 my $self = $_[0];
138 0 0       0 $self->compile() unless ($self->{exec_sub});
139 0         0 $self->{exec_sub};
140             }
141              
142              
143             sub exec {
144 33     33 1 183 my $self = shift;
145 33 100       105 $self->compile() unless ($self->{exec_sub});
146 33         82 $self->{exec_sub}->(@_);
147             }
148              
149              
150             sub add_criteria {
151 18     18 1 100 my $self = shift;
152 18 100       76 return 1 unless (@_ > 0);
153 6         13 $self->{exec_sub} = undef;
154              
155 6         23 push(@{$self->{criteria}}, {@_});
  6         26  
156 6         19 return 1;
157             }
158              
159              
160             sub define_grammar {
161 48     48 1 89 my ($self, $match, $hdlr, $token) = @_;
162 48 50 33     189 return unless ($match and $hdlr);
163 48   50     90 $token ||= TYPE_DYNAMIC;
164 48         219 $self->{dispatch_tbl}->{$token}->{$match} = $hdlr;
165 48         667 return 1;
166             }
167              
168              
169             sub access_mode {
170 19     19 1 499 my ($self, $mode) = @_;
171 19 50       82 if ($mode = $self->{access_tbl}->{$mode}) {
172 19         41 $self->{getter} = $mode;
173 19         671 $self->compile();
174 19         34 return 1;
175             }
176 0         0 return 0;
177             }
178              
179              
180             sub define_access_mode {
181 0     0 1 0 my ($self, $mode, $getter) = @_;
182 0         0 my $a_tbl = $self->{access_tbl};
183             #define mode if not already present
184 0 0 0     0 unless ((!$mode or !$getter)
      0        
185             or $a_tbl->{$mode}) {
186 0         0 $a_tbl->{$mode} = $getter;
187             }
188 0         0 return 0;
189             }
190              
191              
192             sub compile {
193            
194 55     55 1 5786 my ($self, $crit) = @_;
195 55         87 my @action_list = ();
196 55         63 my @crit_list = @{$self->{criteria}};
  55         128  
197 55 100       128 push(@crit_list, $crit) if $crit;
198              
199             #attempt to build subs for criteria
200             #side-step failure condition compexity with blanket eval
201 55         94 my ($last_crit, $exec_sub) = '';
202 55         69 eval {
203 55         63 my ($sub, @args);
204 55         138 foreach my $map (@crit_list) {
205 24         68 foreach (keys(%$map)) {
206 68         101 $last_crit = $_;
207              
208             #lookup handler generator
209 68         160 ($sub, @args) = $self->resolve_dispatch($_);
210 68 50       152 die(sprintf(HANDLER_DIE_MSG, $_,
211             'Handler not found.'))
212             unless ($sub);
213              
214             #execute and store sub from generator
215 68 50       325 push(@action_list,
216             ((ref($sub) eq '')
217             ? $self->$sub($map->{$_}, @args)
218             : $sub->($self, $map->{$_}, @args)));
219             }
220             }
221             #compile all action subs into single sub
222 55         165 ($exec_sub = ($self->{exec_sub} = $self
223             ->_compile_exec_sub(@action_list)));
224             };
225 55 50 33     498 if ($@ or !($exec_sub)) {
226 0         0 chomp($@);
227 0         0 print("Error: Check if `$last_crit` is valid. ($@)\n");
228             }
229 55 50       199 return $@ ? 0 : 1;
230             }
231              
232              
233             sub resolve_dispatch {
234              
235 68     68 1 95 my ($self, $crit) = @_;
236 68         95 my $dispatch_tbl = $self->{dispatch_tbl};
237              
238             #attempt quick static lookup
239 68         322 my $sub = $dispatch_tbl->{TYPE_STATIC()}->{$crit};
240 68 50       564 return $sub if ($sub);
241              
242             #attempt more expensive lookups
243 68         78 my ($dtype_tbl, @matches, @args);
244 68         103 RESOLVE_CRIT: foreach (TYPE_CHAINED, TYPE_DYNAMIC) {
245 136         210 $dtype_tbl = $dispatch_tbl->{$_};
246 136         407 @matches = reverse(keys(%$dtype_tbl));
247              
248 136         4815 foreach (@matches) {
249 276 100       4839 next unless (@args = ($crit =~ /$_/));
250 68         314 $sub = $dtype_tbl->{$_};
251 68 50       601 if ($sub) {
252             #attempt to retrieve subref if not a method
253 68 0       369 $sub = ((exists &$sub) ? \&$sub : $sub)
    50          
254             unless (UNIVERSAL::can($self, $sub));
255 68         135 last RESOLVE_CRIT;
256             }
257             }
258             };
259 68 50       373 return ($sub, @args) if ($sub);
260             }
261              
262              
263 0     0 0 0 sub getter { &{shift->{getter}} }
  0         0  
264              
265              
266 0     0   0 sub _bless_handler { $_[1] }
267              
268              
269             sub _compile_exec_sub {
270            
271 55     55   90 my ($self, @actions) = @_;
272            
273             #create single multi-action execution sub
274             return sub {
275 33     33   57 my @args = @_;
276 33         59 foreach (@actions) {
277 67 100       20453 return 0 unless($_->(@args));
278             }
279 31         10012 return 1;
280 55         348 };
281             }
282              
283              
284              
285             #CRITERIA FACTORY ROUTINES
286              
287              
288             sub _gen_is_sub {
289              
290 16     16   28 my ($context, $val, $attr) = @_;
291              
292 16 50       34 die sprintf(HANDLER_DIE_MSG, 'is',
293             'No attribute supplied.')
294             unless ($attr);
295              
296             #lookup getter implementation once at compile time
297 16         24 my $getter = $context->{getter};
298             #create single multi-action execution sub
299             return sub {
300 15 100 66 15   59 return (ref($_[0])
301             and (local $_ = $getter->($_[0], $attr)))
302             ? ($_ eq $val)
303             : 0;
304 16         83 };
305             }
306              
307              
308             sub _gen_in_sub {
309              
310 4     4   11 my ($context, $val, $attr) = @_;
311              
312 4 50       12 die sprintf(HANDLER_DIE_MSG, 'is',
313             'No attribute supplied.')
314             unless ($attr);
315 4 50       14 die sprintf(HANDLER_DIE_MSG, 'is',
316             'Value supplied must be an ARRAYREF.')
317             unless (ref($val) eq 'ARRAY');
318              
319             #lookup getter implementation once at compile time
320 4         7 my $getter = $context->{getter};
321             #create single multi-action execution sub
322             return sub {
323 4     4   8 my ($ret, $v) = 0;
324 4 50 33     18 if (ref($_[0])
325             and ($v = $getter->($_[0], $attr))) {
326 4         31 foreach (@$val) {
327 32 100       65 ($ret = 1, last) if ($v eq $_);
328             }
329             }
330 4         13 return $ret;
331 4         28 };
332             }
333              
334              
335             sub _gen_like_sub {
336              
337 4     4   10 my ($context, $val, $attr) = @_;
338              
339 4 50       10 die sprintf(HANDLER_DIE_MSG, 'like',
340             'No attribute supplied.')
341             unless ($attr);
342              
343             #lookup getter implementation once at compile time
344 4         9 my $getter = $context->{getter};
345             #create single multi-action execution sub
346             return sub {
347 4     4   9 local $_ = $getter->($_[0], $attr);
348 4 50       124 return m/$val/ ? 1 : 0;
349 4         24 };
350             }
351              
352              
353             sub _gen_matches_sub {
354              
355 4     4   9 my ($context, $val, $attr) = @_;
356              
357 4 50       10 die sprintf(HANDLER_DIE_MSG, 'matches_than',
358             'No attribute supplied.')
359             unless ($attr);
360              
361             #lookup getter implementation once at compile time
362 4         7 my $getter = $context->{getter};
363             #create single multi-action execution sub
364             return sub {
365 4 50 33 4   19 (ref($_[0])
366             and (local $_ = $getter->($_[0], $attr)))
367             ? ($_ ~~ $val)
368             : 0;
369 4         24 };
370             }
371              
372              
373             sub _gen_less_than_sub {
374              
375 4     4   8 my ($context, $val, $attr) = @_;
376              
377 4 50       8 die sprintf(HANDLER_DIE_MSG, 'less_than',
378             'No attribute supplied.')
379             unless ($attr);
380              
381             #lookup getter implementation once at compile time
382 4         8 my $getter = $context->{getter};
383             #create single multi-action execution sub
384             return sub {
385 4 50 33 4   17 (ref($_[0])
386             and (local $_ = $getter->($_[0], $attr)))
387             ? ($_ < $val)
388             : 0;
389 4         27 };
390             }
391              
392              
393             sub _gen_greater_than_sub {
394              
395 4     4   10 my ($context, $val, $attr) = @_;
396              
397 4 50       10 die sprintf(HANDLER_DIE_MSG, 'greater_than',
398             'No attribute supplied.')
399             unless ($attr);
400              
401             #lookup getter implementation once at compile time
402 4         9 my $getter = $context->{getter};
403             #create single multi-action execution sub
404             return sub {
405 4 50 33 4   16 (ref($_[0])
406             and (local $_ = $getter->($_[0], $attr)))
407             ? ($_ > $val)
408             : 0;
409 4         20 };
410             }
411              
412              
413              
414              
415             #//HASH HELPER CLASSES
416              
417              
418             package Criteria::Compile::OBJECT;
419 5     5   53 use base qw( Criteria::Compile );
  5         11  
  5         731  
420              
421              
422             package Criteria::Compile::HASH;
423 5     5   27 use base qw( Criteria::Compile );
  5         10  
  5         943  
424              
425              
426             sub new {
427 1     1   3 my ($class, @args) = @_;
428 1         8 my $self = $class->SUPER::new(@args);
429 1         3 $self->access_mode(Criteria::Compile::ACC_HASH);
430 1         7 return $self;
431             }
432              
433              
434              
435              
436              
437             1;