File Coverage

blib/lib/Operator/Util.pm
Criterion Covered Total %
statement 124 129 96.1
branch 90 104 86.5
condition 33 33 100.0
subroutine 14 15 93.3
pod 8 8 100.0
total 269 289 93.0


line stmt bran cond sub pod time code
1             package Operator::Util;
2              
3 5     5   121881 use 5.006;
  5         20  
  5         170  
4 5     5   26 use strict;
  5         11  
  5         166  
5 5     5   30 use warnings;
  5         18  
  5         159  
6 5     5   15533 use parent 'Exporter';
  5         1836  
  5         27  
7 5     5   8214 use List::MoreUtils qw( uniq );
  5         8538  
  5         26358  
8              
9             our $VERSION = '0.05';
10             our @EXPORT_OK = qw(
11             reduce reducewith
12             zip zipwith
13             cross crosswith
14             hyper hyperwith
15             applyop reverseop
16             );
17             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
18              
19             *reducewith = \&reduce;
20             *hyperwith = \&hyper;
21              
22             # sub: operator subroutines
23             # res: responses in an arrayref for when zero or one argument is provided --
24             # the first element is the response for zero args and the second is for
25             # one arg, defaulting to the arg itself
26             # chain: set non-chaining Perl 5 operator to chaining-associative
27             # right: set operator to right-associative
28              
29             my %ops = (
30             # binary infix
31             'infix:**' => { sub => sub { $_[0] ** $_[1] }, res => [1], right => 1 },
32             'infix:=~' => { sub => sub { $_[0] =~ $_[1] }, res => [1, 1] },
33             'infix:!~' => { sub => sub { $_[0] !~ $_[1] }, res => [1, 1] },
34             'infix:*' => { sub => sub { $_[0] * $_[1] }, res => [1] },
35             'infix:/' => { sub => sub { $_[0] / $_[1] }, res => [undef] },
36             'infix:%' => { sub => sub { $_[0] % $_[1] }, res => [undef] },
37             'infix:x' => { sub => sub { $_[0] x $_[1] }, res => [undef] },
38             'infix:+' => { sub => sub { $_[0] + $_[1] }, res => [0] },
39             'infix:-' => { sub => sub { $_[0] - $_[1] }, res => [0] },
40             'infix:.' => { sub => sub { $_[0] . $_[1] }, res => [''] },
41             'infix:<<' => { sub => sub { $_[0] << $_[1] }, res => [undef] },
42             'infix:>>' => { sub => sub { $_[0] >> $_[1] }, res => [undef] },
43             'infix:<' => { sub => sub { $_[0] < $_[1] }, res => [1, 1], chain => 1 },
44             'infix:>' => { sub => sub { $_[0] > $_[1] }, res => [1, 1], chain => 1 },
45             'infix:<=' => { sub => sub { $_[0] <= $_[1] }, res => [1, 1], chain => 1 },
46             'infix:>=' => { sub => sub { $_[0] >= $_[1] }, res => [1, 1], chain => 1 },
47             'infix:lt' => { sub => sub { $_[0] lt $_[1] }, res => [1, 1], chain => 1 },
48             'infix:gt' => { sub => sub { $_[0] gt $_[1] }, res => [1, 1], chain => 1 },
49             'infix:le' => { sub => sub { $_[0] le $_[1] }, res => [1, 1], chain => 1 },
50             'infix:ge' => { sub => sub { $_[0] ge $_[1] }, res => [1, 1], chain => 1 },
51             'infix:==' => { sub => sub { $_[0] == $_[1] }, res => [1, 1], chain => 1 },
52             'infix:!=' => { sub => sub { $_[0] != $_[1] }, res => [1, 1], chain => 1 },
53             'infix:<=>' => { sub => sub { $_[0] <=> $_[1] }, res => [1, 1], chain => 1 },
54             'infix:eq' => { sub => sub { $_[0] eq $_[1] }, res => [1, 1], chain => 1 },
55             'infix:ne' => { sub => sub { $_[0] ne $_[1] }, res => [1, 1], chain => 1 },
56             'infix:cmp' => { sub => sub { $_[0] cmp $_[1] }, res => [1, 1], chain => 1 },
57             'infix:&' => { sub => sub { $_[0] & $_[1] }, res => [-1] },
58             'infix:|' => { sub => sub { $_[0] | $_[1] }, res => [0] },
59             'infix:^' => { sub => sub { $_[0] ^ $_[1] }, res => [0] },
60             'infix:&&' => { sub => sub { $_[0] && $_[1] }, res => [1] },
61             'infix:||' => { sub => sub { $_[0] || $_[1] }, res => [0] },
62             'infix:..' => { sub => sub { $_[0] .. $_[1] }, res => [1] },
63             'infix:...' => { sub => sub { $_[0] ... $_[1] }, res => [1] },
64             'infix:=' => { sub => sub { $_[0] = $_[1] }, res => [undef] },
65             'infix:**=' => { sub => sub { $_[0] **= $_[1] }, res => [undef] },
66             'infix:*=' => { sub => sub { $_[0] *= $_[1] }, res => [undef] },
67             'infix:/=' => { sub => sub { $_[0] /= $_[1] }, res => [undef] },
68             'infix:%=' => { sub => sub { $_[0] %= $_[1] }, res => [undef] },
69             'infix:x=' => { sub => sub { $_[0] x= $_[1] }, res => [undef] },
70             'infix:+=' => { sub => sub { $_[0] += $_[1] }, res => [undef] },
71             'infix:-=' => { sub => sub { $_[0] -= $_[1] }, res => [undef] },
72             'infix:.=' => { sub => sub { $_[0] .= $_[1] }, res => [undef] },
73             'infix:<<=' => { sub => sub { $_[0] <<= $_[1] }, res => [undef] },
74             'infix:>>=' => { sub => sub { $_[0] >>= $_[1] }, res => [undef] },
75             'infix:&=' => { sub => sub { $_[0] &= $_[1] }, res => [undef] },
76             'infix:|=' => { sub => sub { $_[0] |= $_[1] }, res => [undef] },
77             'infix:^=' => { sub => sub { $_[0] ^= $_[1] }, res => [undef] },
78             'infix:&&=' => { sub => sub { $_[0] &&= $_[1] }, res => [undef] },
79             'infix:||=' => { sub => sub { $_[0] ||= $_[1] }, res => [undef] },
80             'infix:,' => { sub => sub { $_[0] , $_[1] }, res => [[]] },
81             'infix:=>' => { sub => sub { $_[0] => $_[1] }, res => [[]] },
82             'infix:and' => { sub => sub { $_[0] and $_[1] }, res => [1] },
83             'infix:or' => { sub => sub { $_[0] or $_[1] }, res => [0] },
84             'infix:xor' => { sub => sub { $_[0] xor $_[1] }, res => [0] },
85             'infix:->' => { sub => sub { my $m = $_[1]; $_[0]->$m }, res => [undef] },
86             'infix:->=' => { sub => sub { my $m = $_[1]; $_[0] = $_[0]->$m }, res => [undef] },
87              
88             # unary prefix
89             'prefix:++' => { sub => sub { ++$_[0] } },
90             'prefix:--' => { sub => sub { --$_[0] } },
91             'prefix:!' => { sub => sub { !$_[0] } },
92             'prefix:~' => { sub => sub { ~$_[0] } },
93             'prefix:\\' => { sub => sub { \$_[0] } },
94             'prefix:+' => { sub => sub { +$_[0] } },
95             'prefix:-' => { sub => sub { -$_[0] } },
96             'prefix:$' => { sub => sub { ${$_[0]} } },
97             'prefix:@' => { sub => sub { @{$_[0]} } },
98             'prefix:%' => { sub => sub { %{$_[0]} } },
99             'prefix:&' => { sub => sub { &{$_[0]} } },
100             'prefix:*' => { sub => sub { *{$_[0]} } },
101              
102             # unary postfix
103             'postfix:++' => { sub => sub { $_[0]++ } },
104             'postfix:--' => { sub => sub { $_[0]-- } },
105              
106             # circumfix
107             'circumfix:()' => { sub => sub { ($_[0]) } },
108             'circumfix:[]' => { sub => sub { [$_[0]] } },
109             'circumfix:{}' => { sub => sub { {$_[0]} } },
110             'circumfix:${}' => { sub => sub { ${$_[0]} } },
111             'circumfix:@{}' => { sub => sub { @{$_[0]} } },
112             'circumfix:%{}' => { sub => sub { %{$_[0]} } },
113             'circumfix:&{}' => { sub => sub { &{$_[0]} } },
114             'circumfix:*{}' => { sub => sub { *{$_[0]} } },
115              
116             # postcircumfix
117             'postcircumfix:[]' => { sub => sub { $_[0]->[$_[1]] }, res => [undef] },
118             'postcircumfix:{}' => { sub => sub { $_[0]->{$_[1]} }, res => [undef] },
119             'postcircumfix:->[]' => { sub => sub { $_[0]->[$_[1]] }, res => [undef] },
120             'postcircumfix:->{}' => { sub => sub { $_[0]->{$_[1]} }, res => [undef] },
121             );
122              
123             # Perl 5.10 operators
124             if ($] >= 5.010) {
125             $ops{'infix:~~'} = { sub => eval 'sub { $_[0] ~~ $_[1] }', res => [1, 1], chain => 1 };
126             $ops{'infix://'} = { sub => eval 'sub { $_[0] // $_[1] }', res => [0] };
127             }
128              
129             sub reduce {
130 79     79 1 5362 my ($op, $list, %args) = @_;
131 79         100 my $type;
132 79         174 ($op, $type) = _get_op_info($op);
133              
134 79 50       164 return unless $op;
135 79 50       160 return if $type ne 'infix';
136              
137 79 100       313 my @list = ref $list eq 'ARRAY' ? @$list :
    100          
138             defined $list ? $list :
139             () ;
140              
141             # return default for zero args
142 79 100       165 return $ops{$op}{res}[0]
143             unless @list;
144              
145             # return default for one arg if defined, otherwise return the arg itself
146 75 100       199 return exists $ops{$op}{res}[1] ? $ops{$op}{res}[1] : $list[0]
    100          
147             if @list == 1;
148              
149 65 100       248 if ( $ops{$op}{right} ) {
150 3         6 @list = reverse @list;
151             }
152              
153 65         77 my $result = shift @list;
154 65         68 my $bool = 1;
155 65 100       176 my @triangle = $ops{$op}{chain} ? $bool : $result;
156              
157             my $apply = sub {
158 163     163   211 my ($a, $b) = @_;
159 163 100       438 return applyop( $op, $ops{$op}{right} ? ($b, $a) : ($a, $b) );
160 65         282 };
161              
162 65         141 while (@list) {
163 176         208 my $next = shift @list;
164              
165 176 100       359 if ( $ops{$op}{chain} ) {
166 75   100     177 $bool = $bool && $apply->($result, $next);
167 75         83 $result = $next;
168 75 100       241 push @triangle, $bool if $args{triangle};
169             }
170             else {
171 101         173 $result = $apply->($result, $next);
172 101 100       361 push @triangle, $result if $args{triangle};
173             }
174             }
175              
176 65 100       341 return @triangle if $args{triangle};
177 51 100       260 return $bool if $ops{$op}{chain};
178 30         237 return $result;
179             }
180              
181 1     1 1 15 sub zip { zipwith( ',', @_ ) }
182 2     2 1 17 sub cross { crosswith( ',', @_ ) }
183              
184             sub zipwith {
185 8     8 1 18 my ($op, $lhs, $rhs) = @_;
186 8         10 my ($a, $b, @results);
187              
188 8 100       25 $lhs = [$lhs] if ref $lhs ne 'ARRAY';
189 8 100       21 $rhs = [$rhs] if ref $rhs ne 'ARRAY';
190              
191 8   100     39 while (@$lhs && @$rhs) {
192 13         22 $a = shift @$lhs;
193 13         18 $b = shift @$rhs;
194 13         28 push @results, applyop($op, $a, $b);
195             }
196              
197 8         48 return @results;
198             }
199              
200             sub crosswith {
201 11     11 1 11809 my ($op, $lhs, $rhs) = @_;
202 11         13 my ($a, $b, @results);
203              
204 11 100       33 $lhs = [$lhs] if ref $lhs ne 'ARRAY';
205 11 100       24 $rhs = [$rhs] if ref $rhs ne 'ARRAY';
206              
207 11         19 for my $a (@$lhs) {
208 22         30 for my $b (@$rhs) {
209 43         72 push @results, applyop($op, $a, $b);
210             }
211             }
212              
213 11         61 return @results;
214             }
215              
216             sub hyper {
217 71     71 1 103517 my ($op, $lhs, $rhs, %args) = @_;
218              
219 71 100       229 if (@_ == 2) {
220 45 100       187 return map {
    100          
221 24 100       97 ref eq 'ARRAY' ? [ hyper($op, $_) ] :
222             ref eq 'HASH' ? { hyper($op, $_) } :
223             applyop($op, $_)
224             } @$lhs if ref $lhs eq 'ARRAY';
225              
226 15 50       63 return map {
    50          
227 6 50       34 $_ => ref eq 'ARRAY' ? [ hyper($op, $_) ] :
228             ref eq 'HASH' ? { hyper($op, $_) } :
229             applyop($op, $lhs->{$_})
230             } keys %$lhs if ref $lhs eq 'HASH';
231              
232 0 0       0 return applyop($op, $$lhs) if ref $lhs eq 'SCALAR';
233 0         0 return applyop($op, $lhs);
234             }
235              
236 47   100     215 my $dwim_left = $args{dwim_left} || $args{dwim};
237 47   100     168 my $dwim_right = $args{dwim_right} || $args{dwim};
238              
239 47 100 100     285 if (ref $lhs eq 'HASH' && ref $rhs eq 'HASH') {
    100          
    100          
240 10         12 my %results;
241 10         14 my @keys = do {
242 10 100 100     45 if ($dwim_left && $dwim_right) { # intersection
    100          
    100          
243 3         10 grep { exists $rhs->{$_} } keys %$lhs;
  8         23  
244             }
245             elsif ($dwim_left) {
246 2         8 keys %$rhs;
247             }
248             elsif ($dwim_right) {
249 2         7 keys %$lhs;
250             }
251             else { # union
252 3         38 uniq keys %$lhs, keys %$rhs;
253             }
254             };
255              
256 10         24 for my $key (@keys) {
257 26         29 $results{$key} = do {
258 26 100 100     107 if ( exists $lhs->{$key} && exists $rhs->{$key} ) {
259 22         51 applyop( $op, $lhs->{$key}, $rhs->{$key} );
260             }
261             else {
262 4 100       30 exists $ops{$op}{res}[1] ? $ops{$op}{res}[1] :
    50          
263             exists $lhs->{$key} ? $lhs->{$key} :
264             $rhs->{$key} ;
265             }
266             };
267             }
268              
269 10         71 return %results;
270             }
271             elsif (ref $lhs eq 'HASH') {
272 3 50       11 die "Sorry, structures on both sides of non-dwimmy hyper() are not of same shape:\n"
273             . " left: HASH\n"
274             . " right: " . ref($rhs) . "\n"
275             unless $dwim_right;
276              
277 3         11 return map { $_ => applyop( $op, $lhs->{$_}, $rhs ) } keys %$lhs;
  7         19  
278             }
279             elsif (ref $rhs eq 'HASH') {
280 2 50       8 die "Sorry, structures on both sides of non-dwimmy hyper() are not of same shape:\n"
281             . " left: " . ref($rhs) . "\n"
282             . " right: HASH\n"
283             unless $dwim_left;
284              
285 2         8 return map { $_ => applyop( $op, $lhs, $rhs->{$_} ) } keys %$rhs;
  6         16  
286             }
287              
288 32         35 my $length;
289 32 100       70 $lhs = [$lhs] if ref $lhs ne 'ARRAY';
290 32 100       155 $rhs = [$rhs] if ref $rhs ne 'ARRAY';
291              
292 32 100 100     148 if (!$dwim_left && !$dwim_right) {
    100          
    100          
293 9 50       25 die "Sorry, arrayrefs passed to non-dwimmy hyper() are not of same length:\n"
294             . " left: " . @$lhs . " elements\n"
295             . " right: " . @$rhs . " elements\n"
296             if @$lhs != @$rhs;
297              
298 9         14 $length = @$lhs;
299             }
300             elsif (!$dwim_left) {
301 14         101 $length = @$lhs;
302             }
303             elsif (!$dwim_right) {
304 7         9 $length = @$rhs;
305             }
306             else {
307 2 50       7 $length = @$lhs > @$rhs ? @$lhs : @$rhs;
308             }
309              
310 32         40 my @results;
311 32         34 my $lhs_index = 0;
312 32         34 my $rhs_index = 0;
313 32         68 for (1 .. $length) {
314 111 100 100     247 $lhs_index = 0 if $dwim_left && $lhs_index > $#{$lhs};
  32         103  
315 111 100 100     256 $rhs_index = 0 if $dwim_right && $rhs_index > $#{$rhs};
  55         188  
316 111         263 push @results, applyop($op, $lhs->[$lhs_index], $rhs->[$rhs_index]);
317             }
318             continue {
319 111         158 $lhs_index++;
320 111         252 $rhs_index++;
321             }
322              
323 32         260 return @results;
324             }
325              
326             sub applyop {
327 414     414 1 1322 my ($op) = @_;
328 414         412 my $type;
329 414         627 ($op, $type) = _get_op_info($op);
330              
331 414 100       842 return unless $op;
332 408 100 100     2051 return $ops{$op}{sub}->( @_[1, 2] )
333             if $type eq 'infix'
334             || $type eq 'postcircumfix';
335 43         135 return $ops{$op}{sub}->( $_[1] );
336             }
337              
338             sub reverseop {
339 0     0 1 0 my ($op) = @_;
340              
341 0 0       0 return applyop( $op, $_[1] ) if $op =~ m{^ (?: pre | post ) fix : }x;
342 0         0 return applyop( $op, @_[1, 2] );
343             }
344              
345             sub _get_op_info {
346 493     493   563 my ($op) = @_;
347 493         1138 my ($type) = $op =~ m{^ (\w+) : }x;
348              
349 493 100       1025 if (!$type) {
350 282         292 $type = "infix";
351 282         465 $op = "infix:$op";
352             }
353              
354 493 100       1112 return unless exists $ops{$op};
355 487         1241 return $op, $type;
356             }
357              
358             1;
359              
360             __END__