File Coverage

blib/lib/perl5i/0/ARRAY.pm
Criterion Covered Total %
statement 12 141 8.5
branch 0 90 0.0
condition 0 38 0.0
subroutine 4 28 14.2
pod n/a
total 16 297 5.3


line stmt bran cond sub pod time code
1             # vi: set ts=4 sw=4 ht=4 et :
2             package perl5i::0::ARRAY;
3 1     1   32 use 5.010;
  1         4  
  1         110  
4              
5 1     1   8 use strict;
  1         4  
  1         44  
6 1     1   98 use warnings;
  1         2  
  1         62  
7 1     1   2993 no if $] >= 5.018000, warnings => 'experimental::smartmatch';
  1         118  
  1         7  
8              
9             sub ARRAY::first {
10 0     0     my ( $array, $filter ) = @_;
11              
12             # Deep recursion and segfault (lines 90 and 91 in first.t) if we use
13             # the same elegant approach as in ARRAY::grep().
14 0 0         if ( ref $filter eq 'Regexp' ) {
15 0     0     return List::Util::first( sub { $_ ~~ $filter }, @$array );
  0            
16             }
17              
18 0     0     return List::Util::first( sub { $filter->() }, @$array );
  0            
19              
20             }
21              
22             sub ARRAY::grep {
23 0     0     my ( $array, $filter ) = @_;
24              
25 0           my @result = CORE::grep { $_ ~~ $filter } @$array;
  0            
26              
27 0 0         return wantarray ? @result : \@result;
28             }
29              
30             sub ARRAY::all {
31 0     0     require List::MoreUtils;
32 0           return List::MoreUtils::all($_[1], @{$_[0]});
  0            
33             }
34              
35             sub ARRAY::any {
36 0     0     require List::MoreUtils;
37 0           return List::MoreUtils::any($_[1], @{$_[0]});
  0            
38             }
39              
40             sub ARRAY::none {
41 0     0     require List::MoreUtils;
42 0           return List::MoreUtils::none($_[1], @{$_[0]});
  0            
43             }
44              
45             sub ARRAY::true {
46 0     0     require List::MoreUtils;
47 0           return List::MoreUtils::true($_[1], @{$_[0]});
  0            
48             }
49              
50             sub ARRAY::false {
51 0     0     require List::MoreUtils;
52 0           return List::MoreUtils::false($_[1], @{$_[0]});
  0            
53             }
54              
55             sub ARRAY::uniq {
56 0     0     require List::MoreUtils;
57 0           my @uniq = List::MoreUtils::uniq(@{$_[0]});
  0            
58 0 0         return wantarray ? @uniq : \@uniq;
59             }
60              
61             sub ARRAY::minmax {
62 0     0     require List::MoreUtils;
63 0           my @minmax = List::MoreUtils::minmax(@{$_[0]});
  0            
64 0 0         return wantarray ? @minmax : \@minmax;
65             }
66              
67             sub ARRAY::mesh {
68 0     0     require List::MoreUtils;
69 0           my @mesh = List::MoreUtils::zip(@_);
70 0 0         return wantarray ? @mesh : \@mesh;
71             }
72              
73              
74             # Returns the code which will run when the object is used as a string
75             require overload;
76             my $overload_type = sub {
77             return unless ref $_[0];
78             my $str = overload::Method($_[0], q[""]);
79             my $num = overload::Method($_[0], "0+");
80             return "both" if $str and $num;
81             return "" if !$str and !$num;
82             return "str" if $str;
83             return "num" if $num;
84             };
85              
86              
87             sub ARRAY::diff {
88 0     0     my ($base, @rest) = @_;
89 0 0         unless (@rest) {
90 0 0         return wantarray ? @$base : $base;
91             }
92              
93             # XXX If I use carp here, the exception is "bizarre copy of ARRAY in
94             # ssasign ... "
95 0 0         die "Arguments must be array references" if grep { ref $_ ne 'ARRAY' } @rest;
  0            
96              
97 0           foreach my $array (@rest) {
98 0           $base = _diff_two($base, $array);
99             }
100              
101 0 0         return wantarray ? @$base : $base;
102             }
103              
104             sub _diff_two {
105             # Compare differences between two arrays.
106 0     0     my ($c, $d) = @_;
107              
108 0           my $diff = [];
109              
110             # For each element of $c, try to find if it is equal to any of the
111             # elements of $d. If not, it's unique, and has to be pushed into
112             # $diff.
113              
114 0           require List::MoreUtils;
115 0           foreach my $item (@$c) {
116 0 0         unless (
117 0     0     List::MoreUtils::any( sub { _are_equal( $item, $_ ) }, @$d )
118             )
119             {
120 0           push @$diff, $item;
121             }
122             }
123              
124 0           return $diff;
125             }
126              
127             sub ARRAY::intersect {
128 0     0     my ($base, @rest) = @_;
129              
130 0 0         unless (@rest) {
131 0 0         return wantarray ? @$base : $base;
132             }
133              
134             # XXX If I use carp here, the exception is "bizarre copy of ARRAY in
135             # ssasign ... "
136 0 0         die "Arguments must be array references" if grep { ref $_ ne 'ARRAY' } @rest;
  0            
137              
138 0           foreach my $array (@rest) {
139 0           $base = _intersect_two($base, $array);
140             }
141              
142 0 0         return wantarray ? @$base : $base;
143             }
144              
145             sub _intersect_two {
146             # Compare differences between two arrays.
147 0     0     my ($c, $d) = @_;
148              
149 0           my $intersect = [];
150              
151             # For each element of $c, try to find if it is equal to any of the
152             # elements of $d. If it is, it's shared, and has to be pushed into
153             # $intersect.
154              
155 0           require List::MoreUtils;
156 0           foreach my $item (@$c) {
157 0 0         if (
158 0     0     List::MoreUtils::any( sub { _are_equal( $item, $_ ) }, @$d )
159             )
160             {
161 0           push @$intersect, $item;
162             }
163             }
164              
165 0           return $intersect;
166             }
167              
168             sub _are_equal {
169 0     0     my ($r1, $r2) = @_;
170              
171             # given two scalars, decide whether they are identical or not,
172             # recursing over deep data structures. Since it uses recursion,
173             # traversal is done depth-first.
174             # Warning: complex if-then-else decision tree ahead. It's ordered on
175             # my perceived and anecdotical take on the frequency of occurrence
176             # of each reftype: most popular on top, most rare on the bottom.
177             # This way we return as early as possible.
178              
179             # undef eq undef
180 0 0 0       return 1 if !defined $r1 and !defined $r2;
181              
182             # One is defined, one isn't
183 0 0 0       return if defined $r1 xor defined $r2;
184              
185 0           my( $ref1, $ref2 ) = (ref $r1, ref $r2);
186              
187 0 0 0       if( !$ref1 and !$ref2 ) {
    0 0        
    0          
188 0           my $is_num1 = SCALAR::is_number($r1);
189 0           my $is_num2 = SCALAR::is_number($r2);
190 0 0 0       if( $is_num1 xor $is_num2 ) {
    0          
191             # One's looks like a number, the other doesn't.
192             # Can't be equal.
193 0           return 0;
194             }
195             elsif( $is_num1 ) {
196             # They're both numbers
197 0           return $r1 == $r2;
198             }
199             else {
200             # They're both strings
201 0           return $r1 eq $r2;
202             }
203             }
204             elsif( $ref1 eq $ref2 ) {
205 0 0         if ( $ref1 ~~ [qw(Regexp GLOB CODE)] ) {
    0          
    0          
    0          
206 0           return $r1 eq $r2;
207             }
208             elsif ( $ref1 eq 'ARRAY' ) {
209 0           return _equal_arrays( $r1, $r2 );
210             }
211             elsif ( $ref1 eq 'HASH' ) {
212 0           return _equal_hashes( $r1, $r2 );
213             }
214             elsif ( $ref1 ~~ [qw(SCALAR REF)] ) {
215 0           return _are_equal($$r1, $$r2);
216             }
217             else {
218             # Must be an object
219 0           return _equal_objects( $r1, $r2 );
220             }
221             }
222             elsif( $ref1 and $ref2 ) {
223             # They're both refs, but not of the same type
224 0           my $is_overloaded1 = overload::Overloaded($r1);
225 0           my $is_overloaded2 = overload::Overloaded($r2);
226              
227 0 0 0       if( $is_overloaded1 and $is_overloaded2 ) {
228             # Two overloaded objects
229 0           return _equal_overload( $r1, $r2 );
230             }
231             else {
232             # One's an overloaded object, the other is not or
233             # Two plain refs different type or
234             # non-overloaded objects of different type.
235 0           return 0;
236             }
237             }
238             else {
239             # One is a ref, one is not
240 0 0         my $is_overloaded = $ref1 ? overload::Overloaded($r1)
241             : overload::Overloaded($r2);
242              
243 0 0         if( $is_overloaded ) {
244             # One's an overloaded object, one's a plain scalar
245 0 0         return $ref1 ? _equal_overload_vs_scalar($r1, $r2)
246             : _equal_overload_vs_scalar($r2, $r1);
247             }
248             else {
249             # One's a plain ref or object, one's a plain scalar
250 0           return 0;
251             }
252             }
253             }
254              
255              
256             # Two objects, same class
257             sub _equal_objects {
258 0     0     my($r1, $r2) = @_;
259              
260             # No need to check both, they're the same class
261 0           my $is_overloaded = overload::Overloaded($r1);
262              
263 0 0         if( !$is_overloaded ) {
264             # Neither are overloaded, they're the same class, are they the same object?
265 0           return $r1 eq $r2;
266             }
267             else {
268 0           return _equal_overload( $r1, $r2 );
269             }
270             }
271              
272              
273             # Two objects, possibly different classes, both overloaded.
274             sub _equal_overload {
275 0     0     my($obj1, $obj2) = @_;
276              
277 0           my $type1 = $overload_type->($obj1);
278 0           my $type2 = $overload_type->($obj2);
279              
280             # One of them is not overloaded
281 0 0 0       return if !$type1 or !$type2;
282              
283 0 0 0       if( $type1 eq 'both' and $type2 eq 'both' ) {
    0 0        
    0 0        
    0 0        
284 0   0       return $obj1 == $obj2 || $obj1 eq $obj2;
285             }
286             elsif(
287             ($type1 eq 'num' and $type2 eq 'str') or
288             ($type1 eq 'str' and $type2 eq 'num')
289             )
290             {
291             # They're not both numbers, not both strings, and not both both
292             # Must be str vs num.
293 0 0         return $type1 eq 'num' ? $obj1+0 eq "$obj2"
294             : $obj2+0 eq "$obj1";
295             }
296             elsif( 'num' ~~ [$type1, $type2] ) {
297 0           return $obj1 == $obj2;
298             }
299             elsif( 'str' ~~ [$type1, $type2] ) {
300 0           return $obj1 eq $obj2;
301             }
302             else {
303 0           die "Should never be reached";
304             }
305             }
306              
307              
308             # One overloaded object, one plain scalar
309             # STRING != OBJ
310             # NUMBER != OBJ
311             # STRING eq OBJeq
312             # STRING eq OBJboth
313             # STRING != OBJ== (using == will throw a warning)
314             # NUMBER == OBJ==
315             # NUMBER eq OBJeq
316             # NUMBER == OBJboth
317             sub _equal_overload_vs_scalar {
318 0     0     my($obj, $scalar) = @_;
319              
320 0           my $type = $overload_type->($obj);
321 0 0         return unless $type;
322              
323 0 0         if( SCALAR::is_number($scalar) ) {
324 0 0         if( $type eq 'str' ) {
325 0           $obj eq $scalar;
326             }
327             else {
328 0           $obj == $scalar;
329             }
330             }
331             else {
332 0 0         if( $type eq 'num' ) {
333             # Can't reliably compare
334 0           return;
335             }
336             else {
337 0           $obj eq $scalar;
338             }
339             }
340             }
341              
342             sub _equal_arrays {
343 0     0     my ($r1, $r2) = @_;
344             # They can only be equal if they have the same nÂș of elements.
345 0 0         return if @$r1 != @$r2;
346              
347 0           foreach my $i (0 .. @$r1 - 1) {
348 0 0         return unless _are_equal($r1->[$i], $r2->[$i]);
349             }
350              
351 0           return 1;
352             }
353              
354             sub _equal_hashes {
355 0     0     my ($r1, $r2) = @_;
356             # Hashes can't be equal unless their keys are equal.
357 0 0         return unless ( %$r1 ~~ %$r2 );
358              
359             # Compare the equality of the values for each key.
360 0           foreach my $key (keys %$r1) {
361 0 0         return unless _are_equal( $r1->{$key}, $r2->{$key} );
362             }
363              
364 0           return 1;
365             }
366              
367             1;